ASP.NET Core Anatomy – How does UseStartup work? Exploring how UseStartup results in your Startup methods being registered and executed.

I was recently explaining to someone the basics of the program flow for an ASP.NET Core application. One of the things included in the templates for ASP.NET Core and used very often is the UseStartup<T> extension method on the IWebHostBuilder. This gets called from our Program.cs when initialising the application. UseStartup allows us to set the Startup class which defines the services and middleware pipeline for an ASP.NET Core application.

During my explanation I realised that while I know the result of calling this method, I didn’t know how things are wired up under the hood; so I decided to investigate!

NOTE 1: This content is valid as at the 2.0.0 release codebase. I don’t expect that the fundamentals will change dramatically in the future but I have seen some commits which tweak the code a little for 2.1!

NOTE 2: This is a deep dive blog post looking at internal ASP.NET Core code. You don’t need to know this to use ASP.NET Core to build applications – please don’t let this scare you off! This is intended for those of you, who like me, have a curious mind about the internals of ASP.NET Core. I’m conscious that this may get quite hard to follow as we get deep into the guts of the code as there’s a lot of use of delegates that makes explaining the flow quite challenging. I’ll try my best to make it clear!

How are Startup methods registered and executed?

The generic UseStartup<TStartup> method calls down to the main IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) extension, passing in the Type for the Startup class. That method looks like this (full source on GitHub):

The ConfigureServices method on the IWebHostBuilder is called which expects an Action<IServiceCollection> parameter. In this case it’s defined as a lambda expression which acts on the IServiceCollection. The WebHostBuilder class has a private List<Action<WebHostBuilderContext, IServiceCollection>> field named _configureServicesDelegates. The call to ConfigureServices in the code above will add the lambda as a new item to this list which will be used later to construct an instance of IStartup.

At this stage a list of delegates will have been registered within the WebHostBuilder. The main process begins when Build() is called on the WebHostBuilder. I won’t cover everything that Build does, since it’s not all relevant to the scope of this post. Here’s the code of that method (full source on GitHub):

The Build method calls a private method BuildCommonServices (full source on GitHub) which as the name suggests will add some common framework services into the current ServiceCollection. I’ll skip over most of the code in this method. Unless we’ve changed the WebHostOptions to define different assemblies to load Startup from, the execution flow will eventually hit code which looks over the _configureServicesDelegates List, calling each delegate in turn. That piece of code looks like this:

In the sample application which I used in order to debug through the Hosting codebase, I have two delegates registered, one from a FakeServer (needed so that the WebHostBuilder doesn’t throw an exception) and one from the UseStartup call. The first delegate simply registers the FakeServer as the implementation for IServer inside the ServiceCollection. The second delegate will now execute the lambda expression which was registered in the UseStartup method. Let’s remind ourselves what that looked like:

If our Startup class implements IStartup directly, it can and will be registered as the implementation type for IStartup directly. In my sample (which is based on the default ASP.NET Core templates) our Startup class does not implement IStartup and will rely on conventions instead. In this case an AddSingleton overload is used which takes the Func<IServiceProvider, object) as it’s implementation factory. This Func delegate will be called when the first concrete IStartup implementation is requested from the DI container.

At this point the registration of IStartup is included in the ServiceCollection and ready to be called by the framework. The WebHostBuilder.Build method continues to execute and constructs a new WebHost instance, which includes passing in an IServiceCollection (a clone of the current hostingServices variable). It also passes in a ServiceProvider, built using the current state of the hostingServices ServiceCollection. This represents the application services which have been registered so far by the framework.

Once we have a WebHost instance, its Initialize method is called. This calls down to a private BuildApplication method (full source on GitHub). Hold tight, lots of stuff starts to happen at this stage. I’ll try to pick out the parts I think are relevant to the use of our Startup class.

The BuildApplication method does some basic checks to make sure the relevant services are available for the application to start. One of the checks is that the ServiceProvider which was passed in includes an implementation for IStartup. This particular check happens inside a helper method called EnsureStartup()

The call to _hostingServiceProvider.GetRequiredService<IStartup>(); will trigger the DI framework to construct an instance of IStartup as per its registration. Due to the use of delegates, we need to head back to the UseStartup method to look at the lambda we passed in for the Func<IServiceProvider, object) implementationFactory. As a reminder, here’s the service registration that was used:

We can see that we’re going to get returned a newly constructed ConventionBasedStartup instance as the implementation for IStartup. The ConventionBasedStartup constructor accepts a StartupMethods object (full source on GitHub) as its parameter. A static StartupLoader.LoadMethods method (full source on GitHub) is used to generate a StartupMethods instance. This object acts as a holder for three properties.

The most important of these properties for this discussion are the delegates for the ConfigureServices and Configure. These will be setup with the code which should execute when the framework calls these methods further down in the WebHost initialisation. Ultimately the code for these delegates is expected to execute methods on our our Startup class.

The first delegate it will try to find is the ConfigureDelegate. This will be used to build the middleware pipeline for the application. Internally the StartupLoader uses a helper method called FindMethod to do most of the work. This is called from a FindConfigureDelegate method (full source on GitHub). The FindMethod is as follows (full source on GitHub):

This method will first work out the method name(s) it should be looking for on the Startup class based on the methodName parameter passed to it. The convention is that the method which defines the middleware pipeline should be called Configure. There is a lesser known convention in the Startup class which in addition to providing the standard “Configure” method, you can choose to include environment specific version(s) in your Startup class too. By convention, if a Configure{EnvironmentName} is found (e.g. “ConfigureProduction”) for the current environment, that method will be used in preference to the general Configure method.

