How can versioning be done in ASP.NET Core Web Api

11,736

Solution 1

I created a package for this purpose exactly after banging my head on this problem for a few days. It doesn't require attributes.

https://github.com/GoAheadTours/NamespaceVersioning

In summary, you can register an IApplicationModelConvention in your startup file that can iterate through controllers and register routes based on the namespaces. I created a v1 folder, and put my controller inside

The class that implements IApplicationModelConvention implements an Apply method with an ApplicationModel parameter that will have access to the Controllers in your app and their existing routes. If I see a controller does not have a route set up in my class I get the version from the namespace and use a pre-defined URL prefix to generate a route for that version.

public void Apply(ApplicationModel application) {
    foreach (var controller in application.Controllers) {
        var hasRouteAttribute = controller.Selectors.Any(x => x.AttributeRouteModel != null);
        if (hasRouteAttribute) {
            continue;
        }
        var nameSpace = controller.ControllerType.Namespace.Split('.');
        var version = nameSpace.FirstOrDefault(x => Regex.IsMatch(x, @"[v][\d*]"));
        if (string.IsNullOrEmpty(version)) {
            continue;
        }
        controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel() {
            Template = string.Format(urlTemplate, apiPrefix, version, controller.ControllerName)
        };
    }
}

I have all the code up on github and a link to the package on nuget as well

Solution 2

This is a very old question that I stumbled upon, but there are much better solutions now. There is this package

Microsoft.AspNetCore.Mvc.Versioning

Which has a much more feature rich way of implementing versioning controls. These include being able to use URL query strings, url paths, headers, or custom version readers. Being able to read the version from HTTPContext etc.

In short, you add the following into your ConfigureServices method in startup.cs

services.AddApiVersioning(o => {
    o.ReportApiVersions = true;
    o.AssumeDefaultVersionWhenUnspecified = true;
            o.DefaultApiVersion = new ApiVersion(1, 0);
});

Then you have to decorate your controllers with an ApiVersion.

[ApiVersion("1.0")]
[Route("api/home")]
public class HomeV1Controller : Controller
{
    [HttpGet]
    public string Get() => "Version 1";
}

[ApiVersion("2.0")]
[Route("api/home")]
public class HomeV2Controller : Controller
{
    [HttpGet]
    public string Get() => "Version 2";
}

You can also implement it in the path by putting it in the route.

[ApiVersion("1.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV1Controller : Controller
{
    [HttpGet]
    public string Get() => "Version 1";
}

[ApiVersion("2.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV2Controller : Controller
{
    [HttpGet]
    public string Get() => "Version 2";
}

When you go down this method of actually having it implemented via the Microsoft package, it also means that you are able to deprecate versions, have version discovery, access the version number from the HttpContext easily etc. None of which you could really do if it's just hardcoded in your route.

For more info (Including using it in a header) :

Solution 3

Use the routing attributes to control versions.

i.e.

[Route("api/v1/[controller]")]
public class BookingV1Controller : Controller
{
  ....
}

[Route("api/v2/[controller]")]
public class BookingV2Controller : Controller
{
  ....
}

For more information relating to migrating from standard Web Api and .NET Core ASP.NET have a look at: MSDN: Migrating from ASP.NET Web Api

Solution 4

For that Add service API versioning to your ASP.NET Core applications

  public void ConfigureServices( IServiceCollection services )
    {
        services.AddMvc();
        services.AddApiVersioning();

        // remaining other stuff omitted for brevity
    }

QUERYSTRING PARAMETER VERSIONING

[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
    [HttpGet]
    public string Get() => "Hello world!";
}

So this means to get 2.0 over 1.0 in another Controller with the same route, you'd go here:

/api/helloworld?api-version=2.0

we can have the same controller name with different namespaces

URL PATH SEGMENT VERSIONING

 [ApiVersion( "1.0" )]
 [Route( "api/v{version:apiVersion}/[controller]" )]
 public class HelloWorldController : Controller {
    public string Get() => "Hello world!";
 }
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
    [HttpGet]
    public string Get() => "Hello world v2!";

    [HttpGet, MapToApiVersion( "3.0" )]
    public string GetV3() => "Hello world v3!";
}

Header Versioning

  public void ConfigureServices( IServiceCollection services )
    {
        services.AddMvc();
        services.AddApiVersioning(o => o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
    }

When you do HeaderApiVersioning you won't be able to just do a GET in your browser, so I'll use Postman to add the header (or I could use Curl, or WGet, or PowerShell, or a Unit Test):

Image

please refer https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx

Share:
11,736
Shittu Joseph Olugbenga
Author by

Shittu Joseph Olugbenga

I once dreamt of becoming a medical doctor to save lives, but ever since I wrote my first BASIC code to solve a 3x3 matrices, coding has becoming an essential part of my life, giving me a new mission. Coding is what I do to calm down when tension runs high. My passion for coding keeps me coding every day ever since with the believe that I can literally change the world for the better. I am NOW a developer on a mission to make the world a better place. To achieve my mission, I try to: ➤ Be PASSIONATE about what I do. There can never be success without passion. ➤ Give extreme ATTENTION TO DETAILS. Hence, I write elegant codes that are maintainable and well thought out, considerably reducing technical debt to the barest minimum. ➤ LEARN and keep learning fast. Great leaders are good learners. My aim is to get the work done. Hence, I am willing to learn any technology, and learn from people around me as well. ➤ COACH others. I can’t change the world alone. I need to validate what I know and still impact knowledge. Some of my trainees are already full-time developers. My first paid job was a test of my passion, attention to details and creativity. I created a web application that the company used for outsourcing jobs. The application was in use both in Nigeria and in India, and it generated good revenue for the company. While my next job tested my ability to learn and adapt in an unfamiliar industry. The application I built helped curbed fraudulent activities that has been ongoing in the company, and increased the company’s monthly income by over 7 million naira. Also, I architected a SaaS application to manage a logistic ecosystem, which has increased clients’ revenue and accountability among clients’ staff members. To keep my wit as one on a mission, I found it difficult to stop coding even on weekends and during the night. My life revolves around coding. I recently reviewed a code (https://www.lenda.ng/) and the client reported a 30% increase in the performance of the application. I have been part of several entrepreneurial drives in recent years. I WANT TO MAKE THE WORLD BETTER. I strongly believe in building lasting relationship with everyone I cross path with. Most of my bosses and colleagues (both past and present) are my friends. I believe I cannot survive without people. Hence, I look forward to meeting and working with new people. There are lots to learn from others. Please reach out if our mission align. I would be happy to work with you or your organization.

Updated on June 12, 2022

Comments

  • Shittu Joseph Olugbenga
    Shittu Joseph Olugbenga almost 2 years

    In previous asp.net web api, I implement DefaultHttpControllerSelector to specify how I want the request to locate my controller. I often have different controllers with different names but intended for same processes. The only difference is that one is of higher version than the other.

    For example, I could have a controller named BookingV1Controller, which would be meant to handle the version one of the service. I would also have BookingV2Controller, which was designed to handle the version two of the service. A client application would then make a request to the service with this url http://myservice.com/api/v2/booking/someaction?id=12. To handle the request, I would provide a custom implementation of DefaultHttpControllerSelector to select the appropriate version of the controller required based on the requested version.

    However, I seems not to have a way to do this in ASP.NET Core. I have searched everywhere to no avail. No documentation that could help either.

    I would appreciate if anyone can be of help to me here. Thanks.

    UPDATE I would also like to know what to do if the version is specified in a custom header. E.g X-Version:v1

    UPDATE 2

    The requirement was that the version of the service should not be exposed in the URL. If no version is present, the service returns with instruction on how to add the version. If a requested controller is not present in the version requested, the system searches through the lower versions. If it finds it in any lower versions, it uses that. The reason for this is to prevent repetition of controllers on all versions. But with ASP.NET Core, this might not be possible.

  • Shittu Joseph Olugbenga
    Shittu Joseph Olugbenga almost 8 years
    what can be used if the version is specified in a custom header
  • toadflakz
    toadflakz almost 8 years
    Sorry, not done versioning that way as best practice is to typically let URL determine resource location.
  • Jeff
    Jeff almost 8 years
    If you do it this way don't you end up having the version in the URL twice? api/v1/BookingV1 and api/v2/BookingV2. You need to manually write the route out this way to [Route("api/v1/Booking")] and [Route("api/v2/Booking")]. Or am I wrong here? This is how I had to do it.
  • Shittu Joseph Olugbenga
    Shittu Joseph Olugbenga over 7 years
    @PeterLazziarino thanks so much will take a look. If it works, i will mark this as the correct answer. +1 upvote for the time spent digging deep into this.
  • Peter Lazzarino
    Peter Lazzarino over 7 years
    @shittujosepholugbenga thanks! I appreciate it. I have a working example so let me know if you need any further help with this.
  • andrewCanProgram
    andrewCanProgram over 7 years
    @PeterLazzarino this package saved my life. Used it in a microservice I am building with my team.
  • toadflakz
    toadflakz about 7 years
    Typically if you do this, you would use the appropriate attributes on the method as well. Class-level attributes are inherited by the class methods. If you do want to us "BookingController" as the only Controller class name, you would need to use the C# class namespace to help with the versioning as well.
  • Muhammad Rehan Saeed
    Muhammad Rehan Saeed about 7 years
    This is the correct answer. However, this package does not support ASP.NET Core 1.1. Find out more in this GitHub issue.
  • Shittu Joseph Olugbenga
    Shittu Joseph Olugbenga over 6 years
    ApiVersioning seems not to work well when Web API is combined with MVC. ApiVerionNeutral coundn't help. My MVC project also got versioned and messed thibgs up.
  • Oswald
    Oswald over 4 years
    @PeterLazzarino Link is offline