7

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) ?

phuzi
  • 12,078
  • 3
  • 26
  • 50
  • This doesn't answer the OP's question directly, but I really liked this blog post on the topic because it goes deeper into various approaches to versioning an API: https://www.troyhunt.com/your-api-versioning-is-wrong-which-is/ – HeyZiko Sep 21 '16 at 17:18

3 Answers3

12

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.

Cybermaxs
  • 24,378
  • 8
  • 83
  • 112
9

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 :(

Vova Bilyachat
  • 18,765
  • 4
  • 55
  • 80
  • 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? – DSA Mar 14 '16 at 14:36
  • So what you want to do if route is there? – Vova Bilyachat Mar 14 '16 at 14:38
  • 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. – DSA Mar 14 '16 at 14:40
  • You need to check if action has route then use base class to handle that – Vova Bilyachat Mar 14 '16 at 14:41
  • do you mean we need to hard code route on base class? – DSA Mar 14 '16 at 14:45
  • 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 – Vova Bilyachat Mar 15 '16 at 09:55
  • 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); – user3140169 Feb 05 '16 at 23:33
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.

Guanxi
  • 3,103
  • 21
  • 38
  • 1
    I think the working link is https://devblogs.microsoft.com/dotnet/asp-net-web-api-using-namespaces-to-version-web-apis/ now. – Vivek Shukla Sep 20 '22 at 17:06