Routing in ASP NET Web API - For different versions of an API

15,159

Solution 1

There are many ways to implement versionning with attribute routing ; A really basic way is to use RoutePrefix attribute for each version of your ApiController

[RoutePrefix("v1")]
public class V1_ProductsController : ApiController
{
    [Route("products")]
    public IEnumerable<string> Get()
    {
        return new string[] { "v1-product1", "v1-product2" };
    }
     //....
}


[RoutePrefix("v2")]
public class V2_ProductsController : ApiController
{
     [Route("products")]
    public IEnumerable<string> Get()
    {
        return new string[] { "v2-product1", "v2-product2" };
    }
    //....
}

/v1/products goes to the first version of /v2/products goes to the second one.

Solution 2

You can do it by overriding DefaultHttpControllerSelector

there you override method to selectcontroller

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
            {
                HttpControllerDescriptor controllerDescriptor = null;
                IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();

                IHttpRouteData routeData = request.GetRouteData();

                if (routeData == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }

                object apiVersion;
                if (!routeData.Values.TryGetValue("Version", out apiVersion))
                {
                    apiVersion = "1";
                }


                object controllerName;
                if (!routeData.Values.TryGetValue("controller", out controllerName))
                {
                    controllerName = string.Empty;
                }
                if (controllerName == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }

                string newControllerName = String.Concat(controllerName.ToString(), "V", apiVersion);

                if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
                {
                    return controllerDescriptor;
                }
                if (controllers.TryGetValue(controllerName.ToString(), out controllerDescriptor))
                {
                    return controllerDescriptor;
                }
                throw new HttpResponseException(HttpStatusCode.NotFound);

            }

Then you are adding routes webapiconfig

 config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{version}/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

and register controller selector in webapiconfig

config.Services.Replace(typeof(IHttpControllerSelector), new ApiVersioningSelector(config));

So from now if you name controller ProductsV1Controller it will reffer /api/v1/products. Also please note that my example also support routes without version so if v1 is not found it will try to check if ProductsController exists

PS. Code is update one bug was there :(

Solution 3

Another simple way is configuring your route as api/{folder}/{controller}/{action} where in to folder you can give name as V1 or V2.

A good way can be implementing your own Controller selector. You can use this link for more information.

The interface that Web API uses to select a controller is IHttpControllerSelector. The important method on this interface is SelectController, which selects a controller for a given HttpRequestMessage.

Share:
15,159
now he who must not be named.
Author by

now he who must not be named.

#SOreadytohelp There is this joy in helping others. Thanks SO

Updated on June 26, 2022

Comments

  • now he who must not be named.
    now he who must not be named. almost 2 years

    I am reading about Attribute Routing in Web API 2 from here

    The article says,

    Here are some other patterns that attribute routing makes easy.
    
    API versioning
    
    In this example, “/api/v1/products” would be routed to a different controller than “/api/v2/products”.
    
    /api/v1/products
    /api/v2/products
    

    How come?

    EDIT: I would do this in Normal Routing:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
             config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/v2/products",
                defaults: new { controller = V2_Products }
            );
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/v1/products",
                defaults: new { controller = V1_Products }
            );
        }
    }
    

    Could anyone explain me how to do this in Attribute Routing way ? And how come using Attribute routing is easier and convenient for this example (according to the article) ?

  • user3140169
    user3140169 over 8 years
    Volodymyr Bilyachat answer's bug is this line string newControllerName = String.Concat(controllerName.ToString(), "V", apiVersion); fix: string newControllerName = String.Format("Controllers.{0}{1}", controllerName.ToString(), apiVersion);
  • DSA
    DSA about 8 years
    I have an issue with this method when i define custom route for each api metho. i.e. I would like to define Route for my method [Route("Product")]. Any idea on how can we define custom route along with this overriding?
  • Vova Bilyachat
    Vova Bilyachat about 8 years
    So what you want to do if route is there?
  • DSA
    DSA about 8 years
    Let's say i have two diff post method with same parameter, i have to define custom route for each. But if define route, this overriding stops working. It says, method not allowed.
  • Vova Bilyachat
    Vova Bilyachat about 8 years
    You need to check if action has route then use base class to handle that
  • DSA
    DSA about 8 years
    do you mean we need to hard code route on base class?
  • Vova Bilyachat
    Vova Bilyachat about 8 years
    No i mean that what you can do its check if your Action method has attribute route and then use base class from HttpControllerSelector to return