If you watched the ASP.NET Community Standup this week, you might have heard Jon Galloway suggesting that these posts be consumed along with a good cup of coffee or tea. So before I begin, I’ll give you time to get yourself a drink!…
Right; all set? In part 1, I looked at what happens inside the AddMvcCore extension method. In this post I want to look at the AddMvc extension method. As in part 1 I am using the rel/1.1.2 tagged code for ASP.NET Core MVC. Things can and will likely change in the code base in the future. Therefore, consider this general guidance which should only be considered current if you are using the same version of the MVC packages. My starting point again is the MvcSandbox project. My ConfigureServices method in Startup.cs looks like this:
This makes a single call the the AddMvc extension method, which looks like this:
This method first calls the AddMvcCore extension method that I looked at in part 1. Therefore all of the same setup and service registration occurs, including the creation of an ApplicationPartManager. The return type from AddMvcCore is an MvcCoreBuilder which AddMvc holds in a variable. As I noted in part 1, this provides access to the the IServiceCollection and ApplicationPartManager generated by AddMvcCore.
AddMvc calls an extension called AddApiExplorer on the IMvcCoreBuilder. This simply adds two service registrations – ApiDescriptionGroupCollectionProvider and DefaultApiDescriptionProvider – to the builder’s IServiceCollection.
Next it calls the AddAuthorization extension on IMvcCoreBuilder. The relevant methods are:
First it calls down to an extension, AddAuthorization in the Microsoft.AspNetCore.Authorization assembly. I won’t go in depth about that in this post, but this adds the core services to enable authorization. After this, AuthorizationApplicationModelProvider is added to the ServicesCollection.
Next, back in AddMvc it uses a private static method AddDefaultFrameworkParts which adds TagHelpers and Razor AssemblyParts.
First this method looks up the Assembly containing the InputTagHelper class which is Microsoft.AspNetCore.Mvc.TagHelpers. It then checks if the ApplicationPartManager.ApplicationParts list already contains a matching assembly. If not, it adds it. This is then repeated for Razor, using the assembly containing UrlResolutionTagHelper which is Microsoft.AspNetCore.Mvc.Razor. This also gets added to ApplicationParts if it is not already there.
Back in AddMvc again, a number of items are added to the ServiceCollection via extension methods. AddMvc calls AddFormatterMappings which registers the FormatFilter class.
Next it calls AddViews which does a few things. First it adds services for DataAnnotations using the AddDataAnnotations extension. Then it adds the ViewComponentFeatureProvider to the ApplicationPartManager.FeatureProviders. Finally it registers a number of view based services, mostly as singletons. I won’t show the whole class here as it’s quite big. The key parts are:
Next, AddMvc calls the AddRazorViewEngine extension method which looks like this:
This calls AddViews again which made me a little curious. It seems to be redundant to call AddViews prior to AddRazorViewEngine, when we can see that AddRazorViewEngine will do that for us and register those services anyway. It’s a safe operation, since all of these extension methods use the TryAdd… style methods. Those prevent duplicate registrations of implementations of services. Despite the safety of this approach, it was still strange to see this. To satisfy my curiosity I opened an issue on the Mvc GitHub repo, to check if this was an issue or actually by design. I got a very quick response from Ryan Nowak at Microsoft confirming that it was a design decision to make the dependencies a little more explicit and easy to see. Thanks Ryan!
AddRazorViewEngine adds three more feature providers to the ApplicationPartManager.FeatureProviders – TagHelperFeatureProvider, MetadataReferenceFeatureProvider and ViewsFeatureProvider. It concludes by registering the services needed for razor views to function.
AddMvc then uses another extension method to add services for the Cache Tag Helper feature. This is a very simple extension method which just registers 5 required services. Then back in AddMvc, AddDataAnnotations is called again. AddViews had already added this previously. This is for the same design decision that I discussed before.
AddMvc then calls the AddJsonFormatters extension method which adds a couple of items to the ServicesCollection.
The final extension method that gets called is AddCors which uses the extension method in the Microsoft.AspNetCore.Cors to add Cors related services.
With the service registrations completed, AddMvc creates a new MvcBuilder, passing in the current ServicesCollection and the ApplicationPartManager which are stored into its properties. Much like the MvcCoreBuilder we looked at in the first post and which we started with at the beginning of AddMvc, the MvcBuilder is described as allowing fine grained configurations of MVC services.
At the end of AddMvc I had 148 registered services in the services collection. 86 additional services are registered above and beyond those that AddMvcCore has added. The ApplicationPartManager had 3 ApplicationParts and 5 FeatureProviders stored in its lists.
That concludes my dissection of AddMvc. A little less exciting I’m afraid than the AddMvcCore method which laid more groundwork by creating the ApplicationPartManager. Mostly we call extensions to register extra services. As you can see, many of those services are more specific to web applications which need to work with views. For API only applications, it’s possible you won’t need most of these. In the API projects I’ve been working with, we start with AddMvcCore and simply add in the few extra items we need using the builder extension methods. Here is an example of what that looks like in practice:
In the next post I will look at what happens when we call UseMvc in the Startup.cs Configure method. That get’s a little more exciting!
Other posts in this series
Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered in this series.