ASP.NET Core Anatomy (Part 4) – Invoking the MVC Middleware Dissecting and understanding the internals of ASP.NET Core

In the first three parts of this series I looked at what happens when we call AddMvcCore, AddMvc and UseMvc as part of the application startup. Once the MVC services and middleware have been registered in our ASP.NET Core application, MVC is now available to handle HTTP requests.

In this post I want to cover the initial steps that happen when a request flows into the MVC middleware. This is quite a complex area to split up and write about. I’ve broken it out into what I consider a reasonable flow through the code, ignoring for now certain branches of behaviour and details to constrain this post to something that I hope is digestible in one sitting. Where I gloss over deeper implementation details I will highlight these cases and will write about those elements in future posts.

As with my earlier blog posts, I’ve used the original project.json version (1.1.2) of the MVC source code, since I’ve still not found a reliable way to debug through the source of MVC while including source from the other components such as Routing.

So, let’s begin and look at how MVC matches a request through to an available route and finally to an action which can handle that request. As a quick refresher, the middleware pipeline, configured in Startup.cs file of an ASP.NET Core application defines the flow of the request handling. Each middleware component will be invoked in order, until one of the middleware components determines it can provide a suitable response.

I’m using the MvcSandbox, included with the MVC solution to work with MVC while I write this series. The Configure method looks like this:

Assuming none of the earlier middleware handles the request, we reach the MVC pipeline and middleware, included in our pipeline with the UseMvc call. Once the request reaches the MVC pipeline, the middleware we hit is the RouterMiddleware. Its Invoke method looks like this:

The first thing that Invoke does is construct a new RouteContext passing in the current HttpContext object into the constructor.

The HttpContext is used to set a parameter on the RouteContext class and then a new RouteData object is instantiated and set.

Back inside Invoke; the injected IRouter, in this case the RouteCollection that was created during the UseMvc setup, is added to a List of IRouter objects on the RouteContext.RouteData object. Something worth highlighting is that the RouteData object uses a late initialisation pattern for its collections, only allocating them if they are called. This pattern shows the consideration that has to be made for performance inside a large framework such as ASP.NET Core.

For example, here is how the Routers property is defined:

The first time this property is accessed, a new List will be allocated and stored in the backing field.

Back in Invoke; RouteAsync is called on the RouteCollection.

The first thing the RouteAsync implementation does on the RouteCollection is to create a RouteDataSnapshot. As the comment indicates, allocating a RouteData object is not something that should happen for every route being processed. To avoid that, the snapshot of the RouteData object is created once and this allows it to be reset for each iteration. This is another example of where the ASP.NET Core team have coded with performance in mind.

The snapshot is achieved by calling PushState on the RouteData class.

The first thing it does is create a List<IRouter>. To be as performant as possible it only allocates a list if the private field containing the RouteData routers has at least one IRouter in it. If so, a new list is created, with the correct size to avoid internal Array.CopyTo calls occurring to resize the Lists underlying array. Essentially this method now has a copied instance of the RouteData’s internal IRouter list.

Next a RouteDataSnapshot object is created. RouteDataSnapshot is defined as a struct. Its constructor signature looks like this:

The RouteCollection calls PushState with null values for all of the parameters. In cases where the PushState method be called with a non-null IRoute parameter then it gets added to the Routers list. Values and DataTokens are processed in the same way. If any are included on the PushState parameters, the appropriate items in the Values and DataTokens properties on RouteData are updated.

Finally, the snapshot is returned to RouteAsync inside the RouteCollection.

Next a for loop is started which will loop up to the Count property value. Count simply exposes the Count of the Routes (List<IRouter>) on the RouteCollection.

Inside the loop it first gets a route using the value of the loop. This calls to

So this simply returns an IRouter from the List for the required index. In the MvcSandbox example, the first IRouter in the list at index 0 is the AttributeRoute.

Inside a Try/Finally block, the RouteAsync method is called on the IRouter (the AttributeRoute). Ultimately what’s happening here is we’re hoping to find a suitable Handler (a RequestDelegate) that matches the route data.

We’ll look into more detail about what happens inside the AttributeRoute.RouteAsync method in a future post as there’s a lot happening there and at the moment we’ve not defined any AttributeRoutes within the MvcSandbox. In our case, because there are no AttributeRoutes defined, the Handler value remains null.

Inside the finally block, when the Handler is null, the Restore method is called on the RouteDataSnapshot. This method will restore the RouteData object back to its state when the snapshot was created. Because the RouteAsync method may have modified RouteData during processing, this ensures that we are back to an initial state for the object.

