-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Design: IEndpointConventionBuilder Extensions Unusable By Other Extensions #39604
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
Comments
How exactly do you plan to leverage the ability to inherit from We've returned public concrete
The big reason we sealed Rightly or wrongly, we wanted the While you could in theory derive from If you want to add support for additional extension methods on a
I tested adding the following overloads to // Old
public static RouteHandlerBuilder Accepts<TRequest>(this RouteHandlerBuilder builder,
string contentType, params string[] additionalContentTypes) where TRequest : notnull
// New
public static TBuilder Accepts<TBuilder, TRequest>(this TBuilder builder,
string contentType, params string[] additionalContentTypes) where TBuilder : IEndpointConventionBuilder where TRequest : notnull
// New
public static TBuilder Accepts<TBuilder, TRequest>(this TBuilder builder,
string contentType, params string[] additionalContentTypes) where TBuilder : RouteHandlerBuilder where TRequest : notnull The problem is that the C# compiler seems to always prefer the overload with less generic parameters unless you specify the generic parameters explicitly. The following would still use the old overload: app.MapPost("/accepts-xml", () => Accepted()).Accepts<Person>("application/xml"); I verified this is also the case for overloads that previously had no generic parameters. |
Maybe it's okay if the existing overloads |
Thanks for contacting us. We're moving this issue to the |
Inheriting from The current plan of action is that the builder at this level in composition from API Versioning would return it's own If we agree that the generic variants makes sense, the fact that For the scenarios that require multiple, generic parameters, I understand the challenge/problem. I also understand why/how this led down a path where a concrete type such as public static TBuilder Accepts<TBuilder, TRequest>(
this TBuilder builder,
string contentType,
params string[] additionalContentTypes)
where TBuilder : IEndpointConventionBuilder
where TRequest : notnull will not work because the compiler does not allow specifying only some of the generic type parameters explicitly this way. It would have to be However, I feel with some additional thinking and refactoring, this is a solvable problem albeit it another form. For example, if the second generic type parameter was lifted out into a callback, the following could work: public class AcceptMetadataBuilder
{
public AcceptMetadataBuilder Of<TRequest>(string contentType) where TRequest : notnull
{
// ...
return this;
}
public AcceptMetadata Build() { /* ... */ }
}
// ...
public static TBuilder Accept<TBuilder>(this TBuilder builder, Action<AcceptMetadataBuilder> configure)
where TBuilder : IEndpointConventionBuilder
{
var acceptMetadata = new AcceptMetadataBuilder();
configure(acceptMetadata);
builder.WithMetadata(acceptMetadata.Build());
return builder;
}
// ...
builder.Accept(request => request.Of<Person>("application/json")); That's just one possible idea. I'm sure there are other possible ways. Of course, the flavor that doesn't use the builder.Accept(typeof(Person), "application/json"); |
@halter73 API Versioning has an official Preview release with this support. As expected, the community is immediately champing at the bit for parity with the OpenApiRouteHandlerBuilderExtensions. In my attempt to appease the masses, I realized that the current state of the union is even worse than I thought. The current I'm trying to get the compatible .NET 6.0 release out within the next month (or less). Ideally, I'd like to have signature parity with wherever this issue is going to land. That will minimize the impact for the .NET 7.0 release. Expanding upon what's already been discussed, here's the current preview solution I've come up with: api.MapGet("/people/{id:int}", (int id) => new Person(){ Id = id })
.Produces(response => response.Body<Person>())
.HasApiVersion(1.0);
api.MapPost("/people", (Person person) => Results.Created("/people/" + person.Id, person))
.Accepts(request => request.Body<Person>())
.Produces(response => response.Body<Person>(), 201)
.HasApiVersion(1.0);
api.MapPatch("/people/{id:int}", (int id, Person person) => Results.NoContent())
.Accepts(request => request.Body<Person>().FormattedAs("application/merge-patch+json"))
.Produces(response => response.Body<Person>().FormattedAs("application/json"), 204)
.HasApiVersion(1.0); To see the complete picture, here's the end-to-end Minimal OpenAPI Example. |
@commonsensesoftware We recently did some of this as part of #41428 and then also did this for The I'll admit that this API review was focused on the MapGroup use case and less on the API Versioning use case. I realize that the extension methods we left out are exactly the methods you proposed updating in this issue. Would you be okay with having to write something like |
Thanks for contacting us. We're moving this issue to the |
Background and Motivation
API Versioning has been investigating support for Minimal APIs per dotnet/aspnet-api-versioning#751. In doing so, it has come to light that the extension methods for IEndpointConventionBuilder are inconsistently implemented and many of them have little-to-no usably by other extensions such as API Versioning.
The primary issues relate to OpenApiRouteHandlerBuilderExtensions.cs. These extensions are very likely to be used by customers in conjunction with API Versioning, but cannot be for the following reasons:
sealed
RouteHandlerBuilder
typeFor completeness, a similar problem exists for FallbackEndpointRouteBuilderExtensions.cs. Fallback endpoints are not expected to be used with API Versioning, but it could affect other extensions. These extension methods accept and return IEndpointConventionBuilder, which makes them more usable than OpenApiRouteHandlerBuilderExtensions; however, the lack of passing through a more specific type means that the order setup by developers matters.
The design and implementation of each set of extension methods appears to have been done by different people, at different times, and with different design review considerations.
Proposed API
There doesn't appear to be a clear reason why these decisions were made. There seems to be no reason to not implement all of the extension methods using the same approach that @JamesNK used in RoutingEndpointConventionBuilderExtensions.cs. This would mean that all extension methods have the form of:
This approach appears to have been lightly discussed in #8902 previously, which might explain why future extension methods did not follow suite.
The proposed change would benefit not just API Versioning, but any other extension that needs to add/change significant parts of the default Minimal API implementation.
Risks
Changing the signature of the existing APIs are a breaking change, but I believe that adding the intended generic implementations can live side-by-side with the existing non-generic variants.
If the proposal were accepted, when would that happen? As it stands, this issue cascades across APIs. API Versioning would be required to reimplement all of the applicable, existing extension methods to retain feature parity for non-versioned Minimal APIs. Furthermore, the unnecessary non-generic extensions methods have to be retained just as they do in ASP.NET Core - likely forever more. If API Versioning doesn't reimplement the extensions methods, then there is a feature gap that must be filled by customers.
API Versioning is looking for guidance to achieve the right level of synergy in both the short and long terms.
Example Usage
The tentative design for Minimal APIs for API Versioning will look something like:
cc: @davidfowl @JamesNK
The text was updated successfully, but these errors were encountered: