How to create route constraints in ASP.NET Core

Route constraints in ASP.NET Core are used to filter out or restrict unwanted data from reaching your controller actions. For a primer on routing in ASP.NET Core, you can refer to my previous article on attribute-based routing versus convention-based routing in ASP.NET Core. This article goes beyond the basics to explore the advanced operations using route constraints.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. 

Create an ASP.NET Core MVC project in Visual Studio 2019

First off, let’s create an ASP.Net Core project in Visual Studio 2019. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new ASP.Net Core project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web Application” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  7. Click Create.
  8. In the “Create a New ASP.NET Core Web Application” window shown next, select .NET Core as the runtime and ASP.NET Core 3.1 (or later) from the drop-down list at the top.
  9. Select “Web Application (Model-View-Controller)” as the project template to create a new ASP.NET Core MVC application. 
  10. Ensure that the check boxes “Enable Docker Support” and “Configure for HTTPS” are unchecked as we won’t be using those features here.
  11. Ensure that Authentication is set to “No Authentication” as we won’t be using authentication either.
  12. Click Create.

Following these steps will create a new ASP.NET Core MVC project in Visual Studio 2019. We’ll use this project in the sections below to illustrate how we can use route constraints in ASP.NET Core 3.1.

The RouteCollection class in ASP.NET Core

The RouteTable class in ASP.NET Core contains a property named Routes which stores all routes as RouteCollection. The RouteCollection class contains some extension methods that can be used to map routes or ignore them.

MapRoute is an overloaded method that accepts constraints as a parameter. You can use this to pass your constraint to the route. The following is the declaration of the MapRoute method.

public static Route MapRoute(this RouteCollection routes, string name,
    string url, object defaults, object constraints);

The IRouteConstraint interface in ASP.NET Core

The IRouteConstraint interface is a contract that contains the declaration of only one method named Match. This interface must be extended by a class and the Match method implemented in it to check if a particular URL parameter is valid for a constraint. Here is how the IRouteConstraint interface is defined:

namespace Microsoft.AspNetCore.Routing
{
    public interface IRouteConstraint
    {
        bool Match(
            HttpContext httpContext,
            IRouter route,
            string routeKey,
            RouteValueDictionary values,
            RouteDirection routeDirection);
    }
}

The ConstraintMap dictionary in ASP.NET Core

A ConstraintMap is a dictionary that contains a list of route constraints that maps the route constraint keys to the IRouteConstraint implementations. The code snippet given below illustrates how you can add your custom constraints to this dictionary.

public void ConfigureServices(IServiceCollection services) 
{  
  services.Configure<RouteOptions>(routeOptions =>  
  { 
     routeOptions.ConstraintMap.Add("emailconstraint", typeof(EmailRouteContraint)); 
  }); 

Implement the IRouteConstraint Match method in ASP.NET Core

To create a custom route constraint, you should create a class that extends the IRouteConstraint interface and implements its Match method. The constraint can be used to thwart unwanted incoming requests and prevent a route from being matched unless a particular condition is satisfied. For example, you might want to ensure that the parameter passed to an action method is always an integer.

The Match method accepts the following parameters:

  • HttpContext – encapsulates all HTTP specific information about a request
  • IRouter – represents the router that will apply the constraints
  • RouteKey – represents the route parameter that is being validated
  • RouteDirection – an enum that contains two values, namely IncomingRequest and UrlGeneration, and is used to indicate whether the URL is being processed from the HTTP request or generating a URL
  • RouteValues – contains the URL parameters

Structure of a custom route constraint in ASP.NET Core

Here is an example of the structure of a custom route constraint:

public class CustomRouteConstraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route,
        string routeKey, RouteValueDictionary values,
        RouteDirection routeDirection)
        {
            throw new NotImplementedException();
        }
    }

Custom route constraint example in ASP.NET Core

Let us now implement a custom route constraint that can check for email Ids. First off, create a class that extends the IRouteConstraint interface and implements the Match method. The following code snippet shows a custom route constraint class named EmailRouteContraint that extends the IRouteConstraint interface.

public class EmailRouteContraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route,
        string routeKey, RouteValueDictionary values,
        RouteDirection routeDirection)
        {
            return true;
        }
    }

The following code listing shows the EmailRouteConstraint class with the Match method implemented.

public class EmailRouteContraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route,
        string routeKey, RouteValueDictionary values,
        RouteDirection routeDirection)
        {
            if (values.TryGetValue(routeKey, out var routeValue))
            {
                var parameterValueString = Convert.ToString(routeValue,
                CultureInfo.InvariantCulture);
                return IsEmailAddressValid(parameterValueString);
            }
            return false;
        }
        private bool IsEmailAddressValid(string emailAddress)
        {
            return true;
        }
    }

Note the IsEmailAddressValid method here simply returns “true.” I leave it up to you to write the necessary code for validating the email address.

Register a custom route constraint in ASP.NET Core

You should register your custom route constraint with the routing system in the ConfigureServices method of the Startup class. The following code snippet illustrates this. 

public void ConfigureServices(IServiceCollection services)
      {
          services.AddControllersWithViews();
          services.Configure<RouteOptions>(routeOptions =>
          {
              routeOptions.ConstraintMap.Add("ERC",
              typeof(EmailRouteContraint));
          });
      }

You should also configure your custom route constraint in the Configure method of the Startup class as shown in the code snippet below.

app.UseEndpoints(endpoints =>
{
     endpoints.MapControllerRoute(
         name: "default",
         constraints: new { ERC = new EmailRouteContraint() },
         pattern: "{controller=Home}/{action=Index}/{id?}");
});

And that’s it. You can now specify the constraint in your controller or your action methods and start using the application.

The ASP.NET Core runtime validates if the defined pattern and route constraints match the pattern and values of the incoming request. The validation logic of the constraint is defined inside the Match method of your custom route constraint. You can take advantage of constraints to avoid unnecessary requests as well as to validate route values before the request is passed to an action method.

How to do more in ASP.NET Core:

Posted by Contributor