Giter Site home page Giter Site logo

dotnet / aspnet-api-versioning Goto Github PK

View Code? Open in Web Editor NEW
3.0K 111.0 696.0 3.71 MB

Provides a set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core.

License: MIT License

C# 99.97% HTML 0.01% Dockerfile 0.01% Shell 0.02%
aspnet aspnetcore versioning versioning-semantics odata webapi

aspnet-api-versioning's Introduction

.NET Foundation MIT License Build Status

ASP.NET API Versioning

๐Ÿ“ฃ Check out the announcement regarding upcoming changes

The "Asp" project, more formally known as ASP.NET API Versioning, gives you a powerful, but easy-to-use method for adding API versioning semantics to your new and existing REST services built with ASP.NET. The API versioning extensions define simple metadata attributes and conventions that you use to describe which API versions are implemented by your services. You don't need to learn any new routing concepts or change the way you implement your services in ASP.NET today.

The default API versioning configuration is compliant with the versioning semantics outlined by the Microsoft REST Guidelines. There are also a number of customization and extension points available to support transitioning services that may not have supported API versioning in the past or supported API versioning with semantics that are different from the Microsoft REST versioning guidelines.

The supported flavors of ASP.NET are:

  • ASP.NET Core

    Adds API versioning to your ASP.NET Core Minimal API applications

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Core MVC

    Adds API versioning to your ASP.NET Core MVC (Core) applications

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Core and OData

    Adds API versioning to your ASP.NET Core applications using OData v4.0

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Web API

    Adds API versioning to your Web API applications

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Web API and OData

    Adds API versioning to your Web API applications using OData v4.0

    NuGet Package NuGet Downloads Quick Start Examples

This is also the home of the ASP.NET API versioning API explorers that you can use to easily document your REST APIs with OpenAPI:

  • ASP.NET Core Versioned API Explorer

    Adds additional API explorer support to your ASP.NET Core applications

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Core with OData API Explorer

    Adds additional API explorer support to your ASP.NET Core applications using OData v4.0

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Web API Versioned API Explorer

    Replaces the default API explorer in your Web API applications

    NuGet Package NuGet Downloads Quick Start Examples

  • ASP.NET Web API with OData API Explorer

    Adds an API explorer to your Web API applications using OData v4.0

    NuGet Package NuGet Downloads Quick Start Examples

The client-side libraries make it simple to create API version-aware HTTP clients.

  • HTTP Client API Versioning Extensions

    Adds API versioning support to HTTP clients

    NuGet Package NuGet Downloads Quick Start

Documentation

You can find additional examples, documentation, and getting started instructions in the wiki.

Discussion

Have a general question, suggestion, or other feedback? Check out how you can contribute.

Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct.

License

This project is licensed under the MIT license.

.NET Foundation

This project is supported by the .NET Foundation.


If you are an existing user, please makes sure you review the release notes between all major and minor package releases.

aspnet-api-versioning's People

Contributors

commonsensesoftware avatar github-actions[bot] avatar marius-w-nilsen avatar rick-anderson avatar xavierjohn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aspnet-api-versioning's Issues

Asp.Net Core - Consider Changing Dependency from Microsoft.AspNetCore.Mvc to Microsoft.AspNetCore.Mvc.Core

I was updating my Asp.Net Core app from 1.0.1 to 1.1.0, and while I was at it, I was able to add API versioning. At first I was taking a dependency on the NuGet package Microsoft.AspNetCore.Mvc, but decided to move to Microsoft.AspNetCore.Mvc.Core because I only use the "Web API" part of the Mvc package.

To my dismay, when I published my work, I still found Razor, TagHelpers and more in my publish output. I tracked it down to Microsoft.AspNetCore.Mvc.Versioning taking a dependency on Microsoft.AspNetCore.Mvc (>= 1.0.0). The result is having 1.1.0 files mixed with a bunch of 1.0.0 files (not good)!

Microsoft.AspNetCore.Mvc has a dependency on Microsoft.AspNetCore.Mvc.Core, so I assume it is safe for versioning to also take a dependency on Microsoft.AspNetCore.Mvc.Core and no one will break whether they reference the full meta package for Mvc or just the Core meta package. By depending on the Core meta package, "Web Api only" projects can get versioning and stay lean at publish time. I guess it depends on if your package truly needs bits out of the full Mvc meta package.

ApiVersionRouteConstraint Always Returns False for Url Generation

The ApiVersionRouteConstraint always returns false when Match is called for URL generation. This breaks URL generation features provided by the UrlHelper and the numerous extensions built on top of it.

For example, assume a route named HelloWorld:

api/v{version:apiVersion}/helloworld

Then:

Url.Link( "HelloWorld", new { version = "1" } );

Should generate:

api/v1/helloworld

However, an exception is thrown instead.

Multiple entities in the resource path of a versioned API

We just introduced versioning to our API and nearly everything works as it did before. However, one issue continues to linger. In a few cases our resource path references more than one entity. For example, we support this:

/v1.0-beta/candidates/{candidateId}

But also support this:

/v1.0-beta/candidates/{candidateId}/resumes

Before we added versioning we were able to make this work by adding a navigation target to the candidate entity set at startup, like this:

EdmNavigationPropertyInfo resumeProperty = new EdmNavigationPropertyInfo();
resumeProperty.TargetMultiplicity = EdmMultiplicity.Many;
resumeProperty.Target = resumeEntityType;
resumeProperty.ContainsTarget = false;
resumeProperty.OnDelete = EdmOnDeleteAction.None;
resumeProperty.Name = "resumes";
candidatesEntitySet.AddNavigationTarget(candidateEntityType.AddUnidirectionalNavigation(resumeProperty), resumesEntitySet);

I've tried taking a similar approach now that we've added versioning, but it doesn't entirely work. It seems as though the desired path is recognized, but somehow it's not recognized as being part of the version, because we receive the following error each time we try to invoke it:

The HTTP resource that matches the request URI 'http://localhost:52900/v1.0-beta/candidates/10000/resumes' does not support the API version '1.0-beta'.

The function that is intended to handle that request lives in the candidates controller, which is versioned to v1.0-beta. Any requests to /candidates or /candidates/{candidateId} work as expected.

[ApiVersion("1.0-beta")]
[ControllerName("Candidates")]
[ODataRoutePrefix("candidates")]
public class CandidatesController : ODataController
...
[EnableQuery]
[ODataRoute("({candidateId})/resumes")]
public async Task<IHttpActionResult> GetResumes([FromODataUri] int candidateId)

I assume this type of resource path is still supported in a versioned API. Do I need to be doing something different to add that route so that it's correctly associated with the version?

404 is Returned Instead of 400 When API Version is Unspecified

Symptoms

