We are using a couple of nested applications in an asp.net core app as described on this blog post.
Those nested applications are in a satellite net standard 2.0 library.
Routing Is correctly configured with path segment and assume the following template
https://server/{version}/segment/
With the segment being mapped on the nested application. The problem we are facing with Is that we can call the api with out specifying the version (assume the default configured) and it works.
However if we call the api specifying the version it does not work.
Any suggestion?
Do you have an example of your configuration or, better still, a repro with all the bits? Your nested application is partitioned after the API version. Based on the link you provided, that seems to indicate you have some version-based processing in the middleware. It's kind of hard for me to rationalize without seeing something from the configuration.
Something definitely feels off if you're saying you can make a call without the API version. When you use the URL segment method, it's impossible to match a route without an API version. The only way this is sort of possible if you double-map a route template that doesn't require an API version and you configure API versioning to assume a default version for this path. Again - it's hard to say without seeing some part of the configuration.
I have made some progress on this and created a reproducible scenario.
First of all however let me give you a bit of context: I am upgrading an asp.net 4.6 web api application to asp.net core. this application features multiple api which needs to have different execution pipelines (in terms of MediatR pipelines I mean) and those pipelines should be completely independent from each other.
In the 4.6.1 version I did not need to double map the endpoints to have the "assume default version" working. This confused me at the beginning.
The reproducible sample can be found here.
The solution contains two projects:
The Api Host project contains two controllers:
The NestedApp project contains one controller:
With this configuration I am able to
v1/routes endpoint by specifying versionv1/values or v2/values endpoint by specifying versionv1/nested either with or without version specifiedThe problem now arise on the swagger specification generated. As you can see the documents generated misses the v1/values and v2/values endpoints.
Any suggestion?
Looks like you were pretty close. I don't _think_ you need the additional Swagger setup in the nested application. Since you are versioning by URL segment, you probably want to enable URL substitution for the API Explorer.
```c#
.AddVersionedApiExplorer(o => { o.GroupNameFormat = "'v'VVV"; o.SubstituteApiVersionInUrl = true; })
This will replace the route parameter `{version}` with the formatted API version and will also remove the parameter from user input. It's definitely not required, but most people that version this way prefer it.
It looks like the crux of the issues are just the wrong attribute in the wrong place. I remember that throwing me off a bit when I first switched from Web API to Core. In particular, here's a few common gotchas:
* There's no **RoutePrefixAttribute**; it's supplanted by **Route**
* A route template can contain the tokens `[controller]` and `[action]` for substitution by ASP.NET
* Attribute routing requires the HTTP method constraint; no more _magic_ mapping by method name
* The HTTP method constraint can also have any remaining portion of the route template
To see this in action, here's how you want the **ValuesController** defined:
```c#
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class ValuesController : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(string[]), 200)]
[ProducesResponseType(typeof(string[]), 404)]
[Produces("application/json")]
[Consumes("application/json")]
public IActionResult GetValues() => Ok(new string[] { "value 1", "value 2" });
}
This will now correctly show the following in Swagger:
valuesv1/valuesv2/valuesI should point out that there is no such thing as _no API version_. Although you have a route template without the version URL segment in it, you have configured AssumeDefaultVersionWhenUnspecified = true. A client that hits this route is considered to not have specified an API version and the assumed API version will be options.DefaultApiVersion, which you've configured to be 1.0 (also the default). One side effect of this in Swagger is that you will see the route listed twice. If you only want one of them, you'll need to filter that yourself either through customizing the Swagger document or adding an IApiDescriptionProvider that filters out one version of the action. A version-neutral API is different. It accepts any and all API versions, including none at all. Such actions will appear in all Swagger documents.
Using route names can be tricky because the mapping is not API version-aware. Every named route must be distinct. If you truly need this capability, then I suggest appending your version name to the route name. Something like "GetValuesV1". If you need to get the currently requested API version, you can get via Request.HttpContext.GetRequestedApiVersion(). Starting in v3.0+, you'll also be able to define an action parameter that will have the API version provided via model binding.
For your convenience, I've attached a working copy for you repo. That should get you going. I hope that helps. Let me know if you have more questions.
Great!
thanks a lot for your great support!
I have a question about this. If the ApiVersion(s) are 1.0 and 2.0 in this case, why wouldn't the routes be
Is there a way to make it this way?
Ah … fair question. They actually _are_. The _minor_ component is optional. If the _major_ part is specified, but the _minor_ part is not, then the _minor_ part is assumed to be 0 for the purposes of equality. ApiVersion.MinorVersion, however, will still report null and will omit the 0 in ApiVersion.ToString() by default. This maintains the fidelity of the original text value that was parsed. The results of ApiVersion.Equals or ApiVersion.GetHashCode() will treat the _minor_ part as if it were 0 regardless of whether it's null. This means that 1 and 1.0 are semantically equivalent.
This means the following routes are all possible with the listed configuration:
This arguably _special_ case only applies to the _minor_ part and only when it's 0. Unless my BNF notation is wrong, this should also be reflected in the version format wiki topic. There is not currently a way to _force_ the client to specify the _minor_ component when it's zero. You can, however, force it to be emitted when you call ApiVersion.ToString() with the appropriate format code.
I hope that helps and clarifies a few things.
I have updated the sample code with new versions of asp net core released in the past days as well as the new v3 version of the api versioning library. the code is available here.
Now those nested applications are completely isolated between them and I am able to define different implementations of the same interface between any app. I am mapping thise nested apps into two different paths in the main application startup
app.IsolatedMap<NestedStartup1>( "/n1" );
app.IsolatedMap<NestedStartup2>( "/n2" );
and I can call the following endpoints
https://localhost:5001/vXXX/routes (Main application, ApiVersionNeutral)
https://localhost:5001/n1/v1/values (Nested application 1, v1 version)
https://localhost:5001/n1/v2/values (Nested application 1, v2 version)
https://localhost:5001/n2/v3/values (Nested application 2, v2 version)
This is working, generally speaking, very good. However it introduces some drawbacks which I am describing here to see if they can be resolved:
v{version:apiVersion}/values I am obliged to add a prefix to that api (e.g. n1/v{version:apiVersion}/values) because of the mapping path not being versionable. It would be great to be able to use something like thisapp.IsolatedMap<NestedStartup1>( "/v{version:apiVersion}/values" );
https://localhost:5001/n1/v1/values. you will end up with a 500 Internal server error sayingMicrosoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
NestedApp1.Controllers.ValuesV1Controller.GetNestedApp1V1Values (NestedApp1)
NestedApp1.Controllers.ValuesV2Controller.GetNestedApp1V2Values (NestedApp1)
Do you have any suggestion on how to get rid of these issues?
I'm not really sure about this one. I suspect it's related to, or otherwise a limitation of, the routing system. It seems reasonable that isolation must be under a subpath to disambiguate from the main entry point. The ASP.NET team (e.g. repo) can probably provide a better answer.
The answer here is pretty simply - it's not supported yet. You've updated to ASP.NET Core 2.2 with the current feature set which uses _Endpoint Routing_. This is an entirely new routing system and it's supported by API versioning - yet. I'm in the process of getting it working now. I haven't promised anything before the end of January, but I think I'll have something ready in the next week or two. I wanted to get 3.0 first because it supports all the way back to ASP.NET Core 1.0. The upcoming 3.1 release will force everyone to use 2.2 or above.
You can use the other 2.2 features as long as you disable _Endpoint Routing_. That will use the _legacy_ routing system that is supported by API versioning.
c#
services.AddMvc( options => options.EnableEndpointRouting = false );
The API Explorer sees everything so I don't think there's a way to _filter_ anything directly there. You can, however, filter the results as well as slice and dice them however you want.
I hope that helps.
Regarding point 2, I have updated the packages and now I am not experiencing anymore the AmbiguousMatchException. However I get 404 for every route in a nested application.
If you've moved all the way up to 3.1+, this _might_ be happening because you have not decorated your controllers with [ApiController]. Starting in 3.1, the options.UseApiBehavior is now true. This addresses a long-standing request to separate API controllers from non-API controllers. There wasn't a very _clear_ way to do this in the past. Naturally, this is in the release notes.
If this applies to you, you can apply [ApiController] to your controllers or revert the configuration option via options.UseApiBehavior = false. You can also now use a custom IApiControllerSpecification as described in the wiki to identify an API controller. This _might_ require fewer code changes depending on your application.
One of these should get you back in business. I hope that helps.
I am on 3.1.1. All the controllers were already decorated with [ApiController].
However I have also added the UseApiBehavior explicitly
services.AddApiVersioning( o => {
o.AssumeDefaultVersionWhenUnspecified = true;
o.ReportApiVersions = true;
o.DefaultApiVersion = new ApiVersion( 3, 0 );
o.UseApiBehavior = true;
} );
and got it working 😃
I hope to solve also point 1 and 3 of my list. Thanks a lot for your help and effort!
Most helpful comment
Ah … fair question. They actually _are_. The _minor_ component is optional. If the _major_ part is specified, but the _minor_ part is not, then the _minor_ part is assumed to be
0for the purposes of equality.ApiVersion.MinorVersion, however, will still reportnulland will omit the0inApiVersion.ToString()by default. This maintains the fidelity of the original text value that was parsed. The results ofApiVersion.EqualsorApiVersion.GetHashCode()will treat the _minor_ part as if it were0regardless of whether it'snull. This means that1and1.0are semantically equivalent.This means the following routes are all possible with the listed configuration:
This arguably _special_ case only applies to the _minor_ part and only when it's
0. Unless my BNF notation is wrong, this should also be reflected in the version format wiki topic. There is not currently a way to _force_ the client to specify the _minor_ component when it's zero. You can, however, force it to be emitted when you callApiVersion.ToString()with the appropriate format code.I hope that helps and clarifies a few things.