-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
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.