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

Things I’ve Learnt This Week (12th March)

Week 7 of my series, sharing things I’ve learnt, read, watched and listened to, in the pursuit of expanding my knowledge about software development.

Things I’ve Blogged

Things I’ve Learnt

ASP.NET Core MsBuild is Quicker than Project.json

It seems that the build time for the ASP.NET Core framework is nearly halved after moving back to csproj and MsBuild. Reduced down to approx. 28 mins from 52 mins. Read the tweet here

Live Unit Testing in Visual Studio 2017

It was a little disappointing to learn that the new live unit testing feature is not supporting .NET Core projects at launch. Looks like I’ll have to wait a while to try it since most of my projects are .NET Core at the moment.

Registering a Dependency with a non injectable property using Microsoft.Extensions.DependencyInjection

I was looking at an ASP.NET Core project which was using StructureMap to enable some more advanced service registrations that the built in Microsoft DependencyInjection package allowed. However, after looking through, I only found a few that I wasn’t sure how to register with the built in DI container. The project had a StructureMap registration like this:

c.For<DataValidator>().Transient().Use(() => new DataValidator(DataSource.DefaultData));

The DataSource.DefaultData is a static method in this case. After looking at the IServiceCollection methods, it seems the following line works with the default container to achieve the required result:

services.TryAdd(new ServiceDescriptor(typeof(DataValidator), p => new DataValidator(DataSource.DefaultData), ServiceLifetime.Transient)); 

Things I’ve Read

Things I’ve Listened To

Things I’ve Watched

Tools

Visual Studio 2017 is Released Reflecting on the loss of project.json and looking ahead to the new VS 2017 experience

Visual Studio 2017 is released today and I thought it was a good time to reflect on my thoughts about one of the main changes it brings for ASP.NET Core developers. This won’t be a detailed features post, as I’ve only tried the RC a few limited times. Due to some bugs and issues I experienced, I ended up removing the release candidate from my PC and only recently installed the latest more stable version. I plan to begin working with Visual Studio 2017 as soon as possible so I can start to explore the benefits it offers. A few of the new features and tooling enhancements have caught my eye and I’m keen to experience them first hand.

The big change for anyone who has worked on ASP.NET Core projects to date, is the the loss of the project.json format and the move back to a revised and improved csproj format. It’s a decision which caused a lot of reaction when it was first announced and since then has been a common topic of conversation for ASP.NET developers. I’m a fan of project.json format. I found it easy to use, simple to work with and a nice step forward. Personally I would prefer we weren’t loosing it entirely and that the tooling had been developed in a way that could support both formats. Pragmatically I can understand some of the reasons behind the change. Large customers, with heavy reliance on and experience of MsBuild for example, I’m sure would have avoided any use of ASP.NET Core without this compromise being made. Ultimately I am accepting of anything which can allow ASP.NET Core to gain more traction and become the framework of choice for C# based web developers. I do however, find myself a little concerned with the way the news of the change has impacted the first impressions of some people about ASP.NET Core. In a introduction to ASP.NET Core talk I’ve recently presented, it was odd having to avoid too much detail on project.json because I knew it would soon be a thing of the past. Explaining to people that they need to know this now, but don’t get too invested in it as it’s soon obsolete was hard. It was especially confusing for people to be shown the newest version of ASP.NET that already due quite a radical change.

In part due to it’s open development and partly due to this latest change, some people I’ve talked to are left a bit afraid of taking on ASP.NET Core right now. They have the impression it’s not finished, hard work, changing too frequently and ultimately not a safe choice for new projects at the moment. I don’t share these views myself; but I think that it’s partly because that I’ve worked with ASP.NET Core from around the beta 8 time frame. I’ve used it a lot during that time and even have a production system nearly ready for launch which is based around ASP.NET Core API microservices. I’ve seen firsthand the ease of use and enhancements it offers, so I am able to see how those benefits outweigh the bumps along the way. But if new developers are left apprehensive by the strong opinions being shared and the breaking changes, that does worry me a little. I truly hope this is the last of the big forced changes we should expect to the fundamentals of the technology and we can move forward, embracing the future of ASP.NET Core. I certainly wouldn’t and haven’t for the last few weeks promoted starting a new ASP.NET Core project in VS 2015 but at least with today’s launch, we finally have the RTM tooling to be able to move forward with ASP.NET Core and csproj. For any concerned developers reading this post, I think with VS 2017 we should be in the right place to really start making ASP.NET Core the first choice for new projects.

