ASP.NET Web API: Discussing route constraints and creating custom constraints

 


Introduction

In this short discussion, we are going to understand Route Constraints in Web API. As this is a short content article, our main focus will be on the description of Route Constraints.

In the end of discussion, we will create a Custom Route Constraints

 

What are Route Constraints?

Before going to discuss about  Route Constraints let's take a look into attribute routing. In very short version routing is nothing more than just a way to make sure our Web API matches a URI to an action.

In new version of Web API, i.e.  Web API2, there is a new type of routing which is named as attribute routing.

In attribute routing , we define attributes to create a route. With the use of this, one can define the hierarchy of resources of API and its easy to handle/control.

Take a look into a few code snippets:

[Route("serverdata/{type}")] 
  
public IEnumerable<ServerData> GetServerData(int type) 
  
{ 
  
    return ServerDataRepository.GetAll().Where(d => d.Type == type); 
  
} 

 

[Route("serverdata/{zone}")] 
  
public IEnumerable<ServerData> GetServerData(string zone) 
  
{ 
  
    //soem stuff here 
  
} 

 

In this manner, we can easily define the URI pattern and manage/handle our resources.

Now, imagine you have such a complex or long URI where you can use your parameter in any type (like int or string). Refer to the following code-snippet as an example:

[Route("serverdata/{type}/datadetails")] 
  
public IEnumerable<DataDetail> GetDataDetailsByType(int type) 
  
{ 
  
    //some stuff here 
  
} 

Now, whenever a request comes, Web API tried to match URI  "serverdata/{type}/datadetails"  template defined in route. We can pass different values to make this template valid:

                - http://127.0.0.1/serverdata/external/datadetails

                - http://127.0.0.1/serverdata/1/datadetails

                - http://127.0.0.1/serverdata/1oT/datadetails

 

How it works?

In the above URI route template our {type}  is a parameter (I often called it as URI placeholder). When we pass the value it automatically assign the same to our action method's parameter.

Imagine, where we are passing "external" in URI template, our template matches but actually our parameter is  int. You can imagine, what is wrong here?

Here, we reached our main discussion. Route Constraints in Web API , this is nothing but provides a facility to restrict parameters so Web API can match correct URI route template.

In the route you just need to define a constraint with the parameter eg.  "{parameter: constraint}".

Mostly available/used constraints

Here, we try to include almost all route constraints:

1.  int  constraints - restricts to 32-bit integer value

[Route("serverdata/{typeid:int}")] 
  
public IEnumerable<ServerData> GetServerDataByType(int typeid) 
  
{ 
  
    //some stuff here 
  
} 

 

2.  double  constraints - restricts to 64-bit floating value

[Route("serverdata/{typeconst:double}")] 
public ServerData GetCalculatedDataFrequency(double typeconst) 
  
{ 
  
    //some stuff here 
  
}

 

3.  float  constraints - restricts to 32-bit floating value

[Route("serverdata/{typeconst:float}")] 
public ServerData GetCalculatedDataFrequency(float typeconst) 
  
{ 
  
    //some stuff here 
  
}

 

***Note: Try double and float constraints too


4.  guid  constraints - restricts to GUID value

[Route("serverdata/{id:guid}")] 
  
public ServerData GetServerDataById(Guid id) 
  
{ 
  
    //some stuff here 
  
}

 

5.  length  constraints - restricts string to specified length or specified length range

[Route("serverdata/{passcode:length(8)}")] 
  
public String GetDatabasename(string passcode) 
  
{ 
  
    //some stuff here 
  
}

 

[Route("serverdata/{passcode:length(4,18)}")] 
  
public String GetDatabasename(string passcode) 
  
{ 
  
    //some stuff here 
  
}

 

6. ** long  constraints** - restricts to 64-bit integer value

[Route("serverdata/{typeid:long}")] 
  
public IEnumerable<ServerData> GetServerDataByType(long typeid) 
  
{ 
  
    //some stuff here 
  
}

 

7.  alpha  constraints - restricts to upper/lower case English alphabets

[Route("serverdata/{spname:alpha}")] 
public IEnumerable<ServerData> GetServerDataByType(string spname) 
  
{ 
  
    //some stuff here 
  
} 

 

8.  bool  constraints - restricts to boolean value (true/false)

[Route("serverdata/{isDeleted:bool}")] 
public IEnumerable<Records> GetDeletedRecords(bool isDeleted) 
  
{ 
  
    //some stuff here 
  
} 

 

9.  datetime  constraints - restricts to DateTime value

[Route("serverdata/{createdon:datetime}")] 
  
public IEnumerable<Records> GetRecordsoByCreatedDate(DateTime createdon) 
  
{ 
  
    //some stuff here 
  
} 

10.  decimal  constraints - restricts to decimal value

[Route("serverdata/{amount:decimal}/employee")] 
  
public IEnumerable<EmployeeData> GetEmployeebySalary(Decimal amount) 
  
{ 
  
    //some stuff here 
  
}

               

11.  range  constraints - restricts an integer within a range of value

[Route("serverdata/{typeid:range(3,15)}")] 
public IEnumerable<EmployeeData> GetEmployeebyType(int typeid) 
  
{ 
  
    //some stuff here 
  
}

 

Apart from above there are few more like regex, maxlength, minlength. We are not going to discuss these as they are as clear as their names.

 

Creating Custom Route Constraints

There are many Route constraints available (as discussed in preceding code), but there might be scenarios where we need some special kind of constraints. To fulfill those requirements we can create our own constraints.

In the very first step for creation of  Custom Route Constraints, we need to implement interface IHttpRouteConstraint.

Taking an example of a live project where we have to restrict bad-words the following code-snippet depicts a custom constraints:

public class  NoBadWordsConstraint : IHttpRouteConstraint 
  
{ 
 
    public bool  Match(HttpRequestMessage request, IHttpRoute route, string parameterName,  
  
        IDictionary<string, object> values, HttpRouteDirection routeDirection) 
  
    { 
  
        object value;
String[] badWords = new string[] { "badone", "badtwo", "badthree" }; 
  
        if (values.TryGetValue(parameterName, out value) && value != null) 
  
        { 
  
            string stringValue; 
  
            if (value is string) 
  
            { 
                  stringValue = (string)value; 
  
                                   
foreach (string word in badWords){ if (word.Contains(stringValue )) { return false; }}

                ``return true;

  

            ``}

  

   

  

        ``}

  

        ``return false``;

  

    ``}

  

}

 

To use this, we have to tell Web API2 that it exists so, we need to register it first.

Add following lines in your  WebApiConfig  class in  Register  method:

var defaultconstraintResolver = new  DefaultInlineConstraintResolver(); 
  
defaultconstraintResolver.ConstraintMap.Add("nobadword", typeof(NoBadWordsConstraint)); 
  
config.MapHttpAttributeRoutes(defaultconstraintResolver);

 

Following is the code-snippet, showing how we can use it:

[Route("serverdata/{word:length(10)}")] 
  
public IEnumerable<EmployeeData> GetEmployeebyword(string word) 
  
{ 
  
    //some stuff here 
  
}

 

Closing notes

In this short content article, we discussed  Route constraints, types of constraints, and finally we created a custom constraint.

Other Languages

This wiki article is also available in following languages: