-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use MVC action results for formatting viewmodel response #315
Comments
@markphillips100 thanks for raising this issue. From what I have understood you'd expect to be able to do something like: public class MyHandler : ICompositionRequestsHandler
{
[HttpGet("/foo/bar")]
public Task Handle(HttpRequest request)
{
request.HttpContext.BadRequest( new { /* bad request details model */ } );
return Task.CompletedTask;
}
} The |
I'm assuming there's possibly other result types that would need to be catered for, not just bad request. E.g. NotFound, Conflict, Forbid, etc, hence why I only employed a generic interface to write the response. That aside, yes indeed, what should happen if more than one handler for the same route sets an action result. I'm not sure to be honest unless only using subscribers - in that situation I'd say the single composition handler owns the results. When there are multiple composition handlers for the same route I guess there would need to be some convention that all parties agree on. It's necessary to agree on the route anyway. Would just allowing only one to be registered and drop any subsequent results perhaps work? In other words, allow any handler to register a result but ignore them if one has already been registered. This is kinda what I do with the extension now. |
Let's see a use case. We could have something like: public static void BadRequest(this HttpRequest request, object model)
{
//something here to store the action result for later usage
} public static void NotFound(this HttpRequest request)
{
//something here to store the action result for later usage
} And then three handlers for the same route: public class AHandler : ICompositionRequestsHandler
{
[HttpGet("/foo/bar")]
public Task Handle(HttpRequest request)
{
request.BadRequest( new { /* bad request details model */ } );
return Task.CompletedTask;
}
} public class AnotherHandler : ICompositionRequestsHandler
{
[HttpGet("/foo/bar")]
public Task Handle(HttpRequest request)
{
request.NotFound();
return Task.CompletedTask;
}
} public class AThirdHandler : ICompositionRequestsHandler
{
[HttpGet("/foo/bar")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel();
vm.SomeData = "some data";
return Task.CompletedTask;
}
} The third one is interesting because it would be very similar to the following: public class AThirdHandler : ICompositionRequestsHandler
{
[HttpGet("/foo/bar")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel();
vm.SomeData = "some data";
request.ContentResult(vm); //assuming a default HTTP 200
//or request.Ok(vm);
return Task.CompletedTask;
}
} how does the resulting HTTP response look like? Is there any way we could express in a single response the "messages" all handlers are trying to convey? |
Not really as the intent of individual responses are tightly coupled to the http status code. A not found is a 404, a content 200, bad request 400, etc. This is I guess why I only imagined supporting any 4xx results but only 4xx types. 500 is an exception and 200 are resolved using normal objectresult semantics in the SC code using the composed VM dynamic. The first 4xx result wins. From the client perspective it doesn't matter which type of 4xx, they just know that something needs resolving their side |
I should clarify. Of course the client cares about which 4xx they get, just not whether a notfound beat a bad request to the wire |
Everything makes sense, and honestly, I don't think there is any real issue with multiple handlers trying to set multiple action results. There is already a concurrency "issue" in setting the response code and documentation describes that. We can have a similar note on the documentation page for this feature. One last question, hopefully 😄: The first 4xx result wins or the last? The last would be way simpler to implement, the first requires some guards to be put in place to prevent the |
Last or first it doesn't matter I think. The only real requirement is if there is one it is returned instead of the VM. Thanks for the discussion @mauroservienti |
@markphillips100 I dropped the ball on this, sorry about that. I should be able to raise a PR soon. |
@markphillips100 I raised #322. I'm not sure I understand how to change the
Can you please suggest how to use it? |
Don't worry about "dropping the ball". We all lead busy lives at times hey. I've been used an internal customized-for-my-needs version in the meantime. My
This code is dependent on a new addition to your
Note, the
Not really relevant except to just show how I use the extension but I'll explain my use case. My handlers/subscribers use mediator queries returning "result" types. These types are not mvc results, i.e. the command query layer isn't coupled to mvc. Obviously then I need to map them to relevant action results and I do this via a pattern match extension:
Notice the ValidationProblemDetails there surfaced as a 400 BadRequest. This supports the ideal of not throwing an exception for 400 errors. My OneOf ActionResult match handlers then get processed by calling the new
And from a handler this looks like this, where the only real logic is what to do with "ok" data, e.g. add it to viewmodel, raise composition event, etc:
|
Thanks @markphillips100. I just realized that when going through the context all the formatters stuff is handled automatically. Do you think it's worth raising an exception if someone is trying to use this new features and output formatters are not enabled? (They are disabled by default) Second question, do you see any issue with enabling formatters by default in v2, and allowing disabling them merely for backward compatibility? |
Raising an exception would be useful for sure, and I was going to suggest making output formatters the default too :-) As an aside, is there any follow up on System.Text.Json support for dynamic (expando) serialization? Would be nice to have the option of which to use from the MVC perspective, although I typically am using Json.Net anyway due to NServiceBus in process so no big problem having both serializers. |
Raised #323 to track this. I'll add the exception to the PR.
I'm having a hard time keeping track of what they are doing. Based on this comment dotnet/runtime#31175 (comment) it seems that .NET 6 comes with support for JsonNode but they won't support DynamicOjbect out of the box by default, but at least we could implement support for it. Also some more info in dotnet/runtime#29690 and dotnet/runtime#53195. |
I guess I understand them not wanting to add a dependency from the tree shaking perspective. And perhaps creating our own might be the way. A task for another time. |
@markphillips100, it's available on Nuget. Many thanks for your patience. |
Wonderful @mauroservienti . I shall update asap. Many thanks again. |
@mauroservienti I've been experimenting with how I can return MVC action results from handlers. I'll give you some background first.
Many of my handlers utilize the mediator pattern with pipelines that can generate validation errors. Up until now I've been throwing a custom ValidationException with any validation errors that may occur. This exception then gets handled higher up in the request pipeline using custom middleware to map the exception to a standardized problem details response.
I'd like to avoid throwing exceptions for validation but to do so requires 2 things:
Basically I imagine a handler being able to tell SC that it would like a specific IActionResult to be used for the response rather than the viewmodel.
As for the response formatting a lot of code can be replaced (I think) by utilizing MVC result execution. The following extensions would support this.
Then, the
if (useOutputFormatters) {
code block becomes:I provided a
SetActionResult
extension to help supply the required action result to SC but not certain this is the best way.What do you think?
The text was updated successfully, but these errors were encountered: