Aspnet-api-versioning: Using AddVersionedApiExplorer() with AddMvc()

Created on 23 Apr 2018  路  11Comments  路  Source: microsoft/aspnet-api-versioning

It seems the AddVersionedApiExplorer() call is always used together with AddMvcCore(). It does not seem to be available with the standard AddMvc() builder. Is this correct? Do I have to convert my application to use the AddMvcCore() builder instead if I want the versioned version?

asp.net core question

Most helpful comment

Typically, the only options you're ever interested in for the API Explorer is:

  • GroupNameFormat
  • SubstitutionFormat (URL segment style only)
  • SubstituteApiVersionInUrl (URL segment style only)
  • DefaultApiVersionParameterDescription

GroupNameFormat is generally the only thing you need to change from the out-of-the-box experience. If you're using the URL segment style, then most prefer SubstituteApiVersionInUrl = true as well. Everything else is choice, but is usually fine _as is_.

I see the confusion. The API Explorer model in ASP.NET Core is quite different (and more robust) than it was in Web API. The default API Explorer actual discovers everything, but there is no organization to it. The API Versioning _provider_ runs at the end of the API Explorer pipeline and collates all the APIs into appropriate buckets by API version. Technically, AddVersionedApiExplorer() doesn't depend on AddApiExplorer(), but it also doesn't make sense without it so it probably should call AddApiExplorer() on your behalf. I'll consider adding that to the 3.0 work in progress.

Your suggestion is valid, but - unfortunately - I find a lot of people mixing and matching API versioning with UIs. I don't know why anyone needs API versioning if you own both the UI and API in the same application, but I see it quite often. For that reason, the current example should probably stay _as is_. It's useful for those mixing in with the full MVC stack and I use the samples quite often as the basis of for troubleshooting.

That said, I'm not at all opposed to an entirely new sample. Perhaps something called _ApiOnlySample_ that has the setup you are describing. The controllers will need to change their base type to ControllerBase instead of Controller as well. There may be other changes required too, but that's what I can think of off the top of my head.

All 11 comments

I think I see the confusion. AddMvcCore only brings in the _core_ parts of the stack, which is more suited to a RESTful service stack. AddMvc brings in much more; specifically, the required UI services. AddMvc ultimately calls AddMvcCore, which you can see for yourself.

API versioning does not require the full MVC stack. It only requires the _core_ parts. You have to _love_ the overloaded use of the word. I hope that helps clarify things.

Thanks for your explanation. I think I understand fairly well the difference between the two ways of setting up MVC (I have used both in different projects).

Right now I stand with a project using UseMvc(). My question is, do I have to "convert" my project to use UseMvcCore() instead, to be able to then call AddVersionedApiExplorer()? It might not seem like a big change, but it's still a bit out-of-scope for the feature request "Add a V2 version of a certain controller".

Succinctly, I'll just say - no. You don't need to _convert_ anything.

The API Explorer for API Versioning does not have any UseXXX configuration; it adds services via AddMvcCore() and that's it. Since API versioning is lower in the chain than full MVC, it will always be part of the IMvcCoreBuilder. UseMvc() configures the routing infrastructure. Best as I can tell, there is no UseMvcCore() API. To configure routing, you always call UseMvc() regardless of whether you are only using AddMvcCore() or using AddMvc().

I should also point out that you only need the API Explorer if you're wiring up Swagger or something else that requires enumerating the defines APIs and their versions. You don't need the API Explorer to simply _"Add a V2 version of a certain controller"_. I hope that helps.

You are of course correct. I'm sorry, my previous post was incorrect. I meant AddMvc(), not UseMvc(). I will reformulate it here with correct terminology:

Right now I stand with a project using AddMvc(). My question is, do I have to "convert" my project to use AddMvcCore() instead, to be able to then call AddVersionedApiExplorer()? This method call is not available on the builder returned from AddMvc(). And even if it's added automatically, I still need to do some configuration.

Thanks for your patience.

No problem. You can, and will ultimately need, to call both. AddMvc() calls AddMvcCore() internally. There's nothing wrong with calling them both, even multiple times.

Since you're using the full MVC stack, you should expect your setup to update to:

c# public void ConfigureServices( IServiceCollection services ) { // the api explorer doesn't require the UI and you might not be using it, // which is why it's on AddMvcCore services.AddMvcCore().AddVersionedApiExplorer(); services.AddMvc(); services.AddApiVersioning();

The Swagger Sample shows a more complete example. I hope that helps and clears things up. Don't hesitate to keep asking questions.

Great, thanks! Excellent support! 馃専

I hope you don't mind if I jump in on this closed question. I ran into the same situation where I was using AddMvc() for my API project and noticed I can only use AddVersionedApiExplorer() on AddMvcCore().

I thought hey, for a pure API project, it would make sense to not have Razor pages and all wired up, so that would be a logical change anyhow - and so I switched from AddMvc() to AddMvcCore(). But while the API was still working, Swagger UI did not. It showed up, but always had empty definitions.

I finally noticed in the Swagger sample project that _not only_ AddMvcCore() is called, but afterwards also AddMvc(). @commonsensesoftware also mentioned that we will and _have_ to call both. That seems a little odd and counter-intuitive, but ultimately I want to understand why. What is it that AddMvc() adds that is needed by the Swagger UI to actually show any definitions?

