Aspnet-api-versioning: Api version unspecified

Created on 24 Jan 2018  路  20Comments  路  Source: microsoft/aspnet-api-versioning

I am trying to implement web api versioning. I have installed the package and this is what I have done.

This is WebApiConfig class

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            var constraintResolver = new DefaultInlineConstraintResolver
            {
                ConstraintMap =
                {
                    ["apiVersion"] = typeof(ApiVersionRouteConstraint)
                }
            };
            config.MapHttpAttributeRoutes(constraintResolver);
            config.AddApiVersioning();

            //// Web API routes
            ////config.MapHttpAttributeRoutes();

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


        }

Here is a controller

    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/profile")]
    public class ProfileController : ApiController
    {
         [Route]
        public IHttpActionResult Get(string id)
        {
              return Ok();
        }
    }

problem is when I hit this url http://localhost:51071/api/v1/profile/1
I get following error

<Error>
    <Error>
           <Code>ApiVersionUnspecified</Code>
            <Message>An API version is required, but was not specified.</Message>
   </Error>
</Error>

Am I missing something?

answered asp.net web api question

Most helpful comment

Using a browser to test or call an API isn't that uncommon. I do it all the time to test out my services. It's pretty common to use it with Swagger too. However, the API never knows or cares whether a browser is calling it or not. For an API, it seems odd that you'd have different error behaviors (e.g. returning an error response in JSON versus a HTML page).

If you're redirecting to a web page, that is almost certainly what's causing the 400. By setting AssumeDefaultVersionWhenUnspecified = true, you didn't _fix_ the problem. What you did was make all UI controllers have the API version 1.0 (which the default for options.DefaultApiVersion) and allow that to be implicitly matched. If you don't want to, or can't, split your routing, you can mark all of your UI controllers with [ApiVersionNeutral]. This will allow them to match any API version requested, including no API version at all. That also means you don't need the AssumeDefaultVersionWhenUnspecified setting.

Today, ASP.NET Core doesn't have a great way (by design) to disambiguate between UI and API controllers. If you have a lot of UI controllers, you can make a base class to handle this uniformly. For example:

```c#
[ApiVersionNeutral]
public abstract class UIController : Controller
{
protected UIController() { }
}

// this controller is now version-neutral
public class HomeController : UIController
{
}
```

Hopefully, that helps make things more _natural_.

All 20 comments

I think you should change [Route("api/v{version:apiVersion}/account")] by RoutePrefix("api/v{version:apiVersion}/account")].

Could you tell me which namespace has RouteRefix?

Sorry it's RoutePrefix...

Tried it but I get 404 not found error

_HTTP Error 404.0 - Not Found_

Do I need some sort of Startup.cs ? I don't have any startup class at this point

Are you using Asp.Net core or web Api 2 ?
Coul you upload your project?

I am using Web Api 2
I am afraid I cannot upload my project. But I could share any specific information if you want.

Would having startup.cs class help?

https://github.com/cybercop/WebApiVersioning

I have created a project, maybe you could check it and let me know what is wrong

You should change Route to [Route("{id}")] as below :

    [ApiVersion("1.0")]
    [RoutePrefix("api/v{version:apiVersion}/profile")]
    public class ProfileController : ApiController
    {
        [Route("{id}")]
        public string Get(int id)
        {
            return "Ok();";
        }

        [Route]
        public string Get()
        {
            return "yes!!!!!!!!!!!!!!!!!";
        }
    }

Hmm that's interesting.
How about when my Controller's method has query parameters?

For example

        [Route("api/profile/{profileNumber}")]
        [Route("api/profile/")]
        [HttpGet]
        public IHttpActionResult Get(string profileNumber = null, string test = null)
        {
              return Ok();
        }

or another example where I have class as parameter which get mapped from user's json request body

public IHttpActionResult Get(Profile profile)
{
      return Ok();
}

How would the route parameter look in these cases

Let me try to provide some guidance. Every issue I see here has everything to do with how routing works in ASP.NET Web API and nothing specifically to do with API versioning.

First, I highly recommend you choose one form of routing: convention-based or attribute-based. I've seen people pull their hair out when things are mixed and the routes don't work as expected. In other words, either use MapAttributeRoutes or use MapHttpRoute, but not both. It looks like you want MapAttibuteRoutes. BTW: API versioning supports both methods.

As @dawaji pointed out, you want [RoutePrefix] applied to the controller. Since you want to use URL segment API versioning, every defined route template must have the API version route constraint in it (e.g. {version:apiVersion}). Every decoration of [Route] produces a distinct route template. Only the [RoutePrefix] can be used to aggregate route templates together.

Query parameters do not play factor into routing - at all. However, if you don't have the proper null value defaults, you might see _modeling binding_ fail. It depends on the parameter type. I believe string will work if it's not explicitly made null, but types like int won't, but I might be wrong. To be sure, always assign a default value for optional query parameters. In your example, {profileNumber} is not a query parameter, it's a route parameter. If you want an optional route parameter, you have to declare it (e.g. api/profile/{profileNumber?}). In addition, if you define a route template like that, you don't need the second route template at all.

Actions that map to the HTTP GET method do not have bodies and I believe will be ignored even if you provide one. The only way I know of providing a complex object like this in a GET request is if the values come from query and/or route parameters. In order to build a model like that, you'll also need to create and register a custom ModelBinder as I don't recall any out-of-the-box support to do that for you. If you really need a body, then I suggest using POST or PUT.