Within the MvcSandbox example, the second IRouter in the list is the named “default” route which is an instance of Route. This class does not override the RouteAsync method on the base, so in this case, the code inside the RouteBase abstract class is called.

First it calls a private method, EnsureMatcher which looks like this:

This method will instantiate a new TemplateMatcher, passing in two parameters. Again, this appears to be an allocation optimisation to ensure that this object is only created at the point it is needed, after the properties which are passed to its constructor are available.

In case you’re wondering where those properties are set; that happens inside the constructor of the RouteBase class. This constructor was called when the default Route was first created as a result of calling MapRoute inside the UseMvc extension, called by the Configure method of the MvcSandbox Startup class.

Inside the RouteBase.RouteAsync method, the next call is to EnsureLoggers which looks like this:

This method gets the ILoggerFactory instance from the RequestServices and uses it to setup two ILoggers. The first is for the RouteBase class itself and the second will be used by the RouteConstraintMatcher.

Next it stores a local variable holding the path of the request being made, retrieved from the HttpContext.

Next, TryMatch on the TemplateMatcher is called, passing in the path of the request, along with any route data values. We’ll dive into the internals of TemplateMatcher in another post. For now, assume that TryMatch returns true, which is the case in our example. In cases where a match is not made a TaskCache.CompletedTask is returned. This is just a Task that is already set as completed.

Next, if there are any DataTokens (RouteValueDictionary objects) set, then the values on the context.RouteData.DataTokens are updated as necessary. As the comment suggests, this is only done if there are actually any values to be updated. The DataTokens property on RouteData is only created when its first called (lazily instantiated). Therefore, calling it when there are no values to update would risk allocating a new RouteValueDictionary when it may not be needed.

In our demo using MvcSandbox, there are no DataTokens so MergeValues is not called. However, for completeness, here is its code:

When called it loops any values in the RouteValueDictionary from the DataTokens parameter on the RouteBase class and updates the destination value for the matching key on the context.RouteData.DataTokens property.

Next, back in RouteAsync, RouteConstraintMatcher.Match is called. This static method loops over any IRouteContaints that are passed in and determines if they have all been met. Route constraints allow routes to be configured with additional matching rules. For example, a route parameter can be constrained to only match for integer values. In our case there are no constraints on our demo route so it returns true. We’ll look at a URL with constraints in another post.

A logger entry is made via an extension method on ILogger called MatchedRoute. This is an interesting pattern that makes it simple to reuse more complex formatting of logging messages for specific needs.

This class looks like this:

An action delegate is defined when the TreeRouterLoggerExtensions class is first constructed that defines how the logged message will be formatted.

When the MatchedRoute extension method is called, the route name and template string are passed as parameters. They are then passed to the _matchedRoute Action. This action creates a debug level log entry using the provided parameters. When debugging inside visual studio you will see this appear in the Output window.

Back inside RouteAsync; the OnRouteMatched method is called. This is defined as an abstract method on the RouteBase, so the implementation comes from the inheriting class. In our case it’s the Route class. Its OnRouteMatched override looks like this:

Its IRouter field called _target is added to the context.RouteData.Routers list. In this case it’s an MvcRouteHandler which is the default handler for MVC.

The RouteAsync method is then called on the MvcRouteHandler. The details of what this does are quite involved so I’ll save those to be the topic of a future post. In summary, MvcRouteHandler.RouteAsync will try to establish a suitable action method that should handle the request. One thing that is important to know for the context of this blog post is that when an action is found, the Handler property on the RouteContext is defined via a lambda expression. We can dive into that code another time but to summarise, a RequestDelegate is a function that accepts a HttpContext and can process a request.

Back inside the Invoke method on the RouterMiddleware, we might have a handler that MVC has determined can handle the requested route. If not then _logger.RequestDidNotMatchRoutes() is called. This is another example of the logger extension style we explored earlier. It will add a debug message, indicating that the route was not matched. In that case, the next RequestDelegate in the ASP.NET middleware pipeline is invoked because MVC has determined it cannot handle the request.

In common configurations for client web/api applications you will not have any middleware defined after UseMvc. In that case as we have reached the end of the pipeline a default 404 not found HTTP status code response is returned by ASP.NET Core.

In cases where we have a handler that can handle the route of the request, we flow through into the Invoke methods else block.