When a client request is made for an API-versioned service without specifying a version, the service returns HTTP status code 404 (Not Found) instead of HTTP status code 400 (Bad Request).

Assuming that a single service named svc is defined with a single version - 1.0, then the following is the expected behavior:

Request URL Status Code
GET /svc 400
GET /svc?api-version=1.0 200
GET /svc?api-version=2.0 400

However, the following is the actual observed behavior:

Request URL Status Code
GET /svc 404
GET /svc?api-version=1.0 200
GET /svc?api-version=2.0 400

Analysis

API-versioned services require that a client request a version by default. When a client makes a request for an existing service without an API version, then the behavior should be the same as if the client requested a version that does not exist. The expected HTTP status code in the response for this scenario is 400.

This behavior occurs because there is no branching logic that handles when an API version has not been specified and it is required, which results in a 404 response. Controller and actions selectors should handle this condition and return 400 when a route could match, but an API version has not been provided.

Return 405 Method Not Allowed for requests that do not support the http method

If an endpoint only supports specific http methods (e.g. POST), and the requested method is not supported (e.g. GET), return status 405 Method Not Allowed with a message similar to The requested resource does not support http method 'GET'.

Example
Api supports: POST to \customers
Request is for: GET to \customers

Current response is 400 Bad Request

{
  "Error": {
    "$type": "system._web._http._http_error",
    "Code": "UnsupportedApiVersion",
    "Message": "The HTTP resource that matches the request URI 'http://localhost/api/v1/customers' does not support the API version '1'.",
    "InnerError": {
      "$type": "system._web._http._http_error",
      "Message": "No route providing a controller name with API version '1' was found to match request URI 'http://localhost/api/v1/customers'."
    }
  }
}

The endpoint does exist for version 1, but the method is incorrect. The current error is a bit misleading for the user.

Running API versioning with latest ASP.NET Core 1.1.0 preview and full DotNet Framework 4.6.1.

I know this is a stupid question, and probably just a RTFM situation ... but I can not seem to get this to work using the latest (pre)release versions of the frameworks. I am testing a freshly created project and may have an update soon, but any direction pointing would be appreciative.

Is this even possible (I thought I had no problem referencing other libraries from my code) and where am I going wrong?

I am working on a small demo project from a cleaner solution ... so I will see what happens and try to resolve it directly.

Update ... I can get the test project to run using "1.1.0-preview1-final". What does not run is the MVC Controllers (& Views) that I have set up. All controller/actions return a 404 ... and ... "Microsoft.AspNetCore.Mvc.Versioning.ApiVersionActionSelector: Information: Request did not specify a service API version, but multiple candidate actions were found..." So I'll play with the configuration somewhat.

All the best.

Support Major-Only Version Scheme

According to the Microsoft REST API guidelines for versioning, a major-only version scheme should be supported. If a client only provides the API version as "1", then the minor version should implicitly be inferred as ".0" or zero. In other words, the API versions 1 and 1.0 should be equivalent. The equality and comparison methods for the ApiVersion class currently treat them as being different.

Examples

GET /foo?api-version=1 HTTP/1.1
host: localhost
GET /foo?api-version=1.0 HTTP/1.1
host: localhost
GET /foo HTTP/1.1
host: localhost
api-version: 1
GET /foo HTTP/1.1
host: localhost
api-version: 1.0

All of these requests should be treated as semantically equivalent requests.

URL Segment Versioned Controllers Do Not Report All API Versions

When Web API controllers are versioned using a URL segment and the API versioning options are configured to report the supported and deprecated API versions, the complete set of API versions is not correctly reported.

For example:

[ApiVersion( "1.0" )]
[RoutePrefix( "api/v{version:apiVersion}/my" )]
public class MyController : ApiController
{
   [Route]
   public IHttpActionResult Get() => Ok();
}
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[RoutePrefix( "api/v{version:apiVersion}/my" )]
public class My2Controller : ApiController
{
   [Route]
   public IHttpActionResult Get() => Ok();
}
Request Expected Result Actual Result
GET /api/v1/my api-supported-versions: 1.0, 2.0, 3.0 api-supported-versions: 1.0
GET /api/v2/my api-supported-versions: 1.0, 2.0, 3.0 api-supported-versions: 2.0, 3.0
GET /api/v3/my api-supported-versions: 1.0, 2.0, 3.0 api-supported-versions: 2.0, 3.0

MapVersionedODataRoute or MapVersionedODataRoutes always throws an exception

I've been trying to add versioning to our OData API for the last couple of days but despite working off the samples here I find that my call to MapVersionedODataRoute or MapVersionedODataRoutes (depending on which sample I based my code on) inevitably results in the following exception:

System.MissingMethodException was unhandled by user code
  HResult=-2146233069
  Message=Method not found: 'Void System.Web.OData.Routing.ODataPathRouteConstraint..ctor(System.Web.OData.Routing.IODataPathHandler, Microsoft.OData.Edm.IEdmModel, System.String, System.Collections.Generic.IEnumerable'1<System.Web.OData.Routing.Conventions.IODataRoutingConvention>)'.
  Source=Microsoft.AspNet.OData.Versioning

It's been difficult to troubleshoot as I can't tell what method it can't find. In case it matters, the stack trace is basically identical in each case, aside from the name of the method that I invoked in my code.