An important fact to state here is that the tooling for ASP.NET Core has always remained in preview. Despite .NET Core and ASP.NET Core being released back in June last year, under full support, we’ve been told that tooling would RTM at a later date. Many of the early adopters to ASP.NET Core would have experienced some pain and glitches with the earlier versions of the preview tooling, although in the latest versions, I’ve had a much more stable experience. My assumption, and I think that of many other developers was that by investing in ASP.NET Core on Visual Studio 2015 early on, we would eventually get some form of RTM tooling in that IDE. However, at the end end of January I picked up on a comment from Damian Edwards during the ASP.NET Community Standup. What I thought I’d heard was that we would never get final supported tooling for Visual Studio 2015.

Puzzled and unsure of my interpretation of his statement, I reached out to Damian on Twitter. Here is the start of the Twitter thread. In summary, I learned that the decision being made was apparently clear. VS 2015 tooling and project.json would never formally RTM and be supported. Anyone with a project.json project would need to upgrade to Visual Studio 2017 at release or use VS code in order to access the supported RTM tooling for ASP.NET Core running on csproj. As Damian explained it, the csproj changes required core IDE changes in Visual Studio 2017 to be possible. These changes would not / could not be brought back into VS 2015. This is where I start to feel a little left out in the cold by Microsoft. Fair enough, the tooling was preview, but we’ve backed the new and supported ASP.NET Core early, with the direct assumption we had a path forwards on the existing IDE version. Would I have started so early with a production system using ASP.NET Core if we had known our team will need to plan and handle a move to VS 2017 to gain tooling support? Perhaps not. I’m still a huge supporter of the new framework and we’re able to run our services inside Docker, on Linux, which has some great advantages for us. The deployment possibilities this has opened up to us is a key advantage of ASP.NET Core and one I hope to explore on this blog soon. Perhaps it’s just me that missed the point until quite late that the csproj change essentially made ASP.NET Core a VS 2017 product only.

A good demonstration of how the change effects early adopters is with open source projects such as the Humanitarian Toolbox allReady project, to which I contribute regularly. Here we have a real-world ASP.NET Core application which started it’s development way back in the early days of ASP.NET Core. allReady has moved through the various beta, release candidate and finally RTM versions of ASP.NET Core. A journey which has been complicated and painful at times due to some breaking changes. It was an accepted journey, given we were working with software still under development, and while I don’t think anyone expected quite so many twists and turns, the team ducked and weaved through them. The issue of the demise of project.json along with the Visual Studio 2017 release, does pose some logistical issues for the project team though. We’re actively discussing in an issue how and when we want to make the switch by migrating to csproj. We’re slightly stuck between a rock and a hard place. On one hand, we’re on unsupported tooling and also run the risk of new contributors opening the solution in VS 2017 and being migrated too early – leaving them unable to commit back to the project. On the other hand, if we migrate now, do we risk loosing contributors who aren’t in a position to, or willing to, upgrade to Visual Studio 2017 at this time? Agreed, they can still use VS Code or a community edition of Visual Studio 2017 if the concern is around licensing, but this still puts constraints on the ease of entry which we very much try to avoid. With allReady we’ll have to migrate fairly soon to avoid contributors who move to VS 2017 and don’t keep VS 2015 installed side-by-side from finding themselves unable to contribute any further. 

