Created
January 21, 2024 05:40
-
-
Save dj-nitehawk/6e23842dcb7640b165fd80ba57967540 to your computer and use it in GitHub Desktop.
Results pattern with a Post-Processor doing the response sending.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var bld = WebApplication.CreateBuilder(args); | |
bld.Services | |
.AddFastEndpoints() | |
.SwaggerDocument(); | |
var app = bld.Build(); | |
app.UseFastEndpoints() | |
.UseSwaggerGen(); | |
app.Run(); | |
sealed class Request | |
{ | |
public bool IsHappyPath { get; set; } | |
} | |
sealed class Response | |
{ | |
public string Message { get; set; } | |
} | |
sealed class TestEndpoint : Endpoint<Request, Result<Response>> //set response type to ardalis Result<T> | |
{ | |
public override void Configure() | |
{ | |
Get("test/{IsHappyPath}"); | |
AllowAnonymous(); | |
DontAutoSendResponse(); //disable auto send to allow post-processor to handle sending | |
PostProcessor<ResponseSender>(); //register post processor | |
Description( | |
x => x.Produces<Response>(200) //override swagger response type for 200 ok | |
.Produces<ErrorResponse>(400)); | |
} | |
public override Task<Result<Response>> ExecuteAsync(Request r, CancellationToken ct) | |
=> Task.FromResult(HelloService.SayHello(r.IsHappyPath)); //return a Result<T> | |
} | |
sealed class ResponseSender : IPostProcessor<Request, Result<Response>> | |
{ | |
public async Task PostProcessAsync(IPostProcessorContext<Request, Result<Response>> ctx, CancellationToken ct) | |
{ | |
if (!ctx.HttpContext.ResponseStarted()) | |
{ | |
var result = ctx.Response!; | |
switch (result.Status) | |
{ | |
case ResultStatus.Ok: | |
await ctx.HttpContext.Response.SendAsync(result.GetValue()); | |
break; | |
case ResultStatus.Invalid: | |
var failures = result.ValidationErrors.Select(e => new ValidationFailure(e.Identifier, e.ErrorMessage)).ToList(); | |
await ctx.HttpContext.Response.SendErrorsAsync(failures); | |
break; | |
} | |
} | |
} | |
} | |
sealed class HelloService | |
{ | |
public static Result<Response> SayHello(bool isHappyPath) | |
{ | |
if (!isHappyPath) | |
{ | |
return Result<Response>.Invalid( | |
new List<ValidationError> | |
{ | |
new() | |
{ | |
Identifier = nameof(Request.IsHappyPath), | |
ErrorMessage = "I am unhappy!" | |
} | |
}); | |
} | |
return Result<Response>.Success(new() { Message = "hello world..." }); | |
} | |
} |
Reason to go on that path is to avoid putting DontSend... and PostProcessor on each endpoint
in that case, i think the cleanest solution would be to use a global post processor and avoid subclassing the endpoint class.
sealed class GlobalResponseSender : IGlobalPostProcessor
{
public async Task PostProcessAsync(IPostProcessorContext ctx, CancellationToken ct)
{
if (!ctx.HttpContext.ResponseStarted())
{
var result = (IResult)ctx.Response!; //cast is necessary since we don't know what the actual response dto type is
switch (result.Status)
{
case ResultStatus.Ok:
await ctx.HttpContext.Response.SendAsync(result.GetValue());
break;
case ResultStatus.Invalid:
var failures = result.ValidationErrors.Select(e => new ValidationFailure(e.Identifier, e.ErrorMessage)).ToList();
await ctx.HttpContext.Response.SendErrorsAsync(failures);
break;
}
}
}
}
register it like so:
app.UseFastEndpoints(
c => c.Endpoints.Configurator =
ep =>
{
ep.DontAutoSendResponse();
ep.PostProcessor<GlobalResponseSender>(Order.Before);
})
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for quick response. Actually I am trying to extend
Endpoint
to handle some work in my own implementation. Here is my code that is extendingEndpoint
.then my endpoint looks like this:
Here is PostProcessor:
However with these things together
AquaResponseSender
is not triggering. If I commentPostProcessor<AquaResponseSender<TRequest, TResponse>>();
in base class and have it on actual Endpoint with specific type like thisPostProcessor<AquaResponseSender<UpdateAgeWiseMasterRequest, Result<AgeWiseMasterRecord>>>();
then it is working. Working only for unhandled exceptions and all handled business scenarios are not executed because ofcontext.HttpContext.ResponseStarted()
check.Am I on right track with the idea of extending
EndPoint
? Reason to go on that path is to avoid puttingDontSend...
andPostProcessor
on each endpoint though I anyway will have to make them inherit from new base class.