Here a new RoutingFeature is instantiated and added to the Features collection on the HttpContext. Briefly; features are a concept of ASP.NET Core that allows the server to define features that the request supports. This includes flowing of data through the lifetime of a request. Middleware such as the RouterMiddleware can add/modify the features collection and can use this as a mechanism to pass data through the request. In our case, the RouteData from the RouteContext is added as part of the IRoutingFeature definition, so that it can be used by other middleware and request handlers.

The method then invokes the Handler RequestDelegate which will ultimately process the request via the appropriate MVC action. At this point I’ll wrap up this post. What happens next, along with some of the other items I skipped over will form the next parts in this series.

Summary

We’ve seen how MVC is actually invoked as part of the middleware pipeline. Upon invocation, the MVC RouterMiddleware determines if MVC knows how to handle the incoming request path and values. If MVC has an action available that is intended to handle the route and route data on the request, then this handler is used to process the request and provide the response.

Other Posts In This Series

ASP.NET Core Anatomy (Part 3) – UseMvc Dissecting and understanding the internals of ASP.NET Core

In parts one and two of this series I looked at the two main IServiceCollection extensions (AddMvcCore and AddMvc) in ASP.NET Core. These are used to add in the required MVC services when you plan to use the MVC middleware in your application.

The next thing you are required to do to enable MVC in your ASP.NET Core application is to include the UseMvc IApplicationBuilder extension from the Configure method of your Startup class. This method registers the MVC middleware into your application pipeline so that the MVC framework can handle requests and return the appropriate response (usually a view result or some JSON). In this post I will take a look at what happens when the UseMvc method is called during the application startup.

As with the earlier parts of this series, I’m currently using the rel/1.1.2 codebase from the MVC repository for my investigations. I’m still using the slightly older project.json based code as at the moment there don’t seem to be simple ways to debug into multiple ASP.NET Core source code assemblies in VS2017.

UseMvc is an extension on the IApplicationBuilder which takes an Action delegate of IRouteBuilder. The IRouteBuilder will be used to configure the routing for MVC. There is a more basic variant of the UseMvc method which does not required any parameters. This method simply calls down into the main version, passing in an empty delegate like so…

The main UseMvc method looks like this…

Let’s dissect what this method does. First it does a check to see if the IServiceProvider has an MvcMarkerService service registered in it. To do this it uses the ApplicationServices property which exposes the current IServiceProvider. Calling GetService on the service provider will try to locate a registered implementation for the requested Type. The MvcMarkerService is registered when AddMvcCore executes and as a result, if it is not found, it indicates that either AddMvc or AddMvcCore was not called during ConfigureServices. Marker services like this are used in quite a few places to help calling code verify proper registration of fundamental dependencies before they try to execute. The MvcMarkerService is just an empty class with no actual code.

Next, UseMvc requests a MiddlewareFilterBuilder from the ServiceProvider and sets its ApplicationBuilder using the IApplicationBuilder.New() method. The ApplicationBuilder creates and returns a new instance copy of itself when this method is called.

Next, UseMvc initialises a new RouteBuilder, setting the default handler to the registered MvcRouteHandler. At this point, the DI magic really kicks in and a bunch of dependencies start getting instantiated. MvcRouteHandler is requested and because it has some dependencies in its constructor, various other classes get initialised. Each constructor for these classes can also require additional dependencies which then also get created. This is a great example of how the DI system works. While all of the interfaces and concrete implementations are registered in the container during ConfigureServices, the actual objects are only created at the point they are required to be injected into something. By new-ing up the RouteBuilder, this snowball of object creation begins to occur, until all of the required dependencies have been constructed. Some of these are registered as singletons and will then live for the lifetime of the application, being returned whenever they are requested from the ServiceProvider. Others, might be constructed for each request or in some cases, constructed uniquely every time they are required. Understanding the details of how dependency injection works and specifically the ASP.NET Core ServiceProvider is out of scope for this post.

After creating the RouteBuilder, the Action<IRouteBuilder> delegate action is called which takes the RouteBuilder as its only parameter. In the MvcSandbox sample that I’m using to debug and dissect MVC we call the UseMvc method, passing in a delegate function via a lambda. This function maps a single route named “default” as follows.

This calls the MapRoute method which is an extension on the IRouteBuilder. The main MapRoute method looks like this:

This requests an instance of an IInlineConstraintResolver which is resolved by the ServiceProvider to a DefaultInlineConstraintResolver. This class takes an IOptions<RouteOptions> in its constructor.