@commonsensesoftware How can we solve this problem in ASP .NET Core Mvc?
There is no RoutePrefixAttribute in Core, and I have used exclusively attribute routing.
I may have understood what you are trying to say but in Core I get 400 even when i clearly specify controllers that does not exists.
My controller:

    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/links")]
    [Authorize(AuthenticationSchemes = TokenAuthDefaults.AuthenticationScheme)]
    public class LinksApiController : Controller
    {
        [HttpPost("")]
        [Produces("application/json")]
        [ProducesResponseType(200)]
        [ProducesResponseType(400)]
        public async Task<IActionResult> AddOrUpdateLink(LinkPost value)
        {
            return Ok();
        }
    }

It works fine if I make a request on http://alosi.sisteminet.it/api/v1/links, but if I try to POST the same request but on the wrong controller: http://alosi.sisteminet.it/api/v1/kaboom I get 400 ApiVersionUnspecified instead of 404, even if the "kaboom" controller does not exists.

I suspect this must have something to do with your configuration. I don't have enough information here to confirm. If you take the basic example that I provide and try to request api/v1/kaboom, you will get 404.

I solved the problem adding options.AssumeDefaultVersionWhenUnspecified = true; in the options. My initial implementation was basically similar to the basic example and still led to 400.

There must be some other part of your configuration causing this. The example works with and without the AssumeDefaultVersionWhenUnspecified settting. The type of configuration that can cause this are exception filters, SPA handlers, and status code re-execution that are meant for the MVC/UI side of things. You can use these, but they require additional configuration. To simplify things further, these features should not be enabled on API-based routes.

Well, I'm actually using UseStatusCodePagesWithReExecute, so what you are saying match the behaviour I'm seeing. Sadly my service is made to be called both programmatically and from browser, so I guess i'll stick to AssumeDefaultVersionWhenUnspecified, if that's the right thing to do.

Using a browser to test or call an API isn't that uncommon. I do it all the time to test out my services. It's pretty common to use it with Swagger too. However, the API never knows or cares whether a browser is calling it or not. For an API, it seems odd that you'd have different error behaviors (e.g. returning an error response in JSON versus a HTML page).

If you're redirecting to a web page, that is almost certainly what's causing the 400. By setting AssumeDefaultVersionWhenUnspecified = true, you didn't _fix_ the problem. What you did was make all UI controllers have the API version 1.0 (which the default for options.DefaultApiVersion) and allow that to be implicitly matched. If you don't want to, or can't, split your routing, you can mark all of your UI controllers with [ApiVersionNeutral]. This will allow them to match any API version requested, including no API version at all. That also means you don't need the AssumeDefaultVersionWhenUnspecified setting.

Today, ASP.NET Core doesn't have a great way (by design) to disambiguate between UI and API controllers. If you have a lot of UI controllers, you can make a base class to handle this uniformly. For example:

```c#
[ApiVersionNeutral]
public abstract class UIController : Controller
{
protected UIController() { }
}

// this controller is now version-neutral
public class HomeController : UIController
{
}
```

Hopefully, that helps make things more _natural_.

I wasn't talking about testing. My app is currently a reverse proxy with company logics build upon SharpReverseProxy Middleware.

The app has:

  • MVC WebApi for the backend (so requests are made programmatically)
  • Reverse proxy middlware(requests are made both ways as I can proxy through a service or a webpage)
  • UseStatusCodePagesWithReExecute since if a physical user specify a wrong proxy route from a browser request, I need to gracefully tell him that something has gone wrong.

The only controller I have is the API controller I posted before, so every requests made to /api/v1/links are currently valid for API versioning, while every other requests are requests to some reverse proxy endpoint or not found. My issue was about specifying a route that does not exists and receiving a wrong 400 instead of 404.

I understand that probably my setup may be far from the basic example due to a particular pipeline.

Well, you have your reasons. I'm not entirely sure what's causing this without seeing the configuration. You might want to review #221 and the older #146. These might provide additional ideas that are useful to you.

As I'm thinking about this, are there routes behind your proxy that aren't versioned? If that's the case, that is likely the culprit. API versioning requires an API version for all requests by default. If you're building a reverse proxy, then that seems likely. In that case, you probably do want the AssumeDefaultVersionWhenUnspecified = true because it will allow unversioned requests to pass through. If you don't own the other side of proxied requests, there is also a potential issue with incongruent API versioning methods (e.g. query string vs path vs header vs media type). You may need to support them all or consider implementing a custom IApiVersionReader.

API versioning injects itself at the end of the pipeline because the IActionSelector can be called multiple times. That means a decision cannot be made until all candidates have been considered. This only happens on the first pass for a given route. UseStatusCodePagesWithReExecute will try to do something similar by introducing retry logic when the routing result produces specific status code. Since they are both at the end, they can conflict with each other. Unfortunately, there isn't a particularly _nice_ or convenient way to order IRouter implementations. If you can't split the routes by path (which it sounds like you can't), another alternative is to manually register the routers and setup without using AddApiVersioning. That's just a convenient extension method, but you can totally register everything yourself for this type of advanced scenario. I posted a comment in #194 with an attached example that demonstrates how to do this.

@cybercop, it seems you're unblocked. Feel free to come back or reopen the issue if you're not.

@samusaran, I'm not sure if you resolved your issue. It seems you may have. If not, please open a new, separate issue.

Thanks.

Was this page helpful?
0 / 5 - 0 ratings