StackTrace:
       at System.Web.Http.HttpConfigurationExtensions.MapVersionedODataRoutes(HttpConfiguration configuration, String routeName, String routePrefix, IEnumerable'1 models, IODataPathHandler pathHandler, IEnumerable'1 routingConventions, ODataBatchHandler batchHandler)
       at System.Web.Http.HttpConfigurationExtensions.MapVersionedODataRoutes(HttpConfiguration configuration, String routeName, String routePrefix, IEnumerable'1 models, ODataBatchHandler batchHandler)
       at VersionedAPI.Startup.Configuration(IAppBuilder appBuilder)

or

StackTrace:
       at System.Web.Http.HttpConfigurationExtensions.MapVersionedODataRoute(HttpConfiguration configuration, String routeName, String routePrefix, IEdmModel model, ApiVersion apiVersion, IODataPathHandler pathHandler, IEnumerable'1 routingConventions, ODataBatchHandler batchHandler)
       at System.Web.Http.HttpConfigurationExtensions.MapVersionedODataRoute(HttpConfiguration configuration, String routeName, String routePrefix, IEdmModel model, ApiVersion apiVersion)
       at VersionedAPI.Startup.Configuration(IAppBuilder appBuilder)

Do I possibly need to include another package? Is there a bug in the current release?

Web API Help Page - method paths and controllers grouping

Hi, let's assume that i have two versions of HiController. I'm versioning my services by using URL path. In the ApiGroup.cshtml file i'm replacing {version} with the value of ApiVersionAttribute in order to get paths like these: GET api/framework/{version}/hello -> GET api/framework/1/hello. In the bellow configuration i'm expecting to get three paths but instead i'm getting only two. Is this a bug or misconfiguration? What's more, do you know how to group HiController and Hi2Controller in the one group "Hi"?

image

Controllers:

    [ApiVersion("1", Deprecated = true)]
    public class HiController : ApiController
    {
        [HttpGet]
        [Route("api/framework/{version:apiVersion}/hello")]
        public string Hi(){ /* ommitted */ }
    }

    [ApiVersion("2")]
    public class Hi2Controller : ApiController
    {
        [HttpGet]
        [Route("api/framework/hello")]
        [Route("api/framework/{version:apiVersion}/hello")]
        public string Hi() { /* ommitted */ }
    }

WebApiConfig

 public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            config.AddApiVersioning(options =>
            {
                options.ReportApiVersions = true;
                options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
                options.AssumeDefaultVersionWhenUnspecified = true;
            });

            var constraintResolver = new DefaultInlineConstraintResolver()
            {
                ConstraintMap =
                {
                    ["apiVersion"] = typeof( ApiVersionRouteConstraint )
                }
            };
            config.MapHttpAttributeRoutes(constraintResolver);

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

ApiGroup.cshtml

<table class="help-page-table">
    <thead>
        <tr><th>API</th><th>Description</th></tr>
    </thead>
    <tbody>
        @foreach (var api in Model.OrderByDescending(o => o.RelativePath))
        {
            if (api.RelativePath.Contains("{version}"))
            {
                var version = api.ActionDescriptor.ControllerDescriptor.GetDeclaredApiVersions().FirstOrDefault();
                if (version != null)
                {
                    string versionNumber;
                    if (version.MinorVersion != null && version.MinorVersion != 0)
                    {
                        versionNumber = string.Format(version.MajorVersion + "." + version.MinorVersion);
                    }
                    else
                    {
                        versionNumber = version.MajorVersion.ToString();
                    }

                    api.RelativePath = api.RelativePath.Replace("{version}", versionNumber);
                }
            }
            <tr>
                <td class="api-name"><a href="@Url.Action("Api", "Help", new { apiId = api.GetFriendlyId() })">@api.HttpMethod.Method @api.RelativePath</a></td>
                <td class="api-documentation">
                    @if (api.Documentation != null)
                {
                        <p>@api.Documentation</p>
                    }
                    else
                    {
                        <p>No documentation available.</p>
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

By namespace + RoutePrefix + AssumeDefaultVersionWhenUnspecified not working

TL;DR: It seems the the AssumeDefaultVersionWhenUnspecified is not working with RoutePrefix and ByNamespace versioning. It might be too complex for the routing system to handle this combinations but would be definitly great if it is supported.

Long Version:
Consider the ByNamespaceWebApiSample project as a starting point and let us combine it with attribute routing:

namespace Microsoft.Examples.V1.Controllers
{
    [ApiVersion( "1.0" )]
    [RoutePrefix( "v{version:apiVersion}/agreements" )]
    public class AgreementsController : ApiController { [Route("{accountId}")] public IHttpActionResult Get( string accountId ) ... }
}
namespace Microsoft.Examples.V2.Controllers
{
    [ApiVersion( "2.0" )]
    [RoutePrefix( "v{version:apiVersion}/agreements" )]
    public class AgreementsController : ApiController { [Route("{accountId}")] public IHttpActionResult Get( string accountId ) ... }
}
namespace Microsoft.Examples.V3.Controllers
{
    [ApiVersion( "3.0" )]
    [RoutePrefix( "v{version:apiVersion}/agreements" )]
    public class AgreementsController : ApiController { [Route("{accountId}")] public IHttpActionResult Get( string accountId ) ... }
}

And we change the Startup do use the attributes instead of fixed routes:

var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) } };
var configuration = new HttpConfiguration();
var httpServer = new HttpServer( configuration );
configuration.AddApiVersioning(o =>
{
    o.ReportApiVersions = true;
    o.DefaultApiVersion = new ApiVersion(3, 0);
    o.AssumeDefaultVersionWhenUnspecified = true;
} );
configuration.MapHttpAttributeRoutes(constraintResolver);
builder.UseWebApi( httpServer );

Then it is possible to call the different versions via the explicit version:

  • v1/agreements/4711 -> {"Controller":"Microsoft.Examples.V1.Controllers.AgreementsController","AccountId":"test","ApiVersion":"1"}
  • v1.0/agreements/4711 -> {"Controller":"Microsoft.Examples.V1.Controllers.AgreementsController","AccountId":"test","ApiVersion":"1.0"}
  • v2/agreements/4711 -> {"Controller":"Microsoft.Examples.V2.Controllers.AgreementsController","AccountId":"test","ApiVersion":"2"}
  • v2.0/agreements/4711 -> {"Controller":"Microsoft.Examples.V2.Controllers.AgreementsController","AccountId":"test","ApiVersion":"2.0"}
  • v3/agreements/4711 -> {"Controller":"Microsoft.Examples.V3.Controllers.AgreementsController","AccountId":"test","ApiVersion":"3"}
  • v3.0/agreements/4711 -> {"Controller":"Microsoft.Examples.V3.Controllers.AgreementsController","AccountId":"test","ApiVersion":"3.0"}

But if you try to call the latest version by removing the version, you only get an error 404:

  • agreements/4711 -> HTTP Error 404.0 - Not Found instead of {"Controller":"Microsoft.Examples.V3.Controllers.AgreementsController","AccountId":"test","ApiVersion":"3.0"}

Is there a way to have this combination? From code perspective the by namespace is simply the cleanest way to versionize your API. We also prefer the url segment based versioning due to
various reasons (documentation with swagger and swagger UI, browser/html-form compatibility,...). But still we want to provide also a default endpoint for the current version.

Versioning using Accept HTTP Header

There are three main versioning strategies that people use (See link). I don't see versioning by the Accept HTTP header listed as being supported on the Wiki page:

Accept: application/vnd.mycompany.myapp-v2+xml

Add support for PUT and DELETE methods

Hi,

I am getting an error UnsupportedApiVersion

"{
"Error": {
"Code": "UnsupportedApiVersion",
"Message": "The HTTP resource that matches the request URI 'http://localhost:18030/api/v1/resource/1' does not support the API version '1'."
}
}"<

GET and POST methods seems to be working fine.

I tried to search thru the samples available but didn't find any PUT examples.
Similar error occurs when trying DELETE method

Thanks,
Oleg

Add Support for API Explorer

Overview

