FluentResults
FluentResults copied to clipboard
[Question] Does ` FluentResults.Extensions.AspNetCore` include Problem Details?
It would be very nice if the Result HTTP Response was in a common 'standard' format such as: ProblemDetails
I did not see ProblemDetails mentioned, so I thought I would ask
Very cool library!! I really love it.
Also I am working on a way to integrate it with CSharpFunctionalExtensions - the author of CSharpFunctionalExtensions seems to have inspired you to write FluentResults
Thank your for the good feedback!
I had a very short look at the problem details concept last november but then I ignored it to save some time because I wanted to release a first mvp version of the FluentResults.Extensions.AspNetCore package.
If you see the need to use the ProblemDetails concept in this package you can try to integrate it and send an pr. Since November 2022 this asp.net core package have some thousands installs - so the interest is not that big in the community. I have to invest my time wisely. ;)
@jeffward01 @altmann. I needed to add ProblemDetails too.
This is what I did following the documentation.
I'm using .Net7
On program.cs
I added my custom Profile to handle the failing response
AspNetCoreResult.Setup(config => config.DefaultProfile = new CustomAspNetResultProblemDetailProfile());
This is the class CustomAspNetResultProblemDetailProfile
public class CustomAspNetResultProblemDetailProfile : DefaultAspNetCoreResultEndpointProfile
{
public override ActionResult TransformFailedResultToActionResult(FailedResultToActionResultTransformationContext context)
{
var result = context.Result;
if (result.HasError<ApiProblemDetailsError>(out var domainErrors))
{
var problemDetail = domainErrors.First().ProblemDetails;
return (HttpStatusCode)problemDetail.Status! switch
{
HttpStatusCode.NotFound => new NotFoundObjectResult(problemDetail),
HttpStatusCode.Unauthorized => new UnauthorizedObjectResult(problemDetail),
HttpStatusCode.BadRequest => new BadRequestObjectResult(problemDetail),
HttpStatusCode.Conflict => new ConflictObjectResult(problemDetail),
HttpStatusCode.UnprocessableEntity => new UnprocessableEntityResult(),
_ => new StatusCodeResult((int)problemDetail.Status)
};
}
return new StatusCodeResult(500);
}
}
And here the ApiProblemDetailsError
with some custom Errors
public abstract class ApiProblemDetailsError : Error
{
public ValidationProblemDetails ProblemDetails { get; }
protected ApiProblemDetailsError(HttpContext httpContext, HttpStatusCode statusCode, string title, IDictionary<string, string[]> errors)
: base(title)
{
ProblemDetails = new ValidationProblemDetails(errors)
{
Title = title,
Status = (int)statusCode,
Type = $"https://httpstatuses.io/{(int)statusCode}",
Instance = httpContext.Request.Path,
Extensions =
{
["traceId"] = Activity.Current?.Id ?? httpContext.TraceIdentifier
},
};
}
}
public class InvalidUserError : ApiProblemDetailsError
{
public InvalidUserError(HttpContext httpContext, IEnumerable<IdentityError> errors)
: base(httpContext, HttpStatusCode.BadRequest, "Invalid User", CreateErrorDictionary(errors))
{ }
private static IDictionary<string, string[]> CreateErrorDictionary(IEnumerable<IdentityError> identityErrors)
{
var errorDictionary = new Dictionary<string, string[]>(StringComparer.Ordinal);
foreach (var identityError in identityErrors)
{
var key = identityError.Code;
var error = identityError.Description;
errorDictionary.Add(key, error.Split(','));
}
return errorDictionary;
}
}
public class UnauthorizedError : ApiProblemDetailsError
{
public UnauthorizedError(HttpContext httpContext, string username, string resource)
: base(httpContext, HttpStatusCode.Unauthorized, "Unauthorized", CreateErrorDictionary(username, resource))
{
}
private static IDictionary<string, string[]> CreateErrorDictionary(string username, string resource)
{
var errorDictionary = new Dictionary<string, string[]>(StringComparer.Ordinal)
{
{ username, new[] { $"is not authorized to access {resource}." } }
};
return errorDictionary;
}
}
public class NotFoundError : ApiProblemDetailsError
{
public NotFoundError(HttpContext httpContext, string entityName)
: base(httpContext, HttpStatusCode.NotFound, "Not Found", CreateErrorDictionary(entityName))
{
}
private static IDictionary<string, string[]> CreateErrorDictionary(string entityName)
{
var errorDictionary = new Dictionary<string, string[]>(StringComparer.Ordinal)
{
{ entityName, new[] { $"'{entityName}' not found." } }
};
return errorDictionary;
}
}
You can check here
I hope this work for you.
And this is the way I'm calling my CustomError
var result = await userManager.CreateAsync(user, password);
if (!result.Succeeded)
{
return Result.Fail(new InvalidUserError(httpContextAccessor.HttpContext, result.Errors));
}
Hey guys, first version pushed.
Goal here is domain(result)<->api-presentation(problemdetails).
The glue in the middle: https://github.com/ElysiumLabs/FluentProblemDetails
Hey guys, first version pushed.
Goal here is domain(result)<->api-presentation(problemdetails).
The glue in the middle: https://github.com/ElysiumLabs/FluentProblemDetails
This looks interesting. Does this work with minimal APIs?
@frasermclean now it work 😁 Changed the name of repo: https://github.com/ElysiumLabs/FluentResults.Extensions.AspNetCore
@altmann if you want I can PR my repo into yours (extensions). What you think?