The Microsoft.Extensions.Options assembly, as part of its work, calls into the MvcCoreRouteOptionsSetup.Configure method which takes a RouteOptions parameter. This adds a new constraint map of type KnownRouteValueConstraint to the Dictionary of constraint maps. This Dictionary is initialised with a number of default constraints when the RouteOptions is first constructed. I’ll look into the routing code in a little more detail in a future post.

A new Route object is constructed using the name and template provided. In our case this is added the RouteBuilder.Routes List. By this point, running the sample MvcSandbox application, our route builder now has a single route added.

Note that the MvcApplicationBuilderExtensions class also includes an extension method called UseMvcWithDefaultRoute. This method includes a call to UseMvc, passing in a hardcoded default route.

This route is defined with the same name and template as defined in the MvcSandbox application. Therefore it could have been used as a slight code saver within the MvcSandbox Startup class. For the most basic of MVC applications it’s possible that using this helper extension method might be enough for your application routing requirements. In most real-world cases I suspect you’ll end up passing in a more complete customised set of routes. It’s a nice shorthand though if you want to start with the basic routing template. It’s also possible to call UseMvc() without a parameter and instead use the attribute routing approach. 

Next, the static AttributeRouting.CreateAttributeMegaRoute method is called and the resulting route is added to the Routes List (at index zero) on the RouteBuilder. CreateAttributeMegaRoute is documented with the summary “Creates an attribute route using the provided services and provided target router.” and it looks like this:

This method, creates a new AttributeRoute which implements the IRouter interface. Its constructor looks like this:

In its constructor it takes an IActionDescriptorCollectionProvider, an IServiceProvider and a Func which takes an array of ActionDescriptor’s and which returns an IRouter. By default, with the AddMvc registered services, it will get an instance of ActionDescriptorCollectionProvider which is a singleton object registered with the ServiceProvider. ActionDescriptors represent the available MVC actions that have been created and discovered within the application. We’ll look at those another time.

The code (inside the CreateAttributeMegaRoute method) which creates the new AttributeRoute uses a lambda to define the code for the Func<ActionDescriptor[], IRouter>. In this case the delegate function requests an MvcAttributeRouteHandler from the ServiceProvider. Because this is registered as transient, the ServiceProvider will return a new instance of an MvcAttributeRouteHandler each time one is requested. The delegate code then sets the Actions property (an array of ActionDescriptions) on the MvcAttributeRouteHandler using the incoming ActionDescriptions array and finally the new handler is returned.

Back to UseMvc; it finishes by calling the UseRouter extension on the IApplicationBuilder. The object passed into UseRouter is created by calling Build on the RouteBuilder. This method will add the routes into the RouteCollection. Internally the RouteCollection keeps track of which routes are named and which are unnamed. In our MvcSandbox example we end up with a named “default” route and an unnamed AttributeRoute.

UseRouter has two signatures. In this case we’re passing in a IRouter so the following method is called:

This does a check for a RoutingMarkerService within the ServiceProvider. Assuming this is registered as expected  it adds the RouterMiddleware into the middeware pipeline. It is this middleware which will use the IRouter to try and match up requests to the controller and action within MVC which should handle them. The details of this process will feature in a future blog post.

At this point the application pipeline is configured and the application is ready to start receiving requests. That’s a good point to stop for this post.

Summary

In this post we’ve looked at UseMvc and seen that it sets up the MiddlewareFilterBuilder which will be used later. It then also gets an IRouter via a RouteBuilder and much of the work at this stage is around registering routes. Once everything is setup, the routing middleware is registered into the pipeline. It’s this code that knows how to inspect incoming requests and map the paths to the appropriate controllers and actions that can handle them.

Other Posts In This Series

ASP.NET Core Anatomy (Part 2) – AddMvc Dissecting and understanding the internals of ASP.NET Core

Welcome to part 2 of my series where I dissect the internals of the ASP.NET Core source code. Eagle eyed readers will have noticed a slight change to my title for this post. I decided to drop MVC from it. While I’m mostly interested in MVC anatomy at this point, I’ve realised that as I work through the flows, I’m stumbling into features which are part of the main ASP.NET Core framework, such as IRouter. Therefore, it feels sensible to allow myself scope to drift into discussing those parts and as a result, ASP.NET Core Anatomy is a more accurate series title.

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.

Summary

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

ASP.NET Core MVC Anatomy (Part 1) – AddMvcCore Dissecting and understanding the internals of ASP.NET Core MVC

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.

AddMvcCore

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.

Summary

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.

Other Posts In This Series