Add out-of-the-box support for the API Explorer. The implementation should enable enumerating controllers and their associated actions, which are grouped by API version .

Requirements

  • Enable enumeration of API-versioned controllers and actions
  • Simplify documenting API-versioned services using Swagger
  • Simplify documenting API-versioned services using Swashbuckle
  • [Optional] Extend support to ASP.NET Web API Help Pages

Tasks

  • Implement IApiExplorer for ASP.NET Web API
  • Implement IApiExplorer for ASP.NET Core
  • Implement IApiExplorer for ASP.NET Web API with OData

API Version 400 Responses are Not Compliant by Default

Although failure to match an API version does return HTTP status code 400 (Bad Request), the error response format is not always compliant with the structure defined in the error condition responses defined in the Microsoft REST guidelines.

The default response format should be compliant by default. There should also be a means to alter the response format for service authors that are not concerned with compliance.

OData Entity Key Value Segment Unmatched with Custom Route Parameter Name

Symptoms

The default route parameter name of OData entity key values is key; however, a service author may specify an alternate parameter name.

Consider the following OData service:

[ApiVersion( "1.0" )]
[ControllerName( "Candidates" )]
[ODataRoutePrefix( "Candidates" )]
public class CandidatesController : ODataController
{
    public IHttpActionResult Get() =>
        Ok( new[] { new Candidate() { Id = 1, Name = "John Doe" } } );

    [ODataRoute( "({key})" )]
    public IHttpActionResult Get( int key ) =>
        Ok( new Candidate() { Id = key, Name = "Jane Doe" } );

    [ODataRoute( "({key})/Resumes" )]
    public IHttpActionResult GetResumes( int key ) =>
        Ok( new[] { new Resume() { Id = 42, Name = "Resume ABC" } } );
}

This controller will route with or without API versioning applied. If the service is changed to use a key value parameter name of id instead of key, the route continues to work using standard, unversioned OData routes, but not with routes that use OData API versioning.

[ApiVersion( "1.0" )]
[ControllerName( "Candidates" )]
[ODataRoutePrefix( "Candidates" )]
public class CandidatesController : ODataController
{
    public IHttpActionResult Get() =>
        Ok( new[] { new Candidate() { Id = 1, Name = "John Doe" } } );

    [ODataRoute( "({id})" )]
    public IHttpActionResult Get( int id ) =>
        Ok( new Candidate() { Id = id, Name = "Jane Doe" } );

    [ODataRoute( "({id})/Resumes" )]
    public IHttpActionResult GetResumes( int id ) =>
        Ok( new[] { new Resume() { Id = 42, Name = "Resume ABC" } } );
}

Cause

Parameters bound to route values are updated in the ODataPathRouteConstraint during URI resolution. The extended VersionedODataPathRouteConstraint does not call the base implementation, which prevents custom route parameter key tokens from being remapped. The net result is that actions on OData services with key value segments other than key no longer route properly.

ASP.Net Core: internal routing fails with CreatedAtRoute

I detected a problem with the routing, if you want to create an object via post and then return a CreatedAtRoute result. It always returns an error 500: "No route matches the supplied values"

If i remove the package (aspnet-api-versioning), and rename the route of the controller to /api/v1 in a fixed way, it works.

The provided samples in this repository are not (unfortunately ) helping in this case, because there are or only 'GET' request samples.

Can you help me? If you need more informations, i would be happy to send you some more.

Here's my code:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[Controller]")]
public class DocumentController : Controller
{

[HttpGet("{guid}", Name = "GetDocument")]
public IActionResult GetByGuid(string guid)
{
    var doc = DocumentDataProvider.Find(guid);
    if (doc == null)
        return NotFound();

    return new ObjectResult(doc) {StatusCode = 200};
}


[HttpPost]
public IActionResult Create([FromBody] Document doc)
{
    //... Creating Doc

     var newguid = newDoc.Guid.ToString("N");

    //Does not work
   val = CreatedAtRoute("GetDocument", new{ controller =  "document", guid = newguid, version = HttpContext.GetRequestedApiVersion().ToString() }}, newDoc);

    return val;
}
}

ASP.NET Core Controller Should Return 400 Instead of 404

When an ASP.NET Core route that also uses URL segment API versioning could be matched, but no route matches the specified the API version, then the controller should return 400 instead of 404.

For example:

/v1/orders

This route exists and returns 200.

/v2/orders

This route does not exist, but the route ~/v{version}/orders could exist. The response in this case should be 400 and not 404.

error with bynamespacewebapi sample

run the sample. point the url to
http://localhost:1676/v1/agreements

got this error:

UnsupportedApiVersion The HTTP resource that matches the request URI 'http://localhost:1676/v1/agreements' does not support the API version '1'. No route providing a controller name with API version '1' was found to match request URI 'http://localhost:1676/v1/agreements'.

what am I mssing?

thanks

Cleaner minor versioning

I was wondering if a more cleaner approach for minor versioning should/will be included, because if you want to add/remove several actions to a minor version of a controller for you have to clutter up the controller with attributes and the more minor version introduce the harder it gets to maintain it.

For example, lets say there is a ValuesController that has in version 2 only 1 action but has been expanded in minor versions.

[ApiVersion("2")]
[Route("api/[controller]")]
public class ValuesController : ApiController
{
 // introduced at version 2.0
[HttpGet]
 public IEnumerable<string> Get() =>  new[]{ "value1", "value2", "value3"};

 // introduced at version 2.1
[HttpGet]
 [Route("{category}")]
 public IEnumerable<string> Get(string category) => new[]{ "value2", "value3"};

 // introduced at version 2.1, dropped at version 2.2
[HttpGet]
 [Route("{category}/{subcategory}/{id}")]
 public IEnumerable<string> Get(string category, string subcategory, int id) => new[]{"value3"};
}
 // introduced at version 2.2
[HttpGet]
 [Route("{category}/{subcategory}")]
 public IEnumerable<string> Get(string category, string subcategory) => new[]{"value3"};
}

Now the following goals are trying to be achieved:

  • Version 2 should only be able to invoke Get()
  • Version 2.1 should be able to invoke Get(), Get(category), Get(category, subcategory, id)
  • Version 2.1 should be able to invoke Get(), Get(category), Get(category, subcategory) , but not anymore Get(category, subcategory, id)

Now to get this to work your controller will look as followed

[ApiVersion("2"), ApiVersion("2.1"), ApiVersion("2.2")]
[Route("api/[controller]")]
public class ValuesController : ApiController
{
 // introduced at version 2.0
[HttpGet]
 public IEnumerable<string> Get() =>  new[]{ "value1", "value2", "value3"};

