Background and Motivation
Today, with the new Microsoft.AspNetCore.Http.Results class is very easy to get confused and implement a Controller Action similar with that, or any other variation of the methods exposed by this class:
[HttpGet]
public IResult Get()
{
return Results.Ok();
}
Also, is very easy to use an Microsoft.AspNetCore.Http.IResult custom implementation as a return of a controller action, like this:
public class MyCustomResult : IResult
{
public Task ExecuteAsync(HttpContext httpContext) => Task.CompletedTask;
}
[HttpGet]
public IResult Get()
{
return new MyCustomResult();
}
Both examples will not throw exception but will serialize the class. For the first example the action will return the following payload:
{
"value": null,
"statusCode": 200,
"contentType": null
}
Proposed API
The proposal is the creation of a new public action result that will store an instance of the IResult and call the IResult.ExecuteAsync method instead instead of using the IActionResultExecutor to write the Http Response. With this, we will miss all the content-negotiation available in MVC through the ObjectResultExecutor but we will have the correct serialization and all the other components, like Middleware, Filters etc., will work.
+namespace Microsoft.AspNetCore.Mvc;
+public class HttpResultsActionResult : ActionResult
+{
+ public IResult Result { get; }
+ public MinimalActionResult(IResult result){}
+ public override Task ExecuteResultAsync(ActionContext context){}
+}
The controller's action can return a Microsoft.AspNetCore.Http.IResult that will be converted to the new HttpResultsActionResult when the ActionResultTypeMapper.Convert is invoked. Eg.:
[HttpGet]
public IResult Get() => Results.Ok(new Todo() { Id = 1, Title = "Sample todo"});
The sample action will produce the following payload:
{
"id": 1,
"title": "Sample todo"
}
Usage Examples
Controller's action using the HttpResultsActionResult
[HttpGet()]
public ActionResult Get(int id)
{
var result = Results.Ok(new Contact() { ContactId = id });
return new HttpResultsActionResult(result);
}
Result's filter using the HttpResultsActionResult
public class HttpResultResultFilter : IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{
if (context is { Result: HttpResultsActionResult { Result: IResult httpResult } })
{
// Do something after the result executes.
}
}
}
If the proposal #40656 is approved, the results instance could be further inspected, eg.:
public class HttpResultResultFilter : IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{
if (context is
{
Result: HttpResultsActionResult
{ Result: IStatusCodeHttpResult { StatusCode: StatusCodes.Status200OK } }
})
{
// Do something after the result executes.
}
}
}
Alternative Designs
The new type could have a private constructor, that could avoid undesired instantiation
+namespace Microsoft.AspNetCore.Mvc;
+public class HttpResultsActionResult : ActionResult
+{
+ public IResult Result { get; }
+ public override Task ExecuteResultAsync(ActionContext context){}
+}
Or the new action result could be internal instead, that will make this proposal irrelevant, but the feature will be available, however, will not be possible to create Result Filters as shown in the example.
Background and Motivation
Today, with the new
Microsoft.AspNetCore.Http.Resultsclass is very easy to get confused and implement a Controller Action similar with that, or any other variation of the methods exposed by this class:Also, is very easy to use an
Microsoft.AspNetCore.Http.IResultcustom implementation as a return of a controller action, like this:Both examples will not throw exception but will serialize the class. For the first example the action will return the following payload:
{ "value": null, "statusCode": 200, "contentType": null }Proposed API
The proposal is the creation of a new
publicaction result that will store an instance of theIResultand call theIResult.ExecuteAsyncmethod instead instead of using theIActionResultExecutorto write the Http Response. With this, we will miss all the content-negotiation available in MVC through theObjectResultExecutorbut we will have the correct serialization and all the other components, likeMiddleware,Filtersetc., will work.The controller's action can return a
Microsoft.AspNetCore.Http.IResultthat will be converted to the newHttpResultsActionResultwhen theActionResultTypeMapper.Convertis invoked. Eg.:The sample action will produce the following payload:
{ "id": 1, "title": "Sample todo" }Usage Examples
Controller's action using the
HttpResultsActionResultResult's filter using the
HttpResultsActionResultIf the proposal #40656 is approved, the results instance could be further inspected, eg.:
Alternative Designs
The new type could have a
privateconstructor, that could avoid undesired instantiationOr the new action result could be
internalinstead, that will make this proposal irrelevant, but the feature will be available, however, will not be possible to create Result Filters as shown in the example.