Because I tried to manually chain everything that is done inside AddMvc() manually to my AddMvCore() call - but it still does not work:

            services.AddMvcCore()
            .AddAuthorization()
            .AddFormatterMappings()
            .AddViews()
            .AddRazorViewEngine()
            .AddCacheTagHelper()
            .AddVersionedApiExplorer(o =>
            {
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(2, 0);
            })
            .AddDataAnnotations()
            .AddJsonFormatters()
            .AddCors()
            });

I even added everything inside AddDefaultFrameworkParts to my code but still no luck.

So I'd really be interested on what exactly is happening behind the scenes when calling both, AddMvcCore() and AddMvc(), that makes Swagger UI suddenly work as expected (as of: showing actual API definitions).

It really bugs me ... so if anyone can shed some light on this, that would be great.

There is likely a combination of things happening. Registering services is more _tolerant_ than registering routes when it comes to order. The key thing for services is any service that might be replaced. It would appear that you've looked at the source code so you've seen that AddMvc() ultimately calls AddMvcCore() on your behalf.

That said, this could a case of inversed registration. First, AddVersionedApiExplorer() does not call AddApiVersioning(). Maybe it should? Second, the options you are specifying for AddVersionedApiExplorer() are not really meant to be explicitly set by you and have completely different meaning than when you use them in AddApiVersioning(). These option values are automatically copied over from the ApiVersioningOptions. Their use is purely related to API exploration in this context and have zero effect on routing, etc. The only reason they are visible at all is for extensibility purposes. The missing call to AddApiVersioning() _could_ be what's missing here.

Another issue could be the route configuration, which you didn't show (e.g. the UseXXX methods). The order of these do matter and Swashbuckle (interchangeably called _Swagger_) does register it's own middleware to hook up the Swagger-related routes.

Sorry I don't have anything more specific, but that should put you on the path.

Thank you, that was more than specific enough. I called AddApiVersioning() later on, just hadn't it included here. For the options to AddVersionedApiExplorer(), i removed them, but it did not make a difference. I will keep them removed, thanks for your advice! But having GroupNameFormat is ok there, right? As it's also there in the sample project.

But based on the information you gave me, and the pure fact that you expect it to work without AddMvc() put me indeed on the right path: It's AddApiExplorer that needs to be called! It's called in AddMvc() and I didn't include it in my setup on AddMvcCore() as I jumped to the conclusion that AddVersionedApiExplorer() replaces AddApiExplorer() completely, but that seems not to be the case.

So what I have now is:

           services.AddMvcCore()
            .AddJsonFormatters()
            .AddDataAnnotations()
            .AddVersionedApiExplorer(options =>
            {
                options.GroupNameFormat = "'v'VVV";
            })
            .AddApiExplorer()

And with that, I do not need AddMvc() anymore.

So thank you very much - that's a relief :)

Just one question: I think the sample project should do this change too - remove AddMvc() and instead just call AddApiExplorer() in addition to AddVersionedApiExplorer(). It would make the sample more clear on what's needed to be done and my whole adventure started by overlooking the additional AddMvc() call in the sample. If you agree, I'd also do the pull request for this change if you like.

Typically, the only options you're ever interested in for the API Explorer is:

  • GroupNameFormat
  • SubstitutionFormat (URL segment style only)
  • SubstituteApiVersionInUrl (URL segment style only)
  • DefaultApiVersionParameterDescription

GroupNameFormat is generally the only thing you need to change from the out-of-the-box experience. If you're using the URL segment style, then most prefer SubstituteApiVersionInUrl = true as well. Everything else is choice, but is usually fine _as is_.

I see the confusion. The API Explorer model in ASP.NET Core is quite different (and more robust) than it was in Web API. The default API Explorer actual discovers everything, but there is no organization to it. The API Versioning _provider_ runs at the end of the API Explorer pipeline and collates all the APIs into appropriate buckets by API version. Technically, AddVersionedApiExplorer() doesn't depend on AddApiExplorer(), but it also doesn't make sense without it so it probably should call AddApiExplorer() on your behalf. I'll consider adding that to the 3.0 work in progress.

Your suggestion is valid, but - unfortunately - I find a lot of people mixing and matching API versioning with UIs. I don't know why anyone needs API versioning if you own both the UI and API in the same application, but I see it quite often. For that reason, the current example should probably stay _as is_. It's useful for those mixing in with the full MVC stack and I use the samples quite often as the basis of for troubleshooting.

That said, I'm not at all opposed to an entirely new sample. Perhaps something called _ApiOnlySample_ that has the setup you are describing. The controllers will need to change their base type to ControllerBase instead of Controller as well. There may be other changes required too, but that's what I can think of off the top of my head.

Thank you again for your extensive response, this was very helpful to me.

I agree that, considering people mixing their API projects with UIs, the current sample is the best option as it works in every scenario.

If you call AddApiExplorer() on our behalf in the future, which I would really love to see, I also think there is no need for an additional sample project for an _ApiOnlySample_ as, should someone like me decide to leave AddMvc() out for a pure API project, it will still work as expected.

Was this page helpful?
0 / 5 - 0 ratings