 // introduced at version 2.1
[HttpGet]
[MapToApiVersion("2.1"), MapToApiVersion("2.2")]
 [Route("{category}")]
 public IEnumerable<string> Get(string category) => new[]{ "value2", "value3"};

 // introduced at version 2.1, dropped at version 2.2
[HttpGet]
[MapToApiVersion("2.1")]
 [Route("{category}/{subcategory}/{id}")]
 public IEnumerable<string> Get(string category, string subcategory, int id) => new[]{"value3"};
}
 // introduced at version 2.2
[HttpGet]
[MapToApiVersion("2.2")]
 [Route("{category}/{subcategory}")]
 public IEnumerable<string> Get(string category, string subcategory) => new[]{"value3"};
}

While this works and is still clear to maintain with only a few minor versions, this becomes a hassle and polluted with a alot of attributes when you have a lot of minor versions or when it is combined with several major/minor versions in one controller.
This is because you add an ApiVersion attribute to the controller for every minor version supported and you must add MapToApiVersion to the new actions and to any excisting MapToApiVersion unless you drop it. (Just image if you have a 2.12 and every version had an action or more added)

It would be nice if you only had to add the major ApiVersion attribute to the controller and for minor versions you only had to put an attribute at the actions when it was added/dropped/obsolete like for exampe with an attribute SinceApiVersion("2.1"), DroppedSinceVersion() or ObsoleteSinceVersion().

Ability to configure Conventions in a non-generic way

Maybe I'm looking from the wrong angle, so I'll describe my scenario first.
I have a wide set of API controllers for different versions (e.g. v1, v2, v3).
What I'm trying to achieve is configure versioning depending on namespace/typename/whatever.
Expected configuration code might look like this:

foreach (var controllerType in controllerTypes)
{
    if (controllerType.Name.Contains("v1"))
    {
        options.Conventions.Controller(controllerType).HasApiVersion(new ApiVersion(1, 0));
    }
}

However I've found no approach except configuring each controller one by one (either via conventions or attributes).
Are there any elegant solution for this?

Add support for header-based versioning

Is there a plan to support header based api versioning? There are some folks out there that prefer to use the headers to provide the version information mainly for 2 reasons (see Troy Hunt post ):

  • the URL to the resource should not change
  • headers are already used for similar purposes, as describing how the data should be returned

Some months ago, I've created my own versioning system taking inspiration by MVC Versioning WebSite and essentially adding the support for header-based versioning (with a HeaderVersionRangeValidator) which is working pretty well (I've added also with Swagger support).
Since it would be nice to use a standardized facility for this, the one that you're building here, I was wandering if there are plans in this direction that will allow me to replace my custom code.

Provide the ability to return status codes other than 400 when responding to a request for an unsupported version

I have recently added this package to my Asp.Net core Api and am very impressed with the functionality and ease of use so far.

One small thing that I would like to be able to do would be to return something other than BadRequestObjectResult when a request asks for an unsupported version for a particular resource. The reason for this is that by convention we always return HTTP status 200 if the request is handled in our api. Details of success or failure and any other results are returned in the message body. With this approach it is easy to identify whether responses have come from our Api code or from a web server or other infrastructure.

Currently we can set the ApiVersioningOptions.CreateBadRequest delegate so we can send our normal response, however this can only be a BadRequestObjectResult, which will generate a response with HTTP status code 400. Ideally we would like to be able to use an ObjectResult, so the response has a status code 200.

Looking at the code, this looks like a simple case of changing the return type of CreateBadRequestDelegate to ObjectResult (as well as some renaming so it still makes sense with other return types!). This should be a backwards compatible change as ObjectResult is the base class of BadRequestObjectResult??

thanks

Bill

HEAD requests result in 400 somewhat unexpectedly

First, thanks for the fast turn around (#63). Had one more question pop up as a result of the quick testing I just did:

Should HEAD requests against an endpoint return 400s?

curl -I sends a HEAD request

curl -i localhost:9080/info?api-version=1
HTTP/1.1 200 OK
Date: Mon, 28 Nov 2016 22:09:58 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

{"hostname":"pixel"}
curl -I localhost:9080/info?api-version=1.0
HTTP/1.1 400 Bad Request
Date: Mon, 28 Nov 2016 22:10:12 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel

Advice on integrating swagger via Swashbuckle

I'm curious if there has been any attempts at connecting controllers that use this versioning scheme with Swagger/Swashbuckle? I thought I'd ask here since I can see this being used often in conjunction with those kinds of libraries.

I think I've got the version resolution sorted. Does this logic look correct for determining which actions to include per API version? or is there a better way....

static bool ResolveVersionSupportByApiVersionAttributes(ApiDescription apiDescription, string targetApiVersion)
{
  if (apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiVersionNeutralAttribute>(true).Any())
    return true; // always include neutrally marked controllers

  var controllerVersionAttributes = apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiVersionAttribute>(true);
  if (!controllerVersionAttributes.Any())
    return true; // include when no attributes are defined

  if (targetApiVersion.StartsWith("v"))
    targetApiVersion = targetApiVersion.Substring(1); // remove the leading "v" in `v{x.x}`

  var apiVersion = ApiVersion.Parse(targetApiVersion);

  var controllerApiVersion = controllerVersionAttributes
    .Where(x => x.Versions.Contains(apiVersion))
    .FirstOrDefault();

  // has a compatible version, now check the action for [MapToApiVersion]
  if (controllerApiVersion != null)
  {
    var actionMapToAttributes = apiDescription.ActionDescriptor.GetCustomAttributes<MapToApiVersionAttribute>(true);
    if (!actionMapToAttributes.Any())
      return true; // no MapTo attributes matched, then include the action

    if (actionMapToAttributes.Any(x => x.Versions.Contains(apiVersion)))
      return true; // include mapped action
  }

  return false;
}

but I wanted to get a list of versions discovered in the assembly, rather than manually defining them. Should I also loop over all the ControllerDescriptors here as well, or is there a "discovered versions" registry somewhere (I couldn't find one.)

GlobalConfiguration.Configuration
  .EnableSwagger(c => {
    c.MultipleApiVersions(
      ResolveVersionSupportByApiVersionAttributes,
      (versionInfoBuilder) => {
        
        // unsure how to get all the supported versions that were discovered here
        versionInfoBuilder.Version("2.0", "Swashbuckle Dummy API v2.0");
        versionInfoBuilder.Version("1.0", "Swashbuckle Dummy API v1.0");
        versionInfoBuilder.Version("1.0-alpha", "Swashbuckle Dummy API v1.0-alpha");
        
      }
    );

    c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c)
      .Configure(odata => odata.IncludeNavigationProperties())
    );
  })
  .EnableSwaggerUi(c => {
    c.EnableDiscoveryUrlSelector();
  });

Appreciate your thoughts here :)