I feel slightly aggrieved that despite using the LTS supported version, we’re forced into a full IDE version upgrade in order to continue using that version. It slightly blurs the lines of how I feel a supported product should work. Microsoft have chosen the safer and arguably easier road for themselves which is to avoid putting even a basic and time limited project.json tooling experience into the wild under some form of support. This would have provided greater time for teams to embrace and migrate to csproj at their own speed. The project.json format is a really key part of the structure of ASP.NET Core projects built to date. Simply pulling that piece out from under the earlier adopters doesn’t sit quite right with me. I’m sure many discussions were had, compromises agreed, management decisions made that led to the final agreement to in effect, force the rapid death of project.json. Putting my Microsoft hat on, I can see why this makes things a lot easier for them in the long run. I’ve been fairly accepting of the churn we saw during the final phases leading up to RTM for ASP.NET Core, appreciating that in the end it was to give us a more suitable final product. This has been the one issue that I find myself less able to accept without a little annoyance clouding my thoughts. It’ll be nice to just get it behind us now and move forward without having to consider an impending change around the corner.

One key thing I’m conscious of loosing with the new csproj format is the simplicity of debugging into the ASP.NET source code. Currently a simple addition to the global.json file allows us to include the source into a solution, instead of using the Nuget supplied package. This has been something I’ve taken advantage of many times to learn more about the internal workings of MVC for example or to understand why my application is behaving in a certain way. I believe some level of support is being looked at to keep this functionality and an open and current discussion is ongoing on GitHub to continue to enable a source debugging experience. Initially this doesn’t sound quite a simple and painless as the solution I’ve been able to use to date. I hope we can get to a point where debugging the source is similar to the experience that the current json based format supports.

So, there you have it, my thoughts around project.json laid bare for (probably) a final time as the much loved project.json file moves closer to being a short lived part of .NET history. Visual Studio 2017 is pretty much the final and definitive nail in its coffin. While I have a slightly bitter taste in my mouth, don’t get me wrong, I’m still massively excited by ASP.NET Core in general and thankful that Microsoft are putting great effort into building an open platform for the future. I’m already excited about version 2.0 and I’m also keen to start working with Visual Studio 2017 both at home and at work. I expect to start doing so over the coming weeks and something I’ll be experiencing is how a real-world migration to csproj works.

Looking to end on a more positive note – besides the csproj issue – I am looking forward to VS 2017. Here are just a couple of initial features that I like the look of in Visual Studio 2017 from the information I’ve seen so far.

  1. The new, more rapid installation process does sound nice. Granted, once installed I don’t find myself doing installs too often, but it does seem to simplify the removal footprint as well.
  2. Various other performance related improvements and new configuration options promise to make working with solutions faster and easier than ever before. It’ll be interesting to see if those are noticeable or not.
  3. The new Git integrations within VS 2017 look nice. Any time I can save by not having to switch out of Visual Studio is very welcome.
  4. Live unit testing is a feature which I would like to take advantage of. Limited to the Enterprise license though, this may not be seen by everyone. It offers the continuous running of unit tests in the background, giving instant feedback on breaking changes as code is written. This sounds like it will improve productivity and generally make working on complex projects an easier experience.
  5. Finally I like the concept of the Code Style feature, which looks to allow a project level specification of coding standards that should be applied. Any deviance from the standards will result in compiler warnings to help developers quickly remain consistent. This feels like a nice enhancement to keep teams using a common structure in their code which can only help readability between team members. It’ll be interesting if this can assist with open-source projects like allReady as well.

Over the coming weeks I’ll be trying out the features that are available to me, and hopefully reaping the benefits they bring. After reading this post you could be forgiven for thinking I’m against VS 2017 or raining on it’s parade, but that’s not the case. I am always excited about new versions of products like this, and Visual Studio 2017 is no different. It promises to bring some great new features to developers everywhere. For me, my excitement is only slightly reduced by the gripes I have with the ASP.NET Core tooling but I’m hoping this is the final large piece of pain developers can expect on the early ASP.NET Core adoption path. I guess to end this post I’ll simply say – thank you project.json, you’ve been great!

Things I’ve Learnt This Week (5th March)

Week 6 of my series, sharing things I’ve learnt, read, watched and listened to, in the pursuit of expanding my knowledge about software development.

Things I’ve Blogged

Customising ASP.NET MVC Core Behaviour with an IApplicationModelConvention – In this post I cover details and demo the basics of creating custom conventions for ASP.NET MVC Core.

Things I’ve Learnt

UserSecrets API Changes

