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.

Things I’ve Learnt This Week (19th February)

Week 4 of my series, sharing things I’ve learnt, read, watched and listened to, in the pursuit of expanding my knowledge about software development. A slightly shorter set of links this week, things have been fairly busy so I’ve had less time to keep up with all of the fantastic content.

Things I’ve Learned

That I enjoy technical speaking! This week, I delivered a talk to a room of developers about ASP.NET Core. It’s a popular topic which I think generated the interest and high attendance. I’m quite shy and an introvert by nature, and I generally hate any form of public speaking so presenting a talk is way out of my comfort zone. However, it’s something I’ve been keen to work on; It’s a great way to share information and helps me learn as well. I had the usual nerves leading up to the talk, but I’d practised and refined it over a number of rehearsals, to the point that I was confident in what I was delivering. The talk also included a 30 minute live demo, which thankfully worked perfectly thanks to many practice runs. As soon as I got going I started to feel better and by the end, was on a bit of a natural high. I’ve had some very nice feedback from some of the attendees and I’m encouraged to work on future talks as well as hopefully sharing this one with a wider audience in the future. If I was asked for advice from other developers looking to prepare a technical talk, it would be practice, practice, practice! Having gone through my slides, rehearsing the full talk at least 10 times, I knew what I was going to say and that allowed me to present confidently.

Things I’ve Read

Things I’ve Listened To

Things I’ve Watched

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

Week 3 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 Learned

Dependency Injection in Middleware

An important thing to consider when building custom Middleware in ASP.NET Core – If you are injecting a dependency into the constructor of your Middleware it may have unwanted effects if that dependency is expected to be a scoped/transient resource. The Middleware is constructed once for the app lifetime during Startup and not recreated for every request. If you want a transient/scoped dependency, inject it into the Invoke method’s parameters which is called once per request and therefore will give you the correctly scoped instance of your dependency every time.

Dependency Injection Lifetime Fun

A little long for this weekly post, I wrote up a dedicated post this week about some “fun” I had tracking down a range of errors we were seeing in some code this week. Spoiler alert… scoped lifetime dependencies inside a singleton lifetime registration = not good!

I also published a post this week entitled “Migrating from .NET Framework to .NET Core” which describes the process I followed to move an ASP.NET Core application from the full .NET framework onto .NET Core, enabling cross-platform development.

Things I’ve Read

In no particular order here’s some of the blogs and posts that I’ve read this week.

Things I’ve Listened To

Things I’ve Watched

Tools

  • ChangemakerStudios – Papercut – A useful local SMTP receiver for testing email in your code on localhost. This week I was adding some email functionality to one of our services and I wanted to test the flow locally in development. This tool was really easy to use and worked perfectly for my requirements.

Migrating from .NET Framework to .NET Core The journey of re-targeting an ASP.NET Core application onto .NET Core

What better way to start the new year than with some coding? In my case I’d found a bit of time during the end of my Christmas vacation to tackle an issue on allReady I’d been wanting to work on for a while. Before getting into the issue, if you want to read more about what allReady is you can read my earlier blog post where I cover contributing to it in greater detail.

allReady was first created during the early beta’s of ASP.NET Core (at which point it was known as ASP.NET 5). At that time, due to some dependencies which did not yet support .NET Core, the application was built on top of the full .NET framework 4.6.

The first thing to discuss briefly is why and how we can run ASP.NET Core on the full traditional .NET framework. While it might be reasonable to assume that ASP.NET Core naturally needs to run on the new .NET Core framework, remember that .NET Core ultimately includes a subset of the larger full .NET framework API. Because of this, it is perfectly possible to run ASP.NET Core on the original 4.x version of the .NET framework. This is an advantage to those that want to make use of the new features and optimizations within ASP.NET Core, while also still utilising the libraries they know and love. This is a perfectly reasonable way to run ASP.NET Core but with an important limitation. By targeting the .NET framework you are not able to develop or host the ASP.NET application outside of Windows.

For the allReady project this had started to become a bit of a limitation. We had Mac users who were keen to contribute to the project, but were unable to do so without installing and running a Windows VM on their device. As we were getting to the end of some complex requirements for allReady’s v1 milestone, I took the opportunity to spend some time looking at retargeting the application to run on .NET Core. This turned out to be a bit more involved that I had first anticipated. However, we have succeeded and the code base has been proven to build and run on both Mac and Linux devices.

Updating the Solution

The first part of the process was to update the project.json file inside the web solution. The main update here was changing the framework specification to netcoreapp from net46.

project.json before:

project.json after:

We had decided as a team to continue to track the .NET Core 1.0.x LTS support stream. In short, this is a stable version of the framework, under full Microsoft support. New minor patch releases are appearing to fix major bugs or security issues, but otherwise the changes are expected to be quite limited. Our project was already targeting the latest 1.0.3 SDK and latest 1.0.x libraries for each of the components.

