Skip to content

IResult support in MVC #40639

@brunolins16

Description

@brunolins16

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.

Metadata

Metadata

Assignees

Labels

DocsThis issue tracks updating documentationfeature-model-bindingold-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions