ServiceComposer.AspNetCore icon indicating copy to clipboard operation
ServiceComposer.AspNetCore copied to clipboard

Add support for ApiVersionAttribute at the class level

Open mauroservienti opened this issue 5 years ago • 5 comments
trafficstars

mauroservienti avatar Jun 27 '20 17:06 mauroservienti

I did a bit of research on the ApiVersion package that supports MVC controllers to see if it could help here. In short, I got lost down the rabbit hole that is MVC's application model so decided instead to post a question on that package's repo.

I haven't digested the detailed response yet, hopefully will tomorrow. Thought I'd post here in case you were interested.

markphillips100 avatar Mar 09 '21 17:03 markphillips100

Oh, that's a lot if information to digest!

Thanks @markphillips100

mauroservienti avatar Mar 09 '21 17:03 mauroservienti

I've digested the detailed post and determined that endpoint route delegates can indeed be versioned by simply applying an ApiVersionModel to a fake MVC ActionDescriptor. Here's an example that would use a query string api-version=n in the url:

ActionDescriptor GetAction(ApiVersion apiVersion)
{
    var model = new ApiVersionModel(apiVersion);

    var action = new ActionDescriptor();
    action.SetProperty(model);

    return action;
}

endpoints.MapGet("/api/hello", async context =>
{
    await context.Response.WriteAsync("Hello World v1!");
}).WithMetadata(GetAction(new ApiVersion(1, 0)));


endpoints.MapGet("/api/hello", async context =>
{
    await context.Response.WriteAsync("Hello World v2!");
}).WithMetadata(GetAction(new ApiVersion(2, 0)));

So assuming AddApiVersioning() is called in the host's startup this will accept the following routes:

  1. /api/hello?api-version=1
  2. /api/hello?api-version=2

You could use other forms of versioning as needed too without changing the metadata, i.e. use /api/v{version:apiVersion}/... in the URL path if required.

So assuming we could add the required metadata onto a composition endpoint, i.e. build the ApiVersionModel from ApiVersion attributes applied to the Handle method, then the route will only be routed to for the versions supported. For other unsupported versions 400 is returned with some headers indicating what versions are supported. Standard api versioning behaviour.

Now the big questions relevant to ServiceComposer are:

  1. When there are more than one ICompositionRequestsHandler for the same route and both theoretically can have a set of supported version attributes, which one is used to determine the ApiVersionModel?
  2. The single composition endpoint route delegate would need to filter the component types passed to HandleComposableRequest based upon the current request's api version (httpContext.GetRequestedApiVersion()) and some "metadata" relating each component type to a list of their supported versions. This metadata is only necessary in order to not reflect the types for the existence of version attributes on each request. The end result though is to only include components that support the request's version.
  3. How to integrate without coupling to the api versioning package directly? In other words, is it worth creating support via a separate package using some extensibility interfaces? This would then support the notion of perhaps using a builder for service collection to allow extensions to include the main package if not already registered and also register the extensibility types, like the following:
services.AddViewModelComposition().AddApiVersioning()

I'll leave it there so you can digest at your leisure. There's absolutely no rush @mauroservienti .

markphillips100 avatar Mar 10 '21 07:03 markphillips100

I can't say that I know much about this framework, but I'm happy to help answer any questions related to API Versioning that can help you achieve your goals. Based on this discussions, here's some points to consider (in no particular order):

  • The API version format is the only thing that currently is really opinionated and is based on the Microsoft REST guidelines
  • API Versioning does not care about attributes; it cares about IApiVersionProvider. This is realized by attributes and conventions out-of-the-box, but custom options are available
  • You free to define your own metadata for versioning and it can bridge to API versioning with minimal effort. This would be mostly likely achieved by a custom IControllerConvention you create and register through: services.AddApiVersioning( options => options.Conventions.Add( new MyCustomConvention() )
  • Controllers/APIs are collated by (controller) name. This is because semantically equivalent routes are difficult to match up. Fro example, api/v{v:apiVersion}/values/{id} and api/v{version:apiVersion}/values/{id:int} are semantically equivalent, but use different route parameter names and constraints.
  • API Versioning existed (and even helped drive) Endpoint Routing so relying fully on endpoint metadata is a design choice I can full switch to; however, I'm definitely open to the idea of attaching metadata in a more neutral way that can more easily work between the legacy routing model (which is still supported) and Endpoint Routing. This could lead to a path that doesn't require a fake ActionDescriptor, ActionModel, or ControllerModel if it ends up not being necessary

commonsensesoftware avatar Jun 18 '21 16:06 commonsensesoftware

@commonsensesoftware thanks for the highly valuable insights (and for your patience too). I'll have to digest those and all that @markphillips100 wrote https://github.com/ServiceComposer/ServiceComposer.AspNetCore/issues/151#issuecomment-795026553

mauroservienti avatar Aug 09 '21 13:08 mauroservienti