PS: I haven't quite got a working version with config.MapVersionedODataRoutes("odata", "odata", models);, but it works happily if I use the built in config.MapODataServiceRoute("odata", "odata", models.First()) and pass in the first model only.

Add support for extending ApiControllerSelector

I have the case where I want to extend the existing ApiControllerSelector to consider an additional custom route constraint.

I have the following controllers:

  • V1
    • Tenant1
      • ValuesController.cs
    • Tenant2
      • ValuesController.cs

Which should get me the following routes:

  • api/v1/Tenant1/Values
  • api/v1/Tenant2/Values

Similar to the existing Selector I want to match the requested Tenant based on the attribute(s) on the controller.

[ApiVersion("1.0")]
[Tenant("Tenant1")]
[Route("v{version:apiVersion}/{tenant:tenant}/Values")]
public class ValuesController : ApiController { }

Currently it is extremly difficult to extend the ApiControllerSelector as most of the selection logic is internal. It would already be extremely helpful, if one could register a handler to resolve ambigious versions/controllers.

API Versioned Controller Action By Convention Is Not Always Matched

Symptoms

When API versioning is applied using conventions, controller action methods are matched using expressions. The defined conventions are occasionally not matched to the corresponding controller action during runtime execution. This behavior has also manifested as flaky tests in the test coverage. Thus far, the behavior is only exhibited in ASP.NET Core.

services.AddApiVersioning( options =>
 {
     options.Conventions.Controller<ValuesController>()
                        .HasApiVersion( 1, 0 )
                        .HasApiVersion( 2, 0 )
                        .Action( c => c.GetV2() ).MapToApiVersion( 2, 0 )
                        .Action( c => c.GetV2( default( int ) ) ).MapToApiVersion( 2, 0 );
 } );

Figure 1: API versioning using conventions with a method expression

Analysis

API version conventions are matched to a controller action method by using the hash code of the MethodInfo resolved from the developer-defined method expression for the action. At runtime, ASP.NET Web API or Core will provide a controller action who is bound to a method. The hash code of this method is used to look up the conventions. Although the two methods are semantically equivalent, the hash code does not always match. Oddly, the comparison of the two MethodInfo instances using equality appears to always return the correct behavior, even though the hash codes may be different.

Convention-Based Action Routes Do Not Return 400 as Expected

Controllers that use convention-based routing do not always return 400 for actions that match in some API versions, but not others.

For example:

[ApiVersion( "1.0" )]
public class MyController : ApiController
{
   public IHttpActionResult Get() => Ok();
}
[ApiVersion( "2.0" )]
public class My2Controller : ApiController
{
   public IHttpActionResult Get() => Ok();
   public IHttpActionResult Patch() => StatusCode( HttpStatusCode.NoContent );
}
[ApiVersion( "3.0" )]
public class My3Controller : ApiController
{
   public IHttpActionResult Get() => Ok();
}

The requested routes should produce the following results:

Request Expected Result Actual Result
PATCH /my?api-version=1.0 400 404
PATCH /my?api-version=2.0 204 204
PATCH /my?api-version=3.0 400 404

InvalidCastException When Attribute-Routed Controllers Have the Same Name

Synopsis

When versioned controllers that use attribute routing with the same name, but in different namespaces, an InvalidCastException is thrown.

For example:

namespace Example.V1
{
    [ApiVersion( "1.0" )]
    [Route( "api/helloworld" )]
    public class HelloWorldController : ApiController { /* omitted */ }
}
namespace Example.V2
{
    [ApiVersion( "2.0" )]
    [Route( "api/helloworld" )]
    public class HelloWorldController : ApiController { /* omitted */ }
}
namespace Example.V3
{
    [ApiVersion( "3.0" )]
    [Route( "api/helloworld" )]
    public class HelloWorldController : ApiController { /* omitted */ }
}

Analysis

This behavior occurs because although the correct controller type is selected, the action binding is paired to a method from the incorrect candidate controller. For example, api/helloworld?api-version=3.0 selects Example.V3.HelloWorldController, but binds the action to Example.V1.HelloWorldController.Get. This behavior only appears to manifest for attribute-routed controllers and only if the controllers also have the same name.

CurrentImplementationApiVersionSelector while versioning by URL path

Is the CurrentImplementationApiVersionSelector working while using URL path versioning? In case of header versioning everything works fine but in case of URL path versionig always the first version(1.0) of the controller is selected even if the another version(2.0) is specified as DefaultApiVersion.

Controllers:

[ApiVersion("1.0", Deprecated = true)]
[Route("api/{version:apiVersion}/Uri")]
public class UriController : ApiController
{
    [HttpGet]
    public string Get()
    {
        return String.Format("Hello from Uri 1.0");
    }
}   

[ApiVersion("2.0")]
[Route("api/{version:apiVersion}/Uri")]
public class Uri2Controller : ApiController
{
    [HttpGet]
    public string Get()
    {
        return String.Format("Hello from Uri 2.0");
    }
}

[ApiVersion("3.0")]
[Route("api/{version:apiVersion}/Uri")]
public class Uri3Controller : ApiController
{
    [HttpGet]
    public string Get()
    {
        return String.Format("Hello from Uri 3.0");
    }
}

WebApiConfig:

        config.AddApiVersioning(options =>
        {
            options.ReportApiVersions = true;
            options.DefaultApiVersion = new ApiVersion(2, 0);
            options.AssumeDefaultVersionWhenUnspecified = true;
            options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
            
        });

        var constraintResolver = new DefaultInlineConstraintResolver()
        {
            ConstraintMap =
            {
                ["apiVersion"] = typeof( ApiVersionRouteConstraint )
            }
        };
        config.MapHttpAttributeRoutes(constraintResolver);

image

Behavior should be the same when ?api-version is missing, as when it is incorrect.

Setup:

I have an InfoController that looks like this:

using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Api.ContractsV1;

namespace Api.Controllers {
    [ApiVersion("1.0")]
    [Route("/info")]
    public class InfoController {
        public InfoController(){}

        [HttpGet()]
        public InfoContract Index() {
            return new InfoContract{
                Hostname = Environment.MachineName,
            };
        }
    }
}

Here's observed queries, through nginx:

127.0.0.1 - - [25/Nov/2016:21:08:16 +0000] "GET /info HTTP/1.1" 404 0 "-" "curl/7.51.0"
127.0.0.1 - - [25/Nov/2016:21:09:04 +0000] "GET /info?api-version=1.0 HTTP/1.1" 200 31 "-" "curl/7.51.0"
127.0.0.1 - - [25/Nov/2016:21:09:07 +0000] "GET /info?api-version=2.0 HTTP/1.1" 400 192 "-" "curl/7.51.0"