You’ll see above that as well as changing the framework moniker I’ve added an imports section as well. This was required in our case as we have some dependencies on some libraries which don’t yet specify a dotnet Target Moniker or .NET Standard version. It’s essentially there for backwards compatibility until the ecosystem settles down.

Handling Dependencies

Upon making the required changes to project.json, it became apparent that some of the packages we depend on could no longer be restored. The first thing I did was to use Nuget Package Manager to update all of the dependencies to their latest versions to see if any of those had been updated to support .NET Core. This helped in some of the cases but still left me with a few that were not compatible. For the time being I commented those out as well as commenting out any code using those dependencies. In our case this included the following main libraries we had issues with…

  • XZing which was being used in one place to generate a QR code image for use within the allReady phone application. This had its own dependency on System.Drawing which is not part of .NET Core.
  • LinqToTwitter which was being used in one area of the application to query for a user’s information after a sign in via Twitter.
  • GeoCoding.net which was used in a few places in order to take an address and geo code it to latitude and longitude coordinates.

With those taken care of for now (if only commenting out code was considered “fixing”), the last remaining issue for the web project was a dependency on a separate library project in our solution which contains some shared models. This library is used by both our web application and also in some Azure web jobs we have defined. As a result it needed to support both .NET Core and the .NET Framework. The solution here was to convert it to a .NET standard library. In my case I chose the lowest standard version I could find which gave me the API surface I needed, while supporting my dependent projects. This was the .NET Standard 1.3 version. The project.json for this library was changed as follows.

Before:

After:

With this done, I also had to update our test library project.json in a similar way in order to support .NET Core by changing the frameworks section and updating to the latest versions of our dependencies as with the main web project. The changes in this file were very similar to what I’ve already shown, so I won’t repeat the code here.

Wrapping missing dependencies

With the package restore now succeeding for the web project, test project and our .NET standard project my next goal was to find solutions to the libraries we could no longer use.

XZing was an easy decision as currently the phone application project is a bit stale and it was decided that we could simply remove the QRCode endpoint and this dependency as a result.

When I reviewed the use of LinqToTwitter it was only really abstracting one API call to Twitter behind a more fluent Linq syntax. We were using it to request a Twitter user’s profile information. The option I chose here was to write our own small service to wrap the call to the Twitter REST API which using HttpClient. There are some hoops to jump through in order to establish the authentication with Twitter but this ultimately didn’t prove too difficult. I won’t go into the exact details here since this post is intended to cover the general “migration” process.

The final dependent library that wasn’t able to support .NET Core was GeoCoding.net. We were using this library to perform address to coordinate lookups via Google. On review of the usages, it was again clear that we had a whole dependency for what amounted to a single call to a Google endpoint. We were already calling another part of the Google REST API directly from a custom service class, so again, the decision was to wrap up the geocoding functionality in our own code, calling the API directly. Again, the exact details are beyond my scope here, but I may cover this in a future blog post if there’s interest.

Handling API Differences

With the problem packages removed I tried to compile the solution. I was immediately hit by a wall of errors. This was initially a little daunting, but after looking through them I could see that many were quite similar and due to changes to the .NET Core API surface.

Reflection

The first of which was where we’d been using reflection. We have a couple of places in the web app that rely on reflection for wiring up our IoC container and also in some of the test classes. In .NET Core the Object.GetType() method no longer returns full type detail information, instead it returns just the type name. In order to access the full type information there is a new extension method called GetTypeInfo(). It returns TypeInfo which is similar to what Type used to return.

Before:

After:

In this example you can see that the only change is adding in the GetTypeInfo call.

Date Formatting

We also had some code calling ToLongDateString on dates. This is no longer avaiable so I switched to formatting the date using the ToString method instead.

Before:

After:

Exceptions

In a couple of places we were throwing ApplicationException which also seems to have been removed. I switched to a general Exception in this case.

Before:

After:

Final Tweaks

Once all of the compile errors were fixed we were nearly there. In order to enable the site to be run under the non IIS mode of Kestrel I updated the profiles in launchSettings.json

Before:

After:

This new profile basically tell VS to run the project (i.e. via dotnet.exe) – launching it on port 5000. Within Visual Studio you can choose whether to start via IIS Express or directly into the project via the dotnet.exe. New projects targeting .NET core include these two profiles automatically, so updating it in our project felt sensible, to make the developer experience familiar.

Conclusion

With the above steps completed the project was now compiling, tests were running and the site worked as expected. Quite a few hours of work went into the process. The longest time was spent building the new service wrappers around the 3rd party REST APIs so that I could get rid of incompatible dependencies. The actual project and code changes weren’t too painful and it was just a case of working out the API changes so I knew how to modify our code.

It was an interesting experience and once we had it tested on a Mac and Linux during the NDC London code-a-thon it was very exciting to see it working. This has made it much easier for non-Windows developers to contribute to the allReady project and is just one of the great new benefits of working with .NET Core and ASP.NET Core.