-
-
Save dj-nitehawk/6e23842dcb7640b165fd80ba57967540 to your computer and use it in GitHub Desktop.
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..." }); | |
} | |
} |
@pradeepgururani just tried the following by making the post-processor generic, and it works.
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 extending Endpoint
.
public class AquaEndpoint<TRequest, TResponse>: Endpoint<TRequest, Result<TResponse>> where TRequest : class
{
public override void Configure()
{
DontAutoSendResponse();
PostProcessor<AquaResponseSender<TRequest, TResponse>>(); // this is brute force because it is not working with following signature:
/*
The type 'Aqua.Api.AquaResponseSender<TRequest,Ardalis.Result.Result<TResponse>>' must be convertible to
'FastEndpoints.IPostProcessor<TRequest,Ardalis.Result.Result<TResponse>>' in order to use it as
parameter 'TPostProcessor' in the generic method
'void FastEndpoints.Endpoint<TRequest,TResponse>.PostProcessor<TPostProcessor>()'
*/
base.Configure();
}
}
then my endpoint looks like this:
public class Update(IMediator mediator) : AquaEndpoint<UpdateAgeWiseMasterRequest, Result<AgeWiseMasterRecord>>
{
public override void Configure()
{
//PostProcessor<AquaResponseSender<UpdateAgeWiseMasterRequest, Result<AgeWiseMasterRecord>>>();
Put(UpdateAgeWiseMasterRequest.Route);
}
public override async Task HandleAsync(UpdateAgeWiseMasterRequest request, CancellationToken cancellationToken)
{
}
}
Here is PostProcessor:
sealed class AquaResponseSender<TRequest, TResponse> : IPostProcessor<TRequest, Result<TResponse>>
{
public async Task PostProcessAsync(IPostProcessorContext<TRequest, Result<TResponse>> context, CancellationToken cancellationToken)
{
}
}
However with these things together AquaResponseSender
is not triggering. If I comment PostProcessor<AquaResponseSender<TRequest, TResponse>>();
in base class and have it on actual Endpoint with specific type like this PostProcessor<AquaResponseSender<UpdateAgeWiseMasterRequest, Result<AgeWiseMasterRecord>>>();
then it is working. Working only for unhandled exceptions and all handled business scenarios are not executed because of context.HttpContext.ResponseStarted()
check.
Am I on right track with the idea of extending EndPoint
? Reason to go on that path is to avoid putting DontSend...
and PostProcessor
on each endpoint though I anyway will have to make them inherit from new base class.
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);
})
{Edit: Hold up on this, I might have this wrong...}
I am using the example as above and it works perfectly for me. But I have a couple of endpoints (OAuth related, of course) that I need to do a Redirect
back to my client as the endpoint is being called by the OAuth provider. I guess the question is how could I avoid using the GlobalResponseSender
when I don't want/need it?
I don't want to create a fork of Result
and there is no StatusCode
value for a redirect in their package. This is what the endpoint looks like:
public override void Configure()
{
Get(OAuthCallbackRequest.RouteTemplate);
}
public override async Task HandleAsync(OAuthCallbackRequest request, CancellationToken ct = default)
{
var options = AcuityAuthOptions.Value;
if (!TryValidateOAuthResponseAsync(request, options, out var invalidResponseRoute))
{
await SendRedirectAsync(invalidResponseRoute, allowRemoteRedirects: true);
return;
}
var oauthStateValuesResult = await GetStateValuesAsync(options, request);
if (!oauthStateValuesResult.IsSuccess)
{
var invalidStateRoute = CreateInvalidStateValuesRoute(options);
await SendRedirectAsync(invalidStateRoute, allowRemoteRedirects: true);
return;
}
// because this is a redirect from the user's client browser the cookie will still be present and we can get the user.
var userId = User.GetUserId();
var command = request.ToCommand(oauthStateValuesResult.Value, userId, ComposeOAuthCallbackToServerUri());
var result = await mediator.Send(command, ct);
var claims = command.OAuthStateValues ?? [];
var route = CreateResponseRoute(
errors: result.Errors.ToArray(),
uriBase: GetResultUri(claims),
uriPath: options.AuthorizationResultClientPath,
originPage: GetClientOriginUri(claims),
success: result.IsSuccess);
await SendRedirectAsync(route, allowRemoteRedirects: true);
return;
}
Is there a way to make the two work at the same time? TIA
@nhwilly
since you wrote {Edit: Hold up on this, I might have this wrong...}
, i didn't read your code.
if you need further assitance regarding this, don't hesitate to create a new gh issue or discord post with a "simplified" repro ;-)
How do I register my PostProcessor which needs to be generic to handle TRequest and TResponse from different flows?
following is my code:
and I am registering it like this:
PostProcessor<AquaResponseSender<TRequest, TResponse>>();
but it never gets called. what am I missing?