-
Notifications
You must be signed in to change notification settings - Fork 711
Support API Version Interleaving Across Types #230
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
In order to make the attribute [MapToApiVersion] work across types, we've changed the way of seaching the actions and the controllers. I've created a branch from my fork : https://github.com/dawaji/aspnet-api-versioning/pulls Could you take a look, and tell me what do you think ? Thanks! |
Hey @commonsensesoftware, would you be open to tackling this issue by accepting an public interface IControllerSelectorPredicateProvider
{
public Func<HttpControllerDescriptor, bool> CreateControllerSelectionPredicate( ApiVersion version );
public HttpControllerDescriptor HandleAmbiguousMatches( ApiVersion version, IEnumerable<HttpControllerDescriptor> ambiguousMatches, Func<IEnumerable<HttpControllerDescriptor>, Exception> exceptionProvider );
} ? The value provided by An adapted static HttpControllerDescriptor ResolveController( CandidateAction[] directRouteCandidates, ApiVersion requestedVersion, ApiVersioningOptions options )
{
Contract.Requires( directRouteCandidates != null );
Contract.Requires( directRouteCandidates.Length > 0 );
Contract.Requires( requestedVersion != null );
Contract.Requires( options != null );
var controllerDescriptor = default( HttpControllerDescriptor );
var predicate = options.ControllerSelectorPredicateProvider.CreateControllerSelectionPredicate( requestedVersion );
var matches = directRouteCandidates
.Select( candidate => candidate.ActionDescriptor.ControllerDescriptor )
.Where( predicate )
.Distinct();
if ( matches.Count <= 1 )
{
return matches.FirstOrDefault();
}
return options.HandleAmbiguousMatches( requestedVersion, matches, CreateAmbiguousControllerException );
} The default class ExactVersionMatchControllerSelectorPredicateProvider : IControllerSelectorPredicateProvider
{
public Func<HttpControllerDescriptor, bool> CreateControllerSelectionPredicate( ApiVersion version )
{
return ( controller ) => controller.GetDeclaredApiVersions().Contains( requestedVersion );
}
public HttpControllerDescriptor HandleAmbiguousMatches( ApiVersion version,
IEnumerable<HttpControllerDescriptor> ambiguousMatches,
Func<IEnumerable<HttpControllerDescriptor>, Exception> exceptionProvider )
{
throw exceptionProvider( ambiguousMatches );
}
} Background: we have a rather large API (not versioned as of yet, would become So, our class HighestImplementedVersionMatchControllerSelectorPredicateProvider : IControllerSelectorPredicateProvider
{
public Func<HttpControllerDescriptor, bool> CreateControllerSelectionPredicate( ApiVersion version )
{
return (controller) => controller
.GetDeclaredApiVersions()
.Any( version => version <= requestedVersion && version.Status == null );
}
public HttpControllerDescriptor HandleAmbiguousMatches( ApiVersion version,
IEnumerable<HttpControllerDescriptor> ambiguousMatches,
Func<IEnumerable<HttpControllerDescriptor>, Exception> exceptionProvider )
{
return ambiguousMatches
.OrderByDescending( controller
=> controller
.GetDeclaredApiVersions()
.Where( version => version.Status == null )
.OrderByDescending( version => version.MajorVersion )
.ThenByDescending( version => version.MinorVersion )
.FirstOrDefault() )
.FirstOrDefault()
}
} If this is a route that you are open to pursuing, ping me and I'll throw together a PR. |
While I welcome and appreciate the help to drive this enhancement forward, there a couple of fundamental things that are being glossed over.
Any acceptable PR needs to have:
That might all sound like I'm resistant to the change, but I'm not. I do know that there is lot of devil in the details. The relative simplicity of consuming these libraries is largely due to keeping the devil at bay. ;) |
@vzwick you can address your scenario without this feature. The design is already meant to solve the exact scenario you are describing (e.g. transitioning from no versioning to formal versioning). Your A lot of people use it for other purposes, but the primary use case for setting I see that you're using attribute routing and your opting to use URL segment versioning. Since you're not locked in yet, I'd urge you to reconsider. I know the URL segment method is popular, but it actually causes a lot of grief (for you) when the API version is embedded in the URL. There's a number of things I could list off (as I have in many other threads), but let's keep it relevant to just URL management for now. Assuming you have route In contrast, if you choose the URL segment versioning method, you'll have to update every controller to have Also keep in mind that there is not an equivalent of IDirectRouteProvider in ASP.NET Core. I imagine that you'll move to that platform at some point. By relying on Web API-specific features, it may make your transition less smooth. Regardless of the path you choose, you can meet your goals to today without this change. Hopefully that provides you some insight on how to achieve and perhaps even a little advice making the evolution your API easier on you. |
Hey @commonsensesoftware, thanks for your feedback and recommendations. We are aware of Our actual issue is this:
The latter is what we would like to address by the approach I outlined above. Unless there is some other way to express "use the requested implementation where available, otherwise, fall back to |
Ah ... I see what you're trying to do now. Personally, I'm not a fan of things falling back. It might seem convenient as a service author, but it's unpredictable for a client. The server should only match what the client asks for, save for the one exception where both the server and client have a single, implied default version which is not formally named nor has a version number. I can see how inheritance and/or type interleaving can make service authoring easier, which is why this issue now formally exists. I've have seen teams blatantly copy all the code to new spaces actually. I call it the Copy-Paste-Replace (CPR) method. If done correctly, it's not the worst idea ever, but we can do better. I like my controller types to be completely independent per API version without inheritance. It makes the routing and wire protocol components crystal clear. If a lot of logic is put into the controller, this clearly begs to for reuse. My approach and suggestion for remedy that is to push these things to actual business layer components that more naturally support inheritance, dependency injection, and the likes. By organizing things that way, the CPR method isn't such a big deal. The only things that should really be changing in a new API version the wire protocol stuff anyway. For very small changes, interleaving actions is feasible. One of the few cases (IMHO) where an action can reasonably be inherited is something like That leads me to the final note of caution. While you can fallback, make sure you consider the consequences of removing an API version. Deprecating an API version doesn't remove it. The only way to remove it is - well - actually remove it. This very difficult to rationalize with fallbacks. API version ranges are not supported out-of-the-box for the same reason. Things need to be deterministic. All that said, I think there definitely an opportunity to improve the service authoring experience. |
Overview
Support API versioning where the API versions can interleave across implemented types - namely controllers. The ability to interleave API versions across types would simplify inheritance and other partial implementation scenarios.
Challenges
Supporting API version interleaving across types would require a higher level of aggregation in order to disambiguate route candidates. Ideally, all API versions would be pre-aggregated at startup to optimize lookup performance as evaluating the information only at runtime could be expensive. This new type of aggregation would likely require a significant change to the current implementation.
Possible impacted features:
The text was updated successfully, but these errors were encountered: