Comments (10)
Hi all,
I don't think this library should handle all existing ProblemDetails
objects in AspNet world. It takes arround 40 lines to map Ardalis.IResult
to a ProblemDetails
object.
I believe that adding just some guidance/examples in the (missing 😛 ) documentation would be enough.
As a first example how I did it:
- Added the ProblemDetails class from AspNetCore to my .NET framework MVC 5 project
- Added some extension methods for
Ardalis.Result
public static class ArdalisResultExtensions
{
public static int GetHttpStatusCode(this IResult result)
{
return result.Status switch
{
ResultStatus.Error => StatusCodes.Status422UnprocessableEntity,
ResultStatus.Forbidden => StatusCodes.Status403Forbidden,
ResultStatus.Unauthorized => StatusCodes.Status401Unauthorized,
ResultStatus.Invalid => StatusCodes.Status400BadRequest,
ResultStatus.NotFound => StatusCodes.Status404NotFound,
ResultStatus.Conflict => StatusCodes.Status409Conflict,
ResultStatus.CriticalError => StatusCodes.Status500InternalServerError,
ResultStatus.Unavailable => StatusCodes.Status503ServiceUnavailable,
ResultStatus.NoContent => StatusCodes.Status204NoContent,
_ => StatusCodes.Status200OK
};
}
public static ProblemDetails AsProblemDetails(this IResult result)
{
Guard.Against.Null(result, nameof(result));
return new ProblemDetails()
{
Title = result.Status.ToString(),
Detail = result.IsInvalid()
? JsonConvert.SerializeObject(result.ValidationErrors)
: JsonConvert.SerializeObject(result.Errors),
Status = result.GetHttpStatusCode(),
Type = result.ValueType.FullName
};
}
}
- And a new Json* method in my base MVC controller
protected internal ActionResult JsonProblemDetails(IResult result)
{
return new JsonProblemDetailsResult(result);
}
Now all my ajax calls to MVC endpoints return a standard response, with error codes that enable localized messages to the user from frontend.
from result.
I believe that creating a bespoke, AR ProblemDetails
class is the best approach. And it could be modeled closely to the FE ProblemDetails
class. I say this as that might help solve creating the same JSON payload returning to the client whether a FE-FV pipeline ValidationFailure
occurred or an AR ValidationError
was returned.
Same deserialization could occur.
Or does this create an implied dependency?
from result.
If we want to standardize the response, then we need to override the FE validation response. There's a hook to do that. FYI
from result.
Legit question:
ValidationError
contains a lot of detail, while standard Error
contains a collection of strings.
I can use JSON attributes to configure the response to take the shape most people are used to seeing while the deserialization still works.
Is my custom problem details better off supporting two different types of collections, or just one where many values are nullable depending on the type of content?
from result.
Update:
Creating a custom ProblemDetails class
As a first pass, I've written an extension method to use in place of ToMinimalApiResult
.
This approach requires a replacement for MVC's ProblemDetails
which carries two additional properties. One is a collection of strings and the other a collection of a new ValidationErrorDto
class.
This class allows for Ardalis.Result.ValidationError
and FluentValidation.ValidationFailure
to be mapped to a consistent validation detail item.
This approach creates two problems:
- The Asp Net Core
Results.Problem
method won't take a complex type. We can resort to manually constructing the response, but that means that we must supply a Json serialized string and where do we get those? A method argument works but seems nasty. That code looks like this:
private static Microsoft.AspNetCore.Http.IResult UnavailableEntity(IResult result)
{
var problem = new ProblemDetails
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.4",
Title = "Critical errors occurred",
Status = (int)HttpStatusCode.ServiceUnavailable,
Errors = result.Errors.Any() ? result.Errors : null,
ValidationErrors = result.ValidationErrors.Any() ? result.ValidationErrors.ToDto() : null
};
return Results.Json(problem, _jsonOptions, ProblemContentType, 500);
}
Using MVC's ProblemDetail Extensions
I like this idea best as it keeps things close to the framework. However, the validation errors and errors will end up in Extensions
which looks like this:
Extensions = new Dictionary<string, object?> { { "errors", validationErrors } }
This ProblemDetails
property carries this attribute:
[JsonExtensionData]
Everything serializes just fine. Clients, however can't deserialize Extensions
because they've been serialized as object
.
It is possible to resolve this with a JsonConverter
and concrete type to be used on the client.
Examples in the wild show people checking the Type
property of the response (which can be obtained by deserializing to ProblemDetails
). Based on the Type
value, you can specify the target class, such as ProblemDetailsInvalid
or ProblemDetailsWithErrors
to get at the Extensions
values.
Current Package Enhancement
In the current version of the ToMinimalApiResult
method, IResult
is cast before processing the response. In that case we are losing the CorrelationId
before we can put it into a ProblemDetails
.
In Any Case
- A new extension method would have to be added to
Ardalis.Result
to alter howErrors
are put into whatever we develop, making them accessible to clients as an enumerable. - Client classes will have to be supplied because JsonConverters are weird and no one wants to do that. Probably one for
ValidationErrors
and oneErrors
.
Considerations for using MVC ProblemDetails
- If we are using a json converter on the client, we can put both validation error entries and string error entries in the
Extensions
dictionary with a key oferror
. In this way resulting payloads would look more normal to non C# consumers.
Working on this today...
Looking for input
from result.
Success - 2xx
ValidationErrors - 4xx
Errors - 500
ProblemDetails
{
"type": "MyValidationErrorType",
"title": null,
"status": 400,
"detail": null,
"instance": null,
"errors": {
"severity": "Info"
}
}
{
"type": "MyValidationErrorType",
"title": null,
"status": 400,
"detail": null,
"instance": null,
"errors": [
{
"identifier": "PropertyName",
"errorMessage": "you idiot!",
"errorCode": "myCode",
"severity": 2
}
]
}
{
"type": "MyValidationErrorType",
"title": null,
"status": 400,
"detail": null,
"instance": null,
"validationErrors": [
{
"identifier": "PropertyName",
"errorMessage": "you idiot!",
"errorCode": "myCode",
"severity": 2
}
],
"errors": {
"severity": "Info"
}
}
from result.
OK, I've written something that works for me. But during our dB meeting you spoke about swapping some statements around to avoid casting as IResult
and losing CorrelationId
and LocationId
as they are not part of the interface.
Here's the awful workaround I ended up with, but I don't know how you intended it to work.
When I try to cast Result<T>
to Result
it returns null. I guess I'd expect that.
Am I using this Result
library wrong? All of my usages are handling Result<T>
Nothing calls Result
.
public static Microsoft.AspNetCore.Http.IResult ToApiResult<T>(this Result<T> result)
{
return result.Status switch
{
ResultStatus.Ok => result is Result ? Results.Ok() : Results.Ok(result.GetValue()),
ResultStatus.Created => Results.Created(result.Location, result.GetValue()),
ResultStatus.NotFound => NotFoundEntity(result),
ResultStatus.Unauthorized => Results.Unauthorized(),
ResultStatus.Forbidden => Results.Forbid(),
ResultStatus.Invalid => BadEntity(result),
ResultStatus.Error => UnprocessableEntity(result),
ResultStatus.Conflict => ConflictEntity(result),
ResultStatus.Unavailable => UnavailableEntity(result),
ResultStatus.CriticalError => CriticalEntity(result),
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
};
}
internal static Microsoft.AspNetCore.Http.IResult ToApiResult(this Result result)
{
return result.Status switch
{
ResultStatus.Ok => result is Result ? Results.Ok() : Results.Ok(result.GetValue()),
ResultStatus.Created => Results.Created(result.Location, result.GetValue()),
ResultStatus.NotFound => NotFoundEntity(result),
ResultStatus.Unauthorized => Results.Unauthorized(),
ResultStatus.Forbidden => Results.Forbid(),
ResultStatus.Invalid => BadEntity(result),
ResultStatus.Error => UnprocessableEntity(result),
ResultStatus.Conflict => ConflictEntity(result),
ResultStatus.Unavailable => UnavailableEntity(result),
ResultStatus.CriticalError => CriticalEntity(result),
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
};
}
from result.
I don't see where you are trying to cast Result
from Result<T>
in that code. Or is that the point? You couldn't, so you had to duplicate the switch statement?
from result.
The result of casting Result<T>
to Result
is null. So, yes. Had to duplicate the statemen.t
from result.
Definitely could (always) use more docs...
We do have them at: https://result.ardalis.com/
from result.
Related Issues (20)
- Do not drop netstandard2.0 HOT 1
- Constructor with parameters for ValidationError and ValidationError.Create HOT 1
- Add NET 8 to TargetFrameworks
- Add Support For FastEndpoints HOT 1
- Change the Status to Failed when any ValidationError is added to the Result. HOT 7
- Improve error/validation reporting by allowing for a strongly typed property, e.g. Result.Error<T>
- Deserializing always results in a Success object. HOT 5
- [Question] How to handle unsuccesful results at API level HOT 6
- Add Correlation Id to Error Statuses HOT 2
- Extend Result.Status to include "Created" and Validation Failures HOT 4
- Consider New Ardalis.Result.FastEndpoints package with base classes, extension methods HOT 2
- ✨ Result Status Helpers HOT 4
- Converting Invalid Result to ActionResult throws System.ArgumentNullException when Identifier is not set on ValidationError HOT 2
- Optional CorrelationId on ErrorList HOT 1
- Add error messages to Forbidden and UnAuthorized results HOT 2
- Result.Error returns null instead of Result when returned from a method called from a controller HOT 5
- Unsopported Type on Created Result when using ToMinimalApiResult method
- MinimalApiResultExtensions.ToMinimalApiResult is missing case for ResultStatus.NoContent HOT 3
- Add support for HttpStatusCode: Created to the ResultStatus mapping HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from result.