The behavior for missing api-version query parameter should be the same as if a bad version is specified. I expected the first to result in a 400 as well, rather than 404.

New Features

Are thre any new features planed on this project?

What is the significance of API Version Selector?

Hi Chris,

what is the significance of API Version Selector when we have DefaultApiVersion? I am not able to understand the outcome when we have both ApiVersionSelector and DefaultApiVersion is configured.

when I have 4 versions - 1.0, 2.0, 3.0 and 3.1-alpha. I have following configuration.

services.AddApiVersioning(options =>
{
     options.ReportApiVersions = true;
     options.AssumeDefaultVersionWhenUnspecified = true;
     options.ApiVersionReader = new HeaderApiVersionReader("api-version");
     options.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(2, 0);
     options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
});

Now when I make a request without a version, then I am always getting 3.0 (and not 2.0 which I specified as default API version).
When I was making a request with wrong version (say for example 3.6), then I am getting 400 Bad Request stating that API doesn't support specified version.

Can you tell me how API Version Selector works?

Unversioned APIs

When using this library, can we still have api endpoints that are not versioned and still accessible?

I am unable to reach an api endpoint for which I have not specified a version that does not have a version attribute.

I am using dotnet core.

Is it possible to create non-numeric versions?

In addition to the standard, incrementing version numbers, we want to support a version called "beta" on our API (inspired by Microsoft's Graph API). Of course, the code in ApiVersion.Parse stops that in its tracks, as it strictly adheres to the version formatting rules laid out in the Microsoft REST API Guidelines. (I suspect that also means that it's unlikely to be an enhancement anytime soon.) I've been striking out with my attempted workarounds so far. Is there any way I can pull off including a version called "beta"?

Different behavior when using fluent api versus attributes - reporting deprecated apis - Asp.Net Core

Given the ASP.NET Core controller class

[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public IActionResult Version1Method()
    {
        return Json(new { ApiVersion = HttpContext.GetRequestedApiVersion().ToString("v"), Hey = 1, Look = 2 });
    }

    [HttpGet]
    [MapToApiVersion("2.0")]
    public IActionResult Version2Method()
    {
        return Json(new { ApiVersion = HttpContext.GetRequestedApiVersion().ToString("v"), Hey = 2, Look = 4 });
    }
}

and Startup includes

services.AddApiVersioning(options => options.ReportApiVersions = true);

The response headers include the supported and deprecated versions

api-deprecated-versions: 1.0
api-supported-versions: 2.0

But with the following controller class

