O criação de versões de Web API torna-se útil, quando precisamos em um determinado período da vida do nosso sistema, criar uma versão mais funcionalidades de algum determinado endpoint. Porém como nosso endpoint serve um número vasto de clientes, não podemos simplesmente forçar todos os clientes a já consumirem esse nosso novo endpoint. Tornando necessário assim, mantermos por um determinado período a versão antiga do endpoint.
Com o intuído de dar um maior suporte ao versionamento dos endpoints podemos utilizar o pacote Microsoft.AspNet.WebApi.Versioning.
Com este pacote, temos a opção também de integrar com o Swagger. Sendo o Swagger usado para gerar a documentação de nossos endpoints muito mais completo que o tradicional HelpPage.
Para desenvolvermos o exemplo, iremos utilizar o VS2017.
Darei o nome ao projeto de WebAPIVersion, utilizarei a versão 4.7 do Framework e o tipo de projeto será ASP.NET Web Application.
O template que utilizarei será o Web API com a autenticação do tipo Individual User Accounts
Com o projeto criado, a primeira coisa que iremos fazer, será dar um Update-Package. Garantindo assim que tenhamos as ultimas versões dos pacotes instalados em nosso projeto. Caso necessário, reinicie seu visual studio.
Se executarmos o projeto e acessarmos o endereço http://localhost:53339/Help (lembre de trocar a porta), teremos acesso a documentação gerada de nossos endpoints, no entanto, só podemos ver sua documentação, não podendo fazer requisições a eles. Mas para isso, existe o Swagger, que além de mostrar a documentação de nossos endpoints, nos permite interagir com eles.
Nosso próximo passo será adicionar o Swagger ao projeto e vermos a diferença para o HelpPage tradicional.
Para tanto instale o pacote Swashbuckle.
A mágica toda está no arquivo SwaggerConfig.cs que está na pasta App_Start de nosso projeto. Será nesse arquivo que iremos configurar mais adiante o Swagger para ter as versões de nossos endpoints separadamente na documentação.
Com a instalação do Swagger concluída, já seremos capaz de visualizar como ele gerou a documentação, bastando acessar o endereço http://localhost:53339/swagger.
Vejam também que é possível testar nossos endpoints também, bastando expandir o endpoint e clicar no Try it out que ela já realizará a chamada para nós. Não necessitando assim utilizarmos outras ferramentas para testarmos os endpoints.
Agora vamos ao que realmente interessa, o versionamento dos endpoints. Para uma melhor organização, teremos nossos controllers separados por namespaces que definam a versão em que será disponibilizado o endpoint.
Terei a seguinte estrutura de pastas no meu Solution. Sendo o AccountController e ValuesController uma cópia dos que já tinha na pasta Controllers que foram criados pelo template do Visual Studio.
Já a partir deste ponto, o swagger e o HelpPage, não serão mais capazes de mostrar os nossos endpoints, necessitando assim a utilização do pacote Microsoft.AspNet.WebApi.Versioning.
Instale então o pacote Microsoft.AspNet.WebApi.Versioning no projeto, precisaremos também do pacote Microsoft.AspNet.WebApi.Versioning.ApiExplorer, sendo este último que permitirá a integração com o Swagger.
O arquivo App_Start/SwaggerConfig.cs será alterado para o seguinte
using System.Web.Http;
using Swashbuckle.Application;
using Owin;
using Microsoft.Web.Http.Routing;
using System.Web.Http.Routing;
using System.Web.Http.Description;
using WebAPIVersion.Infraestrutura;
namespace WebAPIVersion
{
public partial class Startup
{
public static void ConfigureSwagger(IAppBuilder builder)
{
// we only need to change the default constraint resolver for services that want urls with versioning like: ~/v{version}/{controller}
var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) } };
var configuration = new HttpConfiguration();
var httpServer = new HttpServer(configuration);
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
configuration.AddApiVersioning(o => o.ReportApiVersions = true);
configuration.MapHttpAttributeRoutes(constraintResolver);
// add the versioned IApiExplorer and capture the strongly-typed implementation (e.g. VersionedApiExplorer vs IApiExplorer)
// note: the specified format code will format the version as "'v'major[.minor][-status]"
var apiExplorer = configuration.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
configuration.EnableSwagger(
"{apiVersion}/swagger",
swagger =>
{
// build a swagger document and endpoint for each discovered API version
swagger.MultipleApiVersions(
(apiDescription, version) => apiDescription.GetGroupName() == version,
info =>
{
foreach (var group in apiExplorer.ApiDescriptions)
{
var description = "A sample application with Swagger, Swashbuckle, and API versioning.";
if (group.IsDeprecated)
{
description += " This API version has been deprecated.";
}
info.Version(group.Name, $"Sample API {group.ApiVersion}")
.Contact(c => c.Name("Bill Mei").Email("bill.mei@somewhere.com"))
.Description(description)
.License(l => l.Name("MIT").Url("https://opensource.org/licenses/MIT"))
.TermsOfService("Shareware");
}
});
// add a custom operation filter which sets default values
swagger.OperationFilter<SwaggerDefaultValues>();
// integrate xml comments
// swagger.IncludeXmlComments(XmlCommentsFilePath);
})
.EnableSwaggerUi(swagger => swagger.EnableDiscoveryUrlSelector());
builder.UseWebApi(httpServer);
}
}
}
Cria uma classe chamada SwaggerDefaultValues
using Swashbuckle.Swagger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Description;
namespace WebAPIVersion.Infraestrutura
{
/// <summary>
/// Represents the Swagger/Swashbuckle operation filter used to provide default values.
/// </summary>
/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
/// Once they are fixed and published, this class can be removed.</remarks>
public class SwaggerDefaultValues : IOperationFilter
{
/// <summary>
/// Applies the filter to the specified operation using the given context.
/// </summary>
/// <param name="operation">The operation to apply the filter to.</param>
/// <param name="schemaRegistry">The API schema registry.</param>
/// <param name="apiDescription">The API description being filtered.</param>
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
{
return;
}
foreach (var parameter in operation.parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.name);
// REF: https://github.com/domaindrivendev/Swashbuckle/issues/1101
if (parameter.description == null)
{
parameter.description = description.Documentation;
}
// REF: https://github.com/domaindrivendev/Swashbuckle/issues/1089
// REF: https://github.com/domaindrivendev/Swashbuckle/pull/1090
if (parameter.@default == null)
{
parameter.@default = description.ParameterDescriptor.DefaultValue;
}
}
}
}
}
Remova a chamada GlobalConfiguration.Configure(WebApiConfig.Register); de dentro do Global.asax. Ficando da seguinte forma
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace WebAPIVersion
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
E nosso Startup.cs fica da seguinte maneira
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebAPIVersion.Startup))]
namespace WebAPIVersion
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
ConfigureSwagger(app);
}
}
}
Essas são as configurações para podermos então trabalhar com o atributo ApiVersion, bastando adicionar a cada um de nossos controllers e especificar as versões.
O AccountController fica da seguinte maneira para nossa versão 1.0
namespace WebAPIVersion.V1.Controllers
{
[Authorize]
[ApiVersion("1.0")]
[RoutePrefix("api/v{api-version:apiVersion}/Account")]
public class AccountController : ApiController
{
........
}
}
Nota também que defini um RoutePrefix, onde determinei que a versão para ficar entre o api e o nome do controller, ficando da seguinte maneira a chamada ao endpoint api/v1/Account/endpoint.
O ValuesController da versão 1.0 fica da seguinte maneira
using Microsoft.Web.Http;
using System.Collections.Generic;
using System.Web.Http;
namespace WebAPIVersion.V1.Controllers
{
[ApiVersion("1.0")]
[RoutePrefix("api/v{api-version:apiVersion}/Values")]
public class ValuesController : ApiController
{
// GET api/values
[Route]
public IEnumerable<string> Get()
{
return new string[] { "value1 v1", "value2 v1" };
}
// GET api/values/5
[Route("{id}")]
public string Get(int id)
{
return "value v1";
}
// POST api/values
[Route]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[Route("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[Route("{id}")]
public void Delete(int id)
{
}
}
}
Já para a versão 2.0 basta fazer a alteração no atributo ApiVersion.
Pronto, com isso o swagger já irá mostrar as duas versões separadamente.
Veja um exemplo de uma requisição GET na versão 1.0 usando o Swagger
Agora uma requisição GET na versão 2.0 usando o Swagger
Para fazer uma requisição ao endpoint, basta acessar o endereço http://localhost:53339/api/v1/Values para a versão 1.0 e o endereço http://localhost:53339/api/v2/Values para a versão 2.0.
Com isso podemos ter os nosso endpoints separados por versões definidas usando o ApiVersion.
O código fonte para o exemplo pode ser baixado do meu github.