In our sample, we only have the standard Configure method defined. Reflection is used to find a method matching the expected name on our Startup type. There are various checks in place to ensure that the expected members on our class are valid (i.e. we don’t have more than one Configure method defined with the same name) and that it has the expected Void return type.

Once we have the MethodInfo for the matching method, it is passed as the parameter into the constructor for a new ConfigureBuilder instance (full source on GitHub). This is stored in a variable in the LoadMethods method to be used a little later on.

A very similar process occurs to find and store the MethodInfo for ConfigureServices from our Startup class which is stored in a local variable called servicesMethod. Finally, the same approach is used looking for a ConfigureContainerDelegate. This is  an optional method which we can include on our Startup class to interact with 3rd party dependency injection containers such as AutoFac. We won’t look at this here.

Next, inside LoadMethods, a static ActivatorUtilities.GetServiceOrCreateInstance is called to get or create an instance of our Startup class. Here’s a compressed version of that LoadMethods method for reference (full source on GitHub):

As an implementation instance of IStartup is not currently stored in the DI container, GetServiceOrCreateInstance will create an instance of our Startup class by calling it’s constructor. In my sample (which matches the default Startup class in a new ASP.NET Core application template) it expects an IConfiguration object be passed in. The DI framework will have access to an implementation for this and will inject it in for us. Here’s my Startup constructor for reference:

Next the method on the ConfigureServicesBuilder is set as a callback variable. It gets passed the newly created Startup instance as its parameter. The same occurs to store a callback for ConfigureContainer. Next a Func<IServiceCollection, IServiceProvider> is setup using a lambda expression (which I’ve excluded from the code above for now). We’ll look at this when we see how this gets called a little later.

At this point, the callback delegates are passed into the constructor for a new StartupMethods instance which is then returned as the result of LoadMethods. This is then passed into the constructor for the new ConventionBasedStartup instance. At this point we have a concrete implementation of IStartup registered with the DI framework. Back inside the WebHost.EnsureApplicationServices method, the ConfigureServices method from the IStartup interface is called. Ready to start navigating some delegates!?

The ConfigureServices method on the ConventionBasedStartup instance calls the ConfigureServicesDelegate property on its StartupMethods member.

This executes the lambda defined code from the StartupMethods.LoadMethods method which in-turn invokes the Func<IServiceCollection, IServiceProvider> delegate returned from the ConfigureServicesBuilder.Build method, which ultimately calls the private ConfigureServicesBuilder.Invoke method (full source on GitHub).

The Invoke method uses reflection to get and inspect the parameters required by the ConfigureServices method defined on our Startup class. By convention this method can be either parameterless or take a single parameter of type IServiceCollection.

If the ConfigureServices method on our Startup class expects the IServiceCollection parameter, this is set using the IServiceCollection which was passed into the Invoke method. Once the method is configured via reflection it is invoked and the returned value will either be Void or an IServiceProvider. It’s at this point that we are actually executing the code contained in our ConfigureServices method on our Startup class. Our class can use the IServiceCollection extensions to register services and their implementations with the DI container.

At this point the lambda expression (in StartupLoader) wants to return a ServiceProvider. If our Startup.ConfigureServices method returned an IServiceProvider directly, this then gets returned immediately. If not, an IServiceProviderFactory is requested from the hostingServiceProvider and used to construct the ServiceProvider. This is the application level ServiceProvider that will be used to resolve dependencies in our code base.

The final point I’d like to show within WebHost.BuildApplication is how the final RequestDelegate is built. We won’t cover this in depth here, but in short, the RequestDelegate is defined as “A function that can process an HTTP request.” This is what the framework will actually use to process each request through our application. This will be setup to include all of the middleware as defined in our applications pipeline.

The relevant code in side of WebHost.BuildApplication is (full source on GitHub):

An IApplicationBuilderFactory is used to build up and finally surface our RequestDelegate. This is one of the services registered earlier in the WebHostBuilder.BuildCommonServices method.

The ApplicationServices property on the builder is set with the ServiceProvider that was just created. The next detail is something I’ll gloss over slightly as it goes a bit too far off the flow I want to explore. In short an IEnumerable of IStartupFilters may have been registered with the DI framework. In my sample I haven’t registered any so only the default AutoRequestServicesStartupFilter (full source on GitHub) will be returned from the ServiceProvider.

An Action<IApplicationBuilder> delegate variable is created holding a wrapped set of Configure methods from each IStartupFilter, the final one being the delegate for our Startup.Configure method. At this point, the Configuration chain is called which first hits the AutoRequestServicesStartupFilter.Configure method. This holds our delegate chain as its next action and so this will call down into the ConventionBasedStartup.Configure method. This will call the ConfigureDelegate on its local StartupMethods object.

Invoking that Action will call the private ConfigureBuilder.Invoke method (full source on GitHub) which looks like this:

This will prepare to call our Startup.Configure method sending in the appropriate parameters which get resolved from the ServiceProvider. Our Configure method can add middleware into the application pipeline using the IApplicationBuilder. The final RequestDelegate is built and returned from the IApplicationBuilder and the WebHost initialisation then completes.

Summary

This has been a quite long and deeply technical post. If you’ve stuck with it; well done! I hope I’ve interpreted everything correctly and lifted the curtain on some of the “magic” behind the scenes that makes ASP.NET Core work. It was quite difficult to explain everything due to the layers of delegates involved here. Hopefully I did a good enough job for you to get the gist of things. I find it really useful to dig into the code like this and gain a better understanding of the internals. If you want to explore the code yourself, check out the ASP.NET Core Hosting repository on GitHub.

Other posts in this series

Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered in this series.

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

Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered 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

Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered 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

Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered in this series.