Skip to content

Introduce interfaces to describe IResult types #42187

@brunolins16

Description

@brunolins16

Background and Motivation

The #40656 issue made all IResult concrete types publicly available, at that time the main goal was to make the Minimal API endpoints unit tests easier. As part of the original design was decided to remove all the hierarchy and make all types sealed, however, with the introduction of the ability to add Filters to an endpoint shows that, based on community feedback, without a more generalized way to describe an IResult type (as we have in ActionResult types) the filter implementation become more complex, since is needed to have a type check for every possibility.

Proposed API

The proposal is to introduce the following interfaces that will describe the public IResult types:

namespace Microsoft.AspNetCore.Http.HttpResults;

+public interface IStatusCodeHttpResult
+{
+    int StatusCode { get; }
+}

+public interface IValueHttpResult : IStatusCodeHttpResult
+{
+    object? RawValue { get; }
+}

+public interface IValueHttpResult<TValue> : IValueHttpResult
+{
+    TValue? Value { get; }
+}

+public interface IContentHttpResult
+{
+    string? ContentType { get; }
+}

+public interface IFileHttpResult : IContentHttpResult
+{
+    string? FileDownloadName { get; }
+}

+public interface IMultiResults
+{
+    IResult Result { get; }
+}

Also, as part of the proposal the IResult types must be updated to implement the new interfaces:

namespace Microsoft.AspNetCore.Http.HttpResults;

-public sealed class Accepted : IResult, IEndpointMetadataProvider
+public sealed class Accepted : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class AcceptedAtRoute : IResult, IEndpointMetadataProvider
+public sealed class AcceptedAtRoute : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class AcceptedAtRoute<TValue> : IResult, IEndpointMetadataProvider
+public sealed class AcceptedAtRoute<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed class Accepted<TValue> : IResult, IEndpointMetadataProvider,
+public sealed class Accepted<TValue> : IResult, IEndpointMetadataProvider, IValueHttpResult<TValue>

-public sealed class BadRequest : IResult, IEndpointMetadataProvider
+public sealed class BadRequest : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class BadRequest<TValue> : IResult, IEndpointMetadataProvider
+public sealed class BadRequest<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed class Conflict : IResult, IEndpointMetadataProvider
+public sealed class Conflict : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class Conflict<TValue> : IResult, IEndpointMetadataProvider
+public sealed class Conflict<TValue> : IResult, IEndpointMetadataProvider, IValueHttpResult<TValue>

-public sealed partial class ContentHttpResult : IResult
+public sealed partial class ContentHttpResult : IResult, IStatusCodeHttpResult, IContentHttpResult

-public sealed class Created : IResult, IEndpointMetadataProvider
+public sealed class Created : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class CreatedAtRoute : IResult, IEndpointMetadataProvider
+public sealed class CreatedAtRoute : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class CreatedAtRoute<TValue> : IResult, IEndpointMetadataProvider
+public sealed class CreatedAtRoute<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed class Created<TValue> : IResult, IEndpointMetadataProvider
+public sealed class Created<TValue> : IResult, IEndpointMetadataProvider, IValueHttpResult<TValue>

-public sealed partial class FileContentHttpResult : IResult
+public sealed partial class FileContentHttpResult : IResult, IFileHttpResult

-public sealed class FileStreamHttpResult : IResult
+public sealed class FileStreamHttpResult : IResult, IFileHttpResult

-public sealed partial class JsonHttpResult<TValue> : IResult
+public sealed partial class JsonHttpResult<TValue> : IResult, IValueHttpResult<TValue>, IContentHttpResult

-public class NoContent : IResult, IEndpointMetadataProvider
+public class NoContent : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class NotFound : IResult, IEndpointMetadataProvider
+public sealed class NotFound : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class NotFound<TValue> : IResult, IEndpointMetadataProvider
+public sealed class NotFound<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed class Ok : IResult, IEndpointMetadataProvider
+public sealed class Ok : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class Ok<TValue> : IResult, IEndpointMetadataProvider
+public sealed class Ok<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed partial class PhysicalFileHttpResult : IResult
+public sealed partial class PhysicalFileHttpResult : IResult, IFileHttpResult

-public sealed class PushStreamHttpResult : IResult
+public sealed class PushStreamHttpResult : IResult, IFileHttpResult

-public sealed partial class StatusCodeHttpResult : IResult
+public sealed partial class StatusCodeHttpResult : IResult, IStatusCodeHttpResult

-public sealed class UnauthorizedHttpResult : IResult
+public sealed class UnauthorizedHttpResult : IResult, IStatusCodeHttpResult

-public sealed class UnprocessableEntity : IResult, IEndpointMetadataProvider
+public sealed class UnprocessableEntity : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult

-public sealed class UnprocessableEntity<TValue> : IResult, IEndpointMetadataProvider
+public sealed class UnprocessableEntity<TValue> : IResult, IEndpointMetadataProvider,  IValueHttpResult<TValue>

-public sealed class VirtualFileHttpResult : IResult
+public sealed class VirtualFileHttpResult : IResult, IFileHttpResult

-public sealed class Results<TResult1, TResultN> : IResult, IEndpointMetadataProvider
+public sealed class Results<TResult1, TResultN> : IResult, IMultiResults, IEndpointMetadataProvider

Usage Examples

app.MapGet("/weatherforecast", () =>
{
    WeatherForecast[] forecast = WeatherForecast.GetForecast();
    return Results.Ok(forecast);
})
.AddFilter(async (context, next) =>
{
    var result = await next(context);

    return result switch
    {
        IValueHttpResult<WeatherForecast[]> weatherResut => new WeatherForecastsResult(weatherResut.Value, weatherResut.StatusCode),        
        IValueHttpResult valueResult => Results.Extensions.Xml(valueResult.RawValue, valueResult.StatusCode),
        _ => result
    };
});

Alternative Designs

A simple hierarchy is proposed in my original design, that helps users to create their code, however, an alternative design is remove the hierarchy (as same as the public available types) and update the IResult types to implement all of them explicitly. Eg.:

namespace Microsoft.AspNetCore.Http.HttpResults;

+public interface IStatusCodeHttpResult
+{
+    int StatusCode { get; }
+}

+public interface IValueHttpResult
+{
+    object? RawValue { get; }
+}

+public interface IValueHttpResult<TValue>
+{
+    TValue? Value { get; }
+}

+public interface IContentHttpResult
+{
+    string? ContentType { get; }
+}

+public interface IFileHttpResult : IContentHttpResult
+{
+    string? FileDownloadName { get; }
+}

+public interface IMultiResults
+{
+    IResult Result { get; }
+}


-public sealed class Ok<TValue> : IResult, IEndpointMetadataProvider
+public sealed class Ok<TValue> : IResult, IEndpointMetadataProvider, IStatusCodeHttpResult, IValueHttpResult, IValueHttpResult<TValue>

Risks

I don't see a risk in the API proposal since it only introduces a new set of interfaces.

Metadata

Metadata

Assignees

Labels

DocsThis issue tracks updating documentationapi-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcblog-candidateConsider mentioning this in the release blog postfeature-minimal-actionsController-like actions for endpoint routingold-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions