diff --git a/samples/Demo/Beef.Demo.Api/Controllers/PersonController.cs b/samples/Demo/Beef.Demo.Api/Controllers/PersonController.cs new file mode 100644 index 00000000..62e50ef9 --- /dev/null +++ b/samples/Demo/Beef.Demo.Api/Controllers/PersonController.cs @@ -0,0 +1,38 @@ +#nullable enable + +using CoreEx.Results; + +namespace Beef.Demo.Api.Controllers +{ + public partial class PersonController + { + /// + /// Extend Response. + /// + /// A resultant string. + [HttpPost("api/v1/persons/extend-response", Name = "Person_ExtendResponse")] + [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public Task ExtendResponse(string? name) + => _webApi.PostWithResultAsync(Request, p => + { + return Result.GoAsync(() => _manager.ExtendResponseAsync(name)) // Execute the business logic. + .ThenAs(r => // Then handle response where Result.IsSuccess; otherwise, allow to bubble out. + { + var ia = ValueContentResult.CreateResult(r, HttpStatusCode.OK, null, _webApi.JsonSerializer, p.RequestOptions, false, null); // Use standard CoreEx ValueContentResult to handle the response. + if (ia is ValueContentResult vcr) // Ensure is a ValueContentResult, and if so, then manipulate. + { + vcr.BeforeExtension = hr => // Extend the reponse by adding a new header. + { + hr.Headers.Add("X-Beef-Test", "123"); + return Task.CompletedTask; + }; + } + + return ia; + }); + }, alternateStatusCode: HttpStatusCode.NoContent, operationType: CoreEx.OperationType.Unspecified); + } +} + +#nullable restore \ No newline at end of file diff --git a/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs b/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs index 7e3d05d6..19f37324 100644 --- a/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs +++ b/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs @@ -202,6 +202,13 @@ public partial interface IPersonData /// The identifier. /// A resultant . Task> SimulateWorkAsync(Guid id); + + /// + /// Extend Response. + /// + /// The Name. + /// A resultant . + Task> ExtendResponseAsync(string? name); } #pragma warning restore diff --git a/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs b/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs index 2d02a4e1..4372172c 100644 --- a/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs +++ b/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs @@ -230,6 +230,9 @@ public Task DeleteWithEfAsync(Guid id) => DataInvoker.Current.InvokeAsync(this, /// public Task> SimulateWorkAsync(Guid id) => SimulateWorkOnImplementationAsync(id); + /// + public Task> ExtendResponseAsync(string? name) => ExtendResponseOnImplementationAsync(name); + /// /// Provides the property and database column mapping. /// diff --git a/samples/Demo/Beef.Demo.Business/Data/PersonData.cs b/samples/Demo/Beef.Demo.Business/Data/PersonData.cs index aa1eb67b..e85c9ca0 100644 --- a/samples/Demo/Beef.Demo.Business/Data/PersonData.cs +++ b/samples/Demo/Beef.Demo.Business/Data/PersonData.cs @@ -166,5 +166,7 @@ private static Task Add3OnImplementationAsync(Person value) if (value == null) throw new ArgumentNullException(nameof(value)); return Task.CompletedTask; } + + private Task> ExtendResponseOnImplementationAsync(string? name) => name is null ? Result.Fail("Name is needed dude!").AsTask() : Result.Ok(name).AsTask(); } } \ No newline at end of file diff --git a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs index 6c55335f..fefd1419 100644 --- a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs +++ b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs @@ -221,6 +221,13 @@ public partial interface IPersonDataSvc /// The identifier. /// A resultant . Task> SimulateWorkAsync(Guid id); + + /// + /// Extend Response. + /// + /// The Name. + /// A resultant . + Task> ExtendResponseAsync(string? name); } #pragma warning restore diff --git a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs index 9e23ff32..0d4e5206 100644 --- a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs +++ b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs @@ -45,6 +45,7 @@ public partial class PersonDataSvc : IPersonDataSvc private Func? _deleteWithEfOnAfterAsync; private Func? _getDocumentationOnAfterAsync; private Func>? _simulateWorkOnAfterAsync; + private Func>? _extendResponseOnAfterAsync; #endregion @@ -309,6 +310,13 @@ public async Task GetDocumentationAsync(Guid id) .ThenAsync(r => _simulateWorkOnAfterAsync?.Invoke(r, id) ?? Result.SuccessTask) .Then(r => _events.PublishValueEvent("WorkIt", new Uri($"/person/", UriKind.Relative), $"Work", "Simulated")); }, new InvokerArgs { IncludeTransactionScope = true, EventPublisher = _events }); + + /// + public Task> ExtendResponseAsync(string? name) + { + return Result.GoAsync(_data.ExtendResponseAsync(name)) + .ThenAsync(r => _extendResponseOnAfterAsync?.Invoke(r, name) ?? Result.SuccessTask); + } } #pragma warning restore diff --git a/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs b/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs index 0ebf4858..debd2583 100644 --- a/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs +++ b/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs @@ -242,6 +242,13 @@ public partial interface IPersonManager /// The identifier. /// A resultant . Task> SimulateWorkAsync(Guid id); + + /// + /// Extend Response. + /// + /// The Name. + /// A resultant . + Task> ExtendResponseAsync(string? name); } #pragma warning restore diff --git a/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs b/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs index 3df39467..99755f71 100644 --- a/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs +++ b/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs @@ -167,6 +167,11 @@ public partial class PersonManager : IPersonManager private Func>? _simulateWorkOnBeforeAsync; private Func>? _simulateWorkOnAfterAsync; + private Func>? _extendResponseOnPreValidateAsync; + private Action? _extendResponseOnValidate; + private Func>? _extendResponseOnBeforeAsync; + private Func>? _extendResponseOnAfterAsync; + #endregion /// @@ -674,6 +679,20 @@ await MultiValidator.Create() .ThenAsync(r => _simulateWorkOnAfterAsync?.Invoke(r, id) ?? Result.SuccessTask) .Then(r => Cleaner.Clean(r)); }, InvokerArgs.Unspecified); + + /// + public Task> ExtendResponseAsync(string? name) => ManagerInvoker.Current.InvokeAsync(this, (_, ct) => + { + return Result.Go() + .Then(() => Cleaner.CleanUp(name)) + .ThenAsync(() => _extendResponseOnPreValidateAsync?.Invoke(name) ?? Result.SuccessTask) + .ValidateAsync(() => MultiValidator.Create() + .Additional(mv => _extendResponseOnValidate?.Invoke(mv, name)), cancellationToken: ct) + .ThenAsync(() => _extendResponseOnBeforeAsync?.Invoke(name) ?? Result.SuccessTask) + .ThenAsAsync(() => _dataService.ExtendResponseAsync(name)) + .ThenAsync(r => _extendResponseOnAfterAsync?.Invoke(r, name) ?? Result.SuccessTask) + .Then(r => Cleaner.Clean(r)); + }, InvokerArgs.Unspecified); } #pragma warning restore diff --git a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml index 9f06318c..667d5399 100644 --- a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml +++ b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml @@ -105,7 +105,8 @@ entities: parameters: [ { name: Id, text: '{{Person}} identifier', type: Guid, isMandatory: true } ]}, - { name: SimulateWork, type: Custom, withResult: true, returnType: string?, webApiRoute: simulate, webApiMethod: HttpGet, webApiStatus: OK, primaryKey: true, eventSubject: Work:Simulated, eventValue: '"WorkIt"' } + { name: SimulateWork, type: Custom, withResult: true, returnType: string?, webApiRoute: simulate, webApiMethod: HttpGet, webApiStatus: OK, primaryKey: true, eventSubject: Work:Simulated, eventValue: '"WorkIt"' }, + { name: ExtendResponse, type: Custom, returnType: string?, excludeWebApi: true, webApiRoute: extend-response, withResult: true, parameters: [ { name: Name } ] } ] } diff --git a/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs b/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs index 6795834c..4949159f 100644 --- a/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs +++ b/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs @@ -349,6 +349,15 @@ public partial interface IPersonAgent /// The . /// A . Task> SimulateWorkAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); + + /// + /// Extend Response. + /// + /// The Name. + /// The optional . + /// The . + /// A . + Task> ExtendResponseAsync(string? name, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); } } diff --git a/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs b/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs index a51414fe..79d90e6f 100644 --- a/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs +++ b/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs @@ -170,6 +170,10 @@ public Task GetDocumentationAsync(Guid id, HttpRequestOptions? reque /// public Task> SimulateWorkAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) => GetAsync("api/v1/persons/simulate", requestOptions: requestOptions, args: HttpArgs.Create(new HttpArg("id", id)), cancellationToken: cancellationToken); + + /// + public Task> ExtendResponseAsync(string? name, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => PostAsync("api/v1/persons/extend-response", requestOptions: requestOptions, args: HttpArgs.Create(new HttpArg("name", name)), cancellationToken: cancellationToken); } } diff --git a/samples/Demo/Beef.Demo.Test/PersonTest.cs b/samples/Demo/Beef.Demo.Test/PersonTest.cs index 6c3833f0..18ca2463 100644 --- a/samples/Demo/Beef.Demo.Test/PersonTest.cs +++ b/samples/Demo/Beef.Demo.Test/PersonTest.cs @@ -1312,5 +1312,30 @@ public void I420_Simulate_Perf() } #endregion + + #region ExtendResponse + + [Test, TestSetUp] + public void J110_ExtendResponse_Error() + { + AgentTester.Test() + .ExpectStatusCode(HttpStatusCode.BadRequest) + .ExpectError("Name is needed dude!") + .Run(a => a.ExtendResponseAsync(null)); + } + + [Test, TestSetUp] + public void J120_ExtendResponse_Success() + { + var resp = AgentTester.Test() + .ExpectStatusCode(HttpStatusCode.OK) + .ExpectValue("Sweet!") + .Run(a => a.ExtendResponseAsync("Sweet!")); + + if (resp.Response.Headers.TryGetValues("X-Beef-Test", out var values)) + Assert.That(values, Is.EquivalentTo(new string[] { "123" })); + } + + #endregion } } \ No newline at end of file