[Route("api/v{version:apiVersion}/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public IActionResult Version1Method()
    {
        return Json(new { ApiVersion = HttpContext.GetRequestedApiVersion().ToString("v"), Hey = 1, Look = 2 });
    }

    [HttpGet]
    public IActionResult Version2Method()
    {
        return Json(new { ApiVersion = HttpContext.GetRequestedApiVersion().ToString("v"), Hey = 2, Look = 4 });
    }
}

And Startup includes

services.AddApiVersioning(options =>
{
    options.ReportApiVersions = true;
    options.Conventions.Controller<ValuesController>()
        .HasDeprecatedApiVersion(1, 0)
        .HasApiVersion(2, 0)
        .Action(c => c.Version1Method()).MapToApiVersion(1, 0)
        .Action(c => c.Version2Method()).MapToApiVersion(2, 0);
});

The response headers only include the supported versions

api-supported-versions: 2.0

Expectation was that by ReportingApiVersions being set to true that the headers would be the same whether I used the fluent api or attributes. Either the attributes are erroneously advertising deprecated versions, or the fluent api are erroneously not advertising the deprecated versions. My assumption is the attributes are doing the right thing, advertising both the supported and deprecated versions.

OData $metadata Returns Incorrect EDM

The VersionedMetadataController is API version-neutral and can be accessed using the path ~/$metadata without specifying an API version. In this scenario, the EDM model returned should match the configured, default API version. In some scenarios, the controller returns the EDMX for a different API version.

OData Controller Should Return 400 Instead of 404

When an OData route could be matched, but no route matches the specified the API version, then the controller should return 400 instead of 404.

For example:

/orders?api-version=1.0

This route exists and returns 200.

/orders?api-version=2.0

This route does not exist, but the route ~/orders could exist. The response in this case should be 400 and not 404.

Does not build out of the box

Using VS2015 Update 3:

1>------ Rebuild All started: Project: BasicWebApiSample, Configuration: Debug Any CPU ------
2>------ Rebuild All started: Project: BasicODataWebApiSample, Configuration: Debug Any CPU ------
1>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1820,5): warning MSB3106: Assembly strong name "..\..\..\src\WebApi\bin\Debug\net45\Microsoft.AspNet.WebApi.Versioning.dll" is either a path which could not be found or it is a full assembly name which is badly formed. If it is a full assembly name it may contain characters that need to be escaped with backslash(\). Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\).
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Startup.cs(6,25,6,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\HelloWorldController.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\Values2Controller.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\ValuesController.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\ValuesController.cs(10,6,10,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\HelloWorldController.cs(10,6,10,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
1>C:\src\aspnet-api-versioning\samples\webapi\BasicWebApiSample\Controllers\Values2Controller.cs(10,6,10,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1820,5): warning MSB3106: Assembly strong name "..\..\..\src\WebApi\bin\Debug\net45\Microsoft.AspNet.WebApi.Versioning.dll" is either a path which could not be found or it is a full assembly name which is badly formed. If it is a full assembly name it may contain characters that need to be escaped with backslash(\). Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\).
2>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1820,5): warning MSB3106: Assembly strong name "..\..\..\src\WebApi.OData\bin\Debug\net45\Microsoft.AspNet.OData.Versioning.dll" is either a path which could not be found or it is a full assembly name which is badly formed. If it is a full assembly name it may contain characters that need to be escaped with backslash(\). Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\).
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Startup.cs(8,25,8,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Startup.cs(9,25,9,30): error CS0234: The type or namespace name 'OData' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\OrderModelConfiguration.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\OrderModelConfiguration.cs(4,25,4,30): error CS0234: The type or namespace name 'OData' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\PersonModelConfiguration.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\PersonModelConfiguration.cs(4,25,4,30): error CS0234: The type or namespace name 'OData' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\People2Controller.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\OrdersController.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\PeopleController.cs(3,25,3,29): error CS0234: The type or namespace name 'Http' does not exist in the namespace 'Microsoft.Web' (are you missing an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\OrderModelConfiguration.cs(8,44,8,63): error CS0246: The type or namespace name 'IModelConfiguration' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\People2Controller.cs(11,6,11,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\People2Controller.cs(12,6,12,20): error CS0246: The type or namespace name 'ControllerName' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\OrderModelConfiguration.cs(19,55,19,65): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\PersonModelConfiguration.cs(8,45,8,64): error CS0246: The type or namespace name 'IModelConfiguration' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Configuration\PersonModelConfiguration.cs(28,55,28,65): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\OrdersController.cs(11,6,11,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\PeopleController.cs(11,6,11,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\PeopleController.cs(12,6,12,16): error CS0246: The type or namespace name 'ApiVersion' could not be found (are you missing a using directive or an assembly reference?)
2>C:\src\aspnet-api-versioning\samples\webapi\BasicODataWebApiSample\Controllers\PeopleController.cs(27,10,27,25): error CS0246: The type or namespace name 'MapToApiVersion' could not be found (are you missing a using directive or an assembly reference?)
========== Rebuild All: 0 succeeded, 2 failed, 0 skipped ==========

ODataRoute not working correctly

We have an explicit ODataRoute defined in our ODataController

[ApiVersion("1.0"), ApiVersion("1.1")]
public class TestController : ODataController {
[ODataRoute("Test(Name={name},Token={token})"), HttpGet]
public IHttpActionResult Get([FromODataUri] string name, [FromODataUri] string token) {
return Ok(new Test());
}
}

However, when we execute this endpoint it returns a 400 bad request saying it cannot find our specified route for the given api version. After poking around the code, the issue is that there's a type mismatch set on the EdmModel annotation value.

In src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedAttributeRoutingConvention.cs

line 79: var apiVersion = Model.GetAnnotationValue<ApiVersion>(Model)

Whereas in src/Microsoft.AspNet.OData.Versioning/Web.OData/Builder/VersionedODataModelBuilder.cs

line 142: model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) );

Because ApiVersionAnnotation is being used instead of ApiVersion (or vice-versa), the controller is getting ignored from attribute routing.

Add Support for Swagger

Add basic support for Swagger. The Swagger functionality should be supported for all of the platforms:

  • ASP.NET Web API
  • ASP.NET Web API with OData v4.0
  • ASP.NET Core

Add routing constraint support

It's nice to have a way to version your WebAPI, but as a query parameter it's quite useless unless we can encode this inside a route constraint, i.e. /api/v2/values, which is common practice and way to encode and it becomes a natural part of the WebAPIs base url, when used in rest clients.

Investigate API Versioning with Web API Shim

API versioning should be supported when using the ASP.NET Core backward compatibility shim for Web API. This issue should cover the following items:

  • Does the API versioning work as is when used with the Web API shim?
  • If not, create and track a new item to support it
  • Create a sample ASP.NET Core project to demonstrate an implementation

Implicitly Versioned OData Controller Is Not Resolved

When implicit API versioning is enabled:

configuration.AddApiVersioning( o => o.AssumeDefaultVersionWhenUnspecified = true );

The following OData controller should be implicitly matched:

[ODataRoutePrefix( "People" )]
public class PeopleController : ODataController
{
    // GET ~/people
    // GET ~/people?api-version=1.0
    [ODataRoute]
    public IHttpActionResult Get() => Ok();

The server returns 4xx class response instead.

Disallow Multiple, Different API Versions

The current implementation for reading API versions from a request allows a client to specify an API version multiple times. Each of the IApiVersionReader implementations merely selects the first available API version value known to them.

In practice, few clients would likely send such a request; however, the server should not allow multiple API versions to be specified when they are different. On the other hand, requests that contain multiple API versions with the same meaning should be allowed.

Examples of Invalid Requests

GET /foo?api-version=1.0&api-version=2.0 HTTP/1.1
host: localhost
GET /foo HTTP/1.1
host: localhost
api-version: 1.0
api-version: 2.0
GET /foo?api-version=1.0 HTTP/1.1
host: localhost
api-version: 2.0

Examples of Valid Requests

GET /foo?api-version=1.0&api-version=1.0 HTTP/1.1
host: localhost
GET /foo HTTP/1.1
host: localhost
api-version: 1.0
api-version: 1.0
GET /foo?api-version=1.0 HTTP/1.1
host: localhost
api-version: 1.0

Support more extensibility around versioning formats

My company has a use case where we are versioning our API using System.Version (i.e. Major.Minor.Build.Revision). I'm not in a position where I can change our versioning format at the moment, and I imagine others may be in a similar position. The versioning format restrictions mean I can't represent our version accurately using this library.

There are a few different ways more extensibility could be added to the version format, ranging from most extensible to smallest number of changes.

At minimum, the library could support more version numbers without introducing any breaking changes. For example, the library could support the System.Version format of Major.Minor.Build.Revision or the semver format of MAJOR.MINOR.PATCH-Status.

To achieve this goal, ApiVersion could be modified directly, or (as a more modular solution) ApiVersion could remain unchanged and the library could support users deriving from ApiVersion. This would require Equals to become virtual, IsValidStatus could become private (it doesn't seem to be used publicly), and both Parse and TryParse would need to be moved into a strategy that can be overridden. These methods are called in HttpContextExtensions.GetRequestedApiVersion, ApiVersionRouteConstraint.Match, ApiVersionActionSelector.IsValidRequest and the ApiVersionsBaseAttribute constructor, and so they would need to have this strategy injected or retrieved from the IoC container or a static.

Would you be willing to accept a PR with these changes, at minimum for AspNetCore?

Unhandled exception when you call an HTTP POST action with GET

A bit obscure I know, but I came across this bug when I was doing daft things to my Api to see if it reacted OK (users do daft things occasionally!)

Its easy to reproduce with the Asp.Net Core samples HelloWorldController. Just add an HttpPost action that does anything you like - I have added one called "NotAGet"

`[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class HelloWorldController : Controller
{
// GET api/v{version}/helloworld
[HttpGet]
public IActionResult Get() => Ok(new { Controller = GetType().Name, Version = HttpContext.GetRequestedApiVersion().ToString() });

  // GET api/v{version}/helloworld/{id}
  [HttpGet("{id:int}", Name = "GetMessageById")]
  public IActionResult Get(int id) => Ok(new { Controller = GetType().Name, Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() });

  // POST api/v{version}/helloworld
  [HttpPost]
  public IActionResult Post() => CreatedAtRoute("GetMessageById", new { id = 42 }, null);

  [HttpPost("notAGet")]
  public IActionResult NotAGet() => CreatedAtRoute("notAGet", new { id = 43 }, null);
}`

Run the project and paste the following into a browser address bar:

http:///api/v1/helloworld/notAGet

You will get an unhandled ArgumentNullException in the application, ultimately resulting in an HTTP 500 being returned to the browser.

Obviously this is a pretty low priority as it only occurs when someone does something silly, but ideally it still shouldn't throw an unhandled exception...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.