One of the changes coming due to the project.json to csproj move is a slight change needed on the UserSecrets API. In current project.json applications we can use the AddUserSecrets() extension method. This picks up the UserSecretsId from the project.json file and sets up the UserSecrets config using it. With the move to csproj this method’s implementation might pick up the wrong assembly and therefore not find the newly located UserSecretsId. To resolve this, the method now needs the assembly to be passed to into it.

There is a new generic call we can make we can use with looks like  .AddUserSecrets<Startup>() – This takes a class and will use the assembly where that class is located when calling the AddUserSecrets(assemblyname) method.

When ASP.NET Core 2.0 lands, the old API for AddUserSecrets() will be removed. Making it clear that ASP.NET Core 2.0 will require VS 2017 and support csproj only.

You can read more about these changes on Github here, here and here.

Things I’ve Read

Things I’ve Listened To

Things I’ve Watched

Customising ASP.NET MVC Core Behaviour with an IApplicationModelConvention

I’m currently building a number of ASP.NET Core API applications which together form a new product we’re developing. As we defined the requirements, we came up with a few behaviours that we wanted to apply across the APIs by default. Two examples that I’ll use in this blog post are, applying a prefix to all of our routes and securing all of the endpoints by default.

In the case of the route prefix we needed this in order to support sharing a single AWS application load balancer between the APIs. We have three API microservices working together as part of our system, and each needed its own unique prefix so that the load balancer could direct the traffic to the correct service. We use attribute routing across our APIs and wanted a way to be able to apply a prefix, without hardcoding it onto every controller.

The second requirement from our security discussions, was that we wanted every endpoint to be locked down to the most restrictive authorization policy by default, even when no authorization attribute is applied. We prefer this hardened by default behaviour to avoid the possibility of unsecured access when changes are made. Using this approach we open up access on endpoints on a case-by-case basis.

After a bit of searching on the Internet, followed by a dive into the MVC source, I found a set of interfaces that allowed us to begin customising how MVC Core behaves. Enter the following interfaces…

  • IApplicationModelConvention
  • IControllerModelConvention
  • IActionModelConvention
  • IParameterModelConvention

What are Conventions?

Conventions exist throughout MVC and allow us to work with the framework without having to specify everything manually. Conventions are behaviours that MVC will apply by default and we can lean on these conventions in many cases. An example is the fact that when defining routes in Razor we can use the Controller name without the “Controller” suffix each time. MVC will handle that by convention.

However, as we develop more complex applications, we may want to define our own rules and conventions, or customise the existing behaviours. The four interfaces listed above provide a mechanism to allow us to do just that.

How Can We Create Them?

To start developing a new convention, you create a Class which inherits from one of these interfaces. When the MVC application is loading, we can register these new conventions, which will then be applied as MVC builds up a model in memory that represents the application. Each of these interfaces gets more specific in terms what they target and they are applied in this order as well. More specific conventions will override less specific conventions set higher up in the application model.

In this post I’ll demonstrate creating a couple of conventions. In order to keep everything easy to understand I have reduced the complexity of the conventions themselves as I want to focus on the mechanics of how we create them.

Defining the API

For this example I’ve put together a very basic single Controller API that will allow me to demonstrate the conventions. Here is what the Controller looks like.

Route Prefix Convention

As I mentioned above, in our APIs we use attribute routing to define the routes for all of our endpoints. I prefer this to the route builder approach as it makes documenting the API more explicit in my view. It’s easier for someone working with the code to see the paths that we are using, and also means we can use tools like Swashbuckle to build a swagger interface over our endpoints.

As I also stated, we host our APIs behind an AWS application load balancer. In order to share a load balancer, we need the traffic for each API to come in from a distinct path under a single shared domain. This allows us to use the AWS load balancer path routing to direct the traffic into the correct API application. Due to the way we engineer things, we wanted to be able to potentially update this path in the future, so we didn’t want it to have to be defined as part of the route attributes.

In our case a solution we came up with was to use a convention that would inspect the routes applied to each Controller, updating them at startup, so that the correct prefix was applied.

For this convention we will inherit from IControllerModelConvention which will allow us to work with the Controller and below. Since the attributes are defined on the Controllers, this is perfect for our needs. The convention I ended up using looks like this…

