Search code examples
asp.netasp.net-web-api2versioning

ASP.NET API versioning


I'm new to ASP.NET but I'm looking to implement some versioning for a new API I'm about to start.

I'm not even sure if what I'm looking for is possible but I'm after a very clean version method using a header variable.

Ideally I want to be able to have a versions folder within the code structure and different folders containing the different API versions within that. Each version folder would contain a full copy of the core API code so I'd know there would never be any conflicts etc. I know this would inflate the code but it's worth to keep it very clean and there would only be over 2-3 versions of the API active.

I've found many header samples on the Internet but they all require the classes to be in different namespaces and if I'm doing a complete copy of the code then it's not practical to have to rename all the classes each time they are copied.

Is what I'm trying to do possible? Or is there a cleaner solution when dealing with multiple classes?


Solution

  • There are four basic approaches to version the RESTful way -

    1. URI Path This approach takes the following form:

      http://api/v2/Tasks/{TaskId}

    2. URI Parameter This approach takes the following form:

      http://api/Tasks/{TaskId}?v=2

    3. Content Negotiation This is done in the HTTP header.

      Content Type: application/vnd.taskManagerApp.v2.param.json

    4. Request Header This is also done in the HTTP header.

      x-taskManagerApp-version: 2

    I personally like 1st approach. You can read Mike Wasson's ASP.NET Web API: Using Namespaces to Version Web APIs.

    Many people have modified Mike Wasson's Original Source. I like the one used in ASP.NET Web API 2 book by Jamie Kurtz, Brian Wortman.

    Since it has too many moving pieces, I created a sample project at GitHub.

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

    Then, you add ApiVersionConstraint

    public class ApiVersionConstraint : IHttpRouteConstraint
    {
        public ApiVersionConstraint(string allowedVersion)
        {
            AllowedVersion = allowedVersion.ToLowerInvariant();
        }
    
        public string AllowedVersion { get; private set; }
    
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
            IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            object value;
            if (values.TryGetValue(parameterName, out value) && value != null)
            {
                return AllowedVersion.Equals(value.ToString().ToLowerInvariant());
            }
            return false;
        }
    }
    

    Usage

    You just place RoutePrefix on a controller, and you are done.

    [RoutePrefix("api/{apiVersion:apiVersionConstraint(v1)}/values")]
    public class ValuesController : ApiController
    {
        // GET api/v1/values
        [Route("")]
        public IEnumerable<string> Get()
        {
            return new string[] { "v1-value1", "v1-value2" };
        }
    
        // GET api/v1/values/5
        [Route("{id}")]
        public string Get(int id)
        {
            return "v1-value-" + id;
        }
    }