Welcome to part 1 of a new series where I will dissect the internals of the MVC source code. I am hoping to learn and demonstrate how it works deep (sometimes very deep) under the hood. In this series I will be studying the guts of MVC. If you’re squeamish, you might want to look away! Personally, I get a lot of value from reading, debugging, reading again, scratching my head, debugging again and eventually understanding (or at least thinking I understand) the source code of ASP.NET MVC. By learning how the framework behaves I can better utilise it and understand why things are happening within my own applications.
As I progress through the series, I will be making my best interpretation of what the code is doing based on what I read and observe. I can’t promise to understand and interpret everything 100% correctly but I will do my best! As I’ll be working my way through it in small chunks, I may learn new things that put earlier posts into more context. If that happens, I’ll try to update those earlier posts. It’s fairly hard to succinctly and coherently explain code in writing. I’ll try to keep the explanation clear and will be dropping in chunks of code from the MVC source to make it a little easier to follow. If it’s still not clear, you can (and in fact I recommend you do) spend time looking at the full code yourself, debugging where necessary. I hope this series will be of interest to some of the ASP.NET community who like me, enjoying understanding how things work. Do let me know your feedback so I know if I’m hitting the right notes.
In this post I’ll be dissecting what AddMvcCore is actually doing for us and focusing on classes such as ApplicationPartManager along the way. To do this I’m using the project.json based rel/1.1.2 source code, loading up the included MVC Sandbox project to begin my debugging. As new releases are made it’s possible that some of these classes and methods will change, especially the internal ones. Always refer to GitHub for the latest source! I’ve updated the MvcSandbox ConfigureServices as follows…
AddMvcCore is an extension method on the IServiceCollection. Using extension methods is the common pattern for registering groups of related services into the services collection. AddMvcCore is one of two extension methods which can be used to register the MVC services when building MVC application. AddMvcCore adds a more limited subset of the MVC services than the more commonly used AddMvc method. It’s useful when your application is not taking advantage of all the MVC features. In some simple applications this might be enough. For example, when building REST APIs, where I don’t need the Razor components, I often start with AddMvcCore. You can always add some extra services manually after AddMvcCore, or if you find yourself needing it, move to the more complete AddMvc method instead.
AddMvcCore looks like this:
One of the first things AddMvcCore does is to get an ApplicationPartManager via a static method GetApplicationPartManager which takes in the existing IServiceCollection. This method looks like this:
It first checks in the currently registered services to see if an ApplicationPartManager is already registered. By default this would not be the case. Although unlikely, it is possible that you or a prior service might have registered one before AddMvcCore is called. If an ApplicationPartManager doesn’t already exist, a new one is constructed.
Next the code will try to populate the ApplicationParts property on the ApplicationPartManager. To do this it first asks for an IHostingEnvironment from the services collection. If it can access a matching instance for IHostingEnvironment it uses it to get the application/assembly name. This then gets passed into the static DefaultAssemblyPartDiscoveryProvider.DiscoverAssemblyParts method which will return an IEnumerable<ApplicationPart>.
DiscoverAssemblyParts will first get the Assembly object and a DependencyContext for the supplied assembly name. In my debugging case this was “MvcSandbox”. It then passes these values into GetCandidateAssemblies which calls down into GetCandidateLibraries. Those methods look like this:
The comment above GetCandidateLibraries (stripped in code above) explains what it’s going to do
“Returns a list of libraries that references the assemblies in <see cref=”ReferenceAssemblies”/>. By default it returns all assemblies that reference any of the primary MVC assemblies while ignoring MVC assemblies.”
So to digest that, what we can expect is a list of any assemblies from our solution which reference any of the MVC assemblies.
ReferenceAssemblies is a static HashSet of strings defined at the top of the DefaultAssemblyPartDiscoveryProvider class. It contains the full assembly names for the 13 default MVC related assemblies.
GetCandidateLibraries uses a CandidateResolver class to locate and return the candidates. The CandidateResolver is constructed with the List of RuntimeLibrary objects and the HashSet containing the ReferenceAssemblies. Each of the RuntimeLibrary objects is iterated over and added to a dictionary. During the loop, a check is made to ensure that each depedency name is unique. If not, an exception will be thrown.
Each dependency (RuntimeLibrary) is each stored in the dictionary as a new Dependency object. This object includes a DependencyClassification property that is used to eventually filter out the required libraries (candidates). There are four DependencyClassifications defined in an enum.
When each Dependency is created, it’s initially marked as Unknown unless it is found in the ReferenceAssemblies HashSet. In that case it is instead marked as MvcReference.
The CandidateResolver.GetCandidates method is called which then begins traversing the dictionary of Dependencies. This method combined with ComputeClassification walks the dependency tree. For each Dependency it checks through the child dependencies, breaking out of the loop if any of them is already marked as a Candidate or MvcReference. At this point it will also mark the parent Dependency as a Candidate type. When this process is complete an IEnumerable<RuntimeLibrary> is returned, containing any identified candidates. In my case, using the sample, it is just the MvcSandbox library which is marked as a Candidate.
DiscoverAssemblyParts converts any returned candidates into a new AssemblyPart. This object simply wraps the Assembly and mainly exposes some of the Assembly properties such as it’s name and types. I’ll probably explore that class in a separate post.
Finally, within GetApplicationPartManager the AssemblyParts are added to the ApplicationPartManager which is then returned. As a reminder, here is the relevant snippet of code that does that…
The returned instance of the ApplicationPartManager is added to the services collection as a Singleton back inside the AddMvcCore extension method.
Following so far? 🙂
The next thing AddMvcCore does is call a static ConfigureDefaultFeatureProviders method which takes in the ApplicationPartManager. This will add a new ControllerFeatureProvider to the FeatureProviders list if one is not already included.
The ControllerFeatureProvider will be used to discover controllers from the list of ApplicationPart instances. I’ll look at the ControllerFeatureProvider in a future post. For now, we’ll continue looking at the final steps inside AddMvcCore now that the ApplicationPartManager has been updated.
First a private ConfigureDefaultServices method is called, which calls the AddRouting extension method from Microsoft.AspNetCore.Routing. I won’t cover this here, but it’ll add all of the necessary services and setup to enable the routing functionality.
AddMvcCore then calls another private method – AddMvcCoreServices – This method registers each of the required core services for MVC to function. This includes things like the options framework, action discovery, action selection, controller factories, action invokers, model binding and validation.
Finally, AddMvcCore constructs a new MvcCoreBuilder object with the services collection and the ApplicationPartManager as parameters. This class is documented as follows
“Allows fine grained configuration of essential MVC services.”
The MvcCoreBuilder is returned as the result of the AddMvcCore extension method. It exposes the IServiceCollection and ApplicationPartManager as properties. The McCoreBuilder and it’s extension methods are referenced in a few places to allow extra configuration to occur after the initial AddMvcCore call. In fact, the more complete AddMvc extension method calls AddMvcCore first and then uses the MvcCoreBuilder to perform the extra service configuration.
Explaining the flow above in a way that is reasonably clear is not easy. So, let me wrap up by summarising what we’ve witnessed. This is all very low level MVC code that ultimately adds MVCs required services to the IServiceCollection. Along the way it creates the ApplicationPartManager, which as we’ll look at later is used to start establishing the internal ApplicationModel that MVC uses. We haven’t seen very much direct functionality, but as I follow the startup flow we will get to the more interesting pieces now that the groundwork has been laid.
That completes my initial deep dive into the flow of AddMvcCore. In total it registers 46 additional items into the IServicesCollection. I’ll continue the journey in a future post where I’ll look at the further work that the AddMvc extension method performs.