This is a simplified version of the convention we used in our application but it’s fine for demonstrating the main principles. The first thing to highlight here is the AttributeRouteModel I’m defining at the top. An AttributeRouteModel allows us to programmatically define the properties that make up a RouteAttribute that gets applied on Controllers and Actions. I’m defining one here that contains the template prefix I want to use. In a real world application, I define this template in our configuration and pass it into the RoutePrefixConvention on construction. But to keep this blog post focused I skipped those elements and hard code it as part of the Class.

The IControllerModelConvention interface defines a single void method called Apply. We use this to apply our behaviours to the Controllers in our application. The method accepts a ControllerModel parameter which gives us access to properties on each controller as the application starts up.

Now that we have the ControllerModel we use a collection on it called Selectors, which allows us to inspect the AttributeRouteModel applied to the controller. In the case where we find an AttributeRouteModel, it means the controller already has a Route applied to it. In this case we want to prepend the route defined on the Controller attribute with our prefix. We do this using the static CombineAttributeRouteModel method, which takes two AttributeRouteModels and combines them into a new model. In our example, I have used the prefix first and then applied the value that was defined explicitly on the controller. In the case of our HomeController this means that MVC now understands this to take the template “Prefix/Home” in its route definition. Where we do not find an existing AttributeRouteModel we can simply set the value of this on the selector to our defined prefix. Therefore any Controller without a route attribute will be considered to operate under the “Prefix” path.

Now when our application starts up, it gets configured using our required prefix across all controllers. In our case this means we can rely on the fact that our endpoints will all operate under a single prefix which our load balancer can use to route the traffic. Developers do not need to manually include the prefix value in the controller route and we avoid the risk that they may forget to do so. We also only need to maintain the prefix value in a single place, rather than having to update each controller if this prefix were to change in the future.

Authorization By Default Convention

Another example of a convention we wanted to apply in our APIs was that by default they would be secured to the highest level even when no AuthorizeAttribute is applied. There are various ways this might be achieved but we decided that a convention worked nicely for our requirements. By having actions secured by default it means there is no risk of a new Action being added that could inadvertently be left unsecured if the developer forgets to include an AuthorizeAttribute.

A simplified example of how we achieved this is as follows:

In this convention, since we want to work with Actions we have inherited from the IActionModelConvention interface. Every action in our application runs through this convention, applying the new convention as necessary.

Again we have an Apply method, this time accepting an ActionModel, representing the Action. First we check if we need to apply our convention to the Action. In our case, we only want to apply the AuthorizationPolicy if the Action does not have one specified or has not been explcitly marked as allowing anonymous access. In cases where these attributes have been set we assume the developer has controlled the access as required. Our convention only applies to Actions if they are missing any specific Authorize or AllowAnonymous attributes.

If we do find an action missing the attributes we create an AuthorizationPolicy and apply it with an AuthorizationFilter to the Action. Again, this example is over simplified in that our actual code defines many policies during startup, and in our case we pass in a restrictive policy into the Convention using it’s constructor. The code needed to do that is outside of what I wanted to demonstrate in this post.

For my example HomeController, the Index action does not specify any Authorization values. So by default this will now be secured by convention with a require authenticated user policy. If we access it without being authenticated we receive a 401 result back. The OpenEndpoint Action however will allow unauthenticated users since we have explicitly defined it to AllowAnonymous.

Wiring It Up

The final piece of work is to include our custom conventions so that MVC knows about them and can use them when it builds up its application model. We do this by adding the conventions to a Conventions collection on the MVC options during service registration. Here is the ConfigureServices method that I’ve used in this demo.

On the AddMvcCore extension we can work with the MvcOptions object. We simple call Add on the Conventions collection to register our new conventions.

In Summary

The convention interfaces provide a powerful and simple way to begin defining bespoke behaviour on your applications. They provide a nice single place for customisation to occur and are quite simple to work with. One consideration to keep in mind is that they might make your application confusing to new developers who expect it to work using the MVC default conventions. Developers need to be aware that custom conventions are being applied and how those may override the default behaviour of your application.