9

I have an ASP.NET MVC 4 app. I want to use the new Web API feature for learning purposes. I want to learn how to expose the same endpoint, but provide different versions of it. In other words, I want to expose endpoints like the following:

http://mysite/1.0/Products/1
http://mysite/2.0/Products/1

In an attempt to do this, I added an "Api" directory within the default "Controllers" directory. Within the "Api" directory, I have two other directories: "Version1-0" and "Version2-0". Each of those directories has an ApiController named "ProductsController".

I tried to expose the endpoints by adding the following route definition in my WebApiConfig.cs file:

config.Routes.MapHttpRoute(
  name: "1-0Api",
  routeTemplate: "api/1.0/{controller}/{id}",
  defaults: new { id = RouteParameter.Optional }
);

Unfortunately, I can't figure out how to expose actions via the URLs I listed above. What am I doing wrong? Thank you!

6 Answers 6

7

You're probably running into issues because the controllers have the same name. The controller namespace or the folder it's in doesn't matter at all to WebAPI, only the name does. The simplest thing I can think of is to rename your controllers ProductsV1Controller and ProductsV2Controller and set up two routes to point to your controllers:

config.Routes.MapHttpRoute(
    name: "1-0Api",
    routeTemplate: "api/1.0/Products/{id}",
    defaults: new { controller = "ProductsV1", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
    name: "2-0Api",
    routeTemplate: "api/2.0/Products/{id}",
    defaults: new { controller = "ProductsV2", id = RouteParameter.Optional }
);

Of course, this gets messy if you have multiple controllers you want to expose in this way. Let me see if I can't think of something better for you.

Sign up to request clarification or add additional context in comments.

6 Comments

Currently, I only have 1 controller (ProductsV1Controller), and I can't access that endpoint. What am I doing wrong? There have been no other change to the project. I literally started a new ASP.NET MVC 4 project and wanted to add a versioned controller.
Several things could be wrong. Your controller might not be a public class, the controller might not derive from ApiController, or something else. What's the response you get back from the server on a local machine?
My controller is a public class. The controller does derive from ApiContoller. On my local machine, I receive a 404. It the weirdest thing.
Is there anything in the response body? WebAPI should be adding information about what went wrong,
@YoussefMoussaoui did you ever think of a better way to do this? Or a completely different way to implement versioning of the API? I'd like to use the HTTP Accept header, as described at barelyenough.org/blog/2008/05/versioning-rest-web-services but I have to support some legacy Flex client stuff that gives little to no ability to modify HTTP headers :(
|
5

Sebastiaan Dammann has, on his blog, described how he did Web API versioning by writing his own implementation of IHttpControllerSelector and supporting interfaces.

http://damsteen.nl/blog/implementing-versioning-in-asp.net-web-api

He's also put the code on github

https://github.com/Sebazzz/SDammann.WebApi.Versioning

And packaged it in NuGet for us! :)

https://nuget.org/packages/SDammann.WebApi.Versioning

While implementing IHttpControllerSelector is certainly (imho) the Right Way to do Web API versioning, I think it would be ideal if he included the ability to version based on the HTTP Accept header (see http://barelyenough.org/blog/2008/05/versioning-rest-web-services/).

Unfortunately my client side is unable to work with the Accept header, so his RouteVersionedControllerSelector is ideal for me.

Edit: Not sure how I missed it, but there is indeed an AcceptHeaderVersionedControllerSelector that can be used to do versioning the ideal way. I'm currently using it on a new project, but it still has some drawbacks

Comments

1

Any chance you still have the default Web API route defined and it's before your custom route? That would cause your scenario to fail. The following route definitions (note the order) worked for me.

public static void Register(HttpConfiguration config) {
    config.Routes.MapHttpRoute(
        name: "1-0Api",
        routeTemplate: "api/1.0/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

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

Comments

1

Using HttpControllerSelctor Implementation Controller Versioning in Web Api 2 using URL

for more information you can check and Here

Comments

0

If we are going your 1st approach then it helps to route and allow us to get the data for V1, V2.... but now we have taken an example of v1 and v2 for one controller so routing code will be as below:

config.Routes.MapHttpRoute(
    name: "1-0Api",
    routeTemplate: "tables/v1/Products",
    defaults: new { controller = "ProductsV1", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
    name: "2-0Api",
    routeTemplate: "tables/v2/Products",
    defaults: new { controller = "ProductsV2", id = RouteParameter.Optional }
);

But we have more than 20 controllers and multiple versions then how to make it generic.

Comments

0

If you need API Versioning in ASP.NET Web API, the Asp.Versioning.WebApi (formerly Microsoft.AspNet.WebApi.Versioning) is the most straight forward and robust option. You can version your API any way that you like including:

  • By query string
  • By header
  • By URL segment
  • By media type
  • Custom
  • Any combination of the above methods

Convention-based Routing, and even ASP.NET Web API itself, were not designed to support multiple controllers with the same name. This is actually necessary for proper collation. Disambiguation comes from API versions, not controller names. To address this issue, you have several options:

  1. Use the same name, but different namespaces
    1. V1.ProductsController vs V2.ProductsController
    2. There is a provided convention for deriving API version from namespace
  2. Use the provided ControllerNameAttribute
    1. ex: [ControllerName("Products")]
  3. Rely on the build-in convention: <name><version>Controller
    1. This is defined by IControllerNameConvention
  4. Replace the default IControllerNameConvention with one of the 3 built-in methods or roll your own.

Using the OP, the entire solution is:

[ApiVersion(1.0)]
public class ProductsController : ApiController
{
    public IHttpActionResult Get(string id = default) =>
        Ok(new { message = "From V1" });
}

[ApiVersion(2.0)]
public class Products2Controller : ApiController
{
    public IHttpActionResult Get(string id = default) =>
        Ok(new { message = "From V2" });
}

Products2Controller satisfies the naming convention so it will have the controller name Products and be collated with ProductsController. ApiVersion is just metadata. Using attributes tends to be the most common way to apply version metadata, but they can also be applied by convention.

public void Configuration(HttpConfiguration configuration)
{
    // reporting api versions will return the headers
    // "api-supported-versions" and "api-deprecated-versions"
    configuration.AddApiVersioning(options => options.ReportApiVersions = true);

    // the api version can't be just 'anything'. the ApiVersionRouteConstraint
    // ensures the value well-formed long before any other processing. this
    // also lets you control where in the template the api version resides.
    // there is no 'magic string' parsing or regex. you even define if there
    // should be a leading 'v'
    configuration.Routes.MapHttpRoute(
        name: "api",
        routeTemplate: "tables/v{apiVersion}/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional },
        constraints: new { apiVersion = new ApiVersionRouteConstraint() } );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.