-
Notifications
You must be signed in to change notification settings - Fork 711
[Question] [ASP.NET Core Web API] - Multiple Route Prefixes for a single API Version #628
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
First, understand that OData doesn't intrinsically handle duplicate routes and EDM matching/parsing begins after the prefix. I recommend that you do not try to use the prefix as a sub application. You may not be, but I've seen that several times. There may be a way to beat OData into submission, but that was not the intent of the prefix design. If you're going to version by URL segment, then you need include the route constraint with everything else in the prefix. It should look something like this: app.UseMvc(
routes => routes.MapVersionedODataRoutes(
"odata",
"api/v{version:apiVersion}/Contexts({contextId})",
builder.GetEdmModels() ) ); Based on what you have shared so far, I believe this will solve the problem. The template contains route parameters that are not processed by the OData URL parser. I hope that helps. |
Sorry ... I didn't read the question fully. Why is there?
These look to be the same resource. If you want permissions, you should do that another way. Alternatively, move In REST, the URL path is the identifier of the resource. Versioning by URL already violates this constraint as API Versioning for OData does not do some type of grouping by prefix as some people might expect. That would the behavior of a sub application. API Versioning sees all of the possible route candidates as a flat list. Grouping is done by route and API version just like all other routes. If it didn't behave this way, it would be incongruent with versioning non-OData controllers and versioning OData controllers by URL segment (despite my reservations) would not work as expected (due the prefix design of OData). The paths |
Ok let me try to clarify. My intention is to have different prefixes, for different controllers, on the same API version. In this example I have two resources, People and Applications. For the API routes to the PeopleController, I want the route prefixed by While I assume there are better ways to design the API to fit this scenario (move Contexts and API version to query parameters), unfortunately this is a extremely simplified example of a production application that is already in place, but I'm adding API Versioning to, while migrating to .net core 3.1, and the API is already built using these URI constraints. I'm attempting to find a way to introduce aspnet-api-versioning to our existing system, without requiring a version change. |
I think I understand. So you already have some kind of API version in the path? If that's the case, it's unfortunate, but workable. If not, then I would suggest query string or media type negotiation method instead. I can provide other recommendations about how to grandfather in your existing APIs. You can have multiple prefixes. What is not going to work or, at least, you're going to have problems with is the same resource stripes over multiple prefixes. I've seen this before with people using Setup 1 (Works)
Setup 2 (Has Problems)
If you're within the realm of Setup 1, then the following configuration should work: app.UseMvc(routes => {
routes.MapVersionedODataRoutes(
"odata-default",
"api/v{version:apiVersion}",
builder.GetEdmModels() ) ;
routes.MapVersionedODataRoutes(
"odata-by-context",
"api/v{version:apiVersion}/Contexts({contextId})",
builder.GetEdmModels() );
}); In your particular scenario, you may need to bucketize the EDMs yourself and pass them to the respective It probably helps to understand what the builder does:
As you can see, there's no discrimination that some EDM should be used for some routes and some are used for others. The flexibility in OData is a pro and con, but there's not really a universal way to figure out someone means in all scenarios. There are several ways you could address this by building up EDMs yourself, customizing/extending the builder, and so on. If you do build the EDMs yourself, you should always attach the associated API version with the ApiVersionAnnotation. This is how Hopefully that helps you get started. |
Thanks a lot! I’m exactly targeting Setup 1. I’ll take a stab at your suggestions, and report back. |
I've updated my sample following your suggestions, but I'll also provide a synopsis here. Updated configuration to app.UseMvc(
routeBuilder =>
{
/*
* Try to Achieve:
* GET ~/api/v1/People
* GET ~/api/v1/Contexts({contextId})/Applications
*/
var apiVersion = new ApiVersion( 1, 0 );
// Add ~/api/v1/People
routeBuilder.MapVersionedODataRoutes( "odata-default", "api/v{version:apiVersion}", GetPeopleModels( apiVersion ) );
// Add ~/api/v1/Contexts({contextId})/Applications
routeBuilder.MapVersionedODataRoutes( "odata-by-context", "api/v{version:apiVersion}/Contexts({contextId})", GetApplicationModels( apiVersion ) );
} );
private IEnumerable<IEdmModel> GetPeopleModels ( ApiVersion apiVersion )
{
var models = new List<IEdmModel>();
var builder = new ODataConventionModelBuilder().EnableLowerCamelCase();
var person = builder.EntitySet<Person>( "People" ).EntityType.HasKey( p => p.Id );
var model = builder.GetEdmModel();
model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) );
models.Add( model );
return models;
}
private IEnumerable<IEdmModel> GetApplicationModels ( ApiVersion apiVersion )
{
var models = new List<IEdmModel>();
var builder = new ODataConventionModelBuilder().EnableLowerCamelCase();
var order = builder.EntitySet<Application>( "Applications" ).EntityType.HasKey( o => o.Id );
var model = builder.GetEdmModel();
model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) );
models.Add( model );
return models;
} There is an initialization exception happening upon the first execution of Is there a way to specify which controllers should be parsed during route configuration? |
Exception details:
As you can see from the |
@commonsensesoftware any ideas to workaround this road block? I went down a rabbit hole into the OData route registration code and I couldn't find a way around registering all models during route registration. |
@commonsensesoftware If this isn't the right repo to gather more information about this scenario, do you know of another place. I'm quite aware that you're the only maintainer of this repository, and are spread too thin to answer everyone's questions. This seems to be outside the scope of api-versioning (maybe?). In that case, maybe this questions is better fit for another repo? |
Appreciate the understanding. You have a repro. Let me see if I can't get something combination working for you later today. If you use true subapplications (using |
I attempted using |
Were you able to find the time to look into this? |
@commonsensesoftware could you spare some time this week and help me resolve this issue? |
Any chance a solution was found to this issue? |
A quick follow up this issue. Ultimately, there has not been a way to associate the route prefix to the registered EDM(s). I've come to the conclusion that I've started working on the Endpoint Routing support and come up with a break through that is changing all the internal mechanics - in a good and simpler way. I've got the full test suite working in the Web API version and now I'm working updating things in Core. Once all of these changes are in, then you should be able to achieve the results you want in a pretty straight forward way. Stay tuned. ;) |
As previously mentioned, this is now an official supported concept. The This is now available in 5.0.0-RC.1. The official release will likely occur after 2 weeks of burn-in. If this doesn't resolve your issue, feel free to reopen the issue or open a new one. Thanks. |
Uh oh!
There was an error while loading. Please reload this page.
Framework: netcoreapp3.1
I'm having trouble setting up an API with a unique routing structure. I've intentionally kept the API as simple as possible to explain my problem clearly. If this repo is not the correct place to ask this question, please redirect me to the proper location. I've put together a github sample, which I've been working in to try and get this working.
The API I'm trying to achieve is the follow:
Note that the
Contexts({contextId})
segment isn't related to the Application Entity, but rather a logic step in the API to retrieve a group of Applications, segregated by context.I've tried doing this in two ways:
UseMvc
. This allows me to specify the two routePrefixes, for api/v1 and api/v1/Contexts({contextId}). The problem with this is that a call toMapVersionedODataRoutes
, configures routes for all of the controllers. Thus by making two calls toMapVersionedODataRoutes
, I end up withMapVersionedODataRoutes
, but somehow use Attributes to specify theContext({contextId})
prefix for the Controller(s) that need it. This option seemed to be the correct approach, but I hit a snag with the EDM specifications, when added theContexts({contextId})
prefix. I tried using theODataRoutePrefix
attribute on the controller, like[ODataRoutePrefix("Contexts({contextId})/Applications")]
, but this produces at ODataUnrecognizedPathException for the segment Contexts, because I haven't registered the Contexts EntitySet (which I don't want to do, because it doesn't represent an Entity).Any advice to help me achieve this design would be much appreciated!
The text was updated successfully, but these errors were encountered: