Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O encaminhamento é a forma como a Web API associa um URI a uma ação. A Web API 2 suporta um novo tipo de encaminhamento, chamado encaminhamento por atributos. Como o nome indica, o encaminhamento por atributos utiliza atributos para definir rotas. O roteamento de atributos oferece mais controle sobre os URIs em sua API da Web. Por exemplo, pode facilmente criar URIs que descrevam hierarquias de recursos.
O estilo anterior de encaminhamento, chamado encaminhamento baseado em convenção, ainda é totalmente suportado. Na verdade, podes combinar ambas as técnicas no mesmo projeto.
Este tópico mostra como ativar o encaminhamento de atributos e descreve as várias opções para o encaminhamento de atributos. Para um tutorial de ponta a ponta que utiliza encaminhamento por atributos, veja Criar uma API REST com Roteamento de Atributos na Web API 2.
Pré-requisitos
Visual Studio 2017 Edição Comunitária, Profissional ou Empresarial
Alternativamente, use o NuGet Package Manager para instalar os pacotes necessários. No menu Ferramentas do Visual Studio, selecione Gestor de Pacotes NuGet e depois selecione Console do Gestor de Pacotes. Introduza o seguinte comando na janela da Consola do Gestor de Pacotes:
Install-Package Microsoft.AspNet.WebApi.WebHost
Porque usar encaminhamento de atributos?
A primeira versão da Web API usava encaminhamento baseado em convenções . Nesse tipo de encaminhamento, define-se um ou mais modelos de rota, que são basicamente cadeias parametrizadas. Quando o framework recebe um pedido, faz corresponder o URI ao template da rota. Para mais informações sobre encaminhamento baseado em convenções, veja Routing in ASP.NET Web API.
Uma vantagem do encaminhamento baseado em convenções é que os modelos são definidos num único local, e as regras de encaminhamento são aplicadas de forma consistente em todos os controladores. Infelizmente, o encaminhamento baseado em convenções dificulta o suporte a certos padrões de URI comuns em APIs RESTful. Por exemplo, os recursos frequentemente contêm recursos infantis: os clientes têm encomendas, os filmes têm atores, os livros têm autores, e assim por diante. É natural criar URIs que reflitam estas relações:
/customers/1/orders
Este tipo de URI é difícil de criar usando encaminhamento baseado em convenções. Embora seja possível, os resultados não escalam bem se tiveres muitos controladores ou tipos de recursos.
Com o encaminhamento por atributos, é trivial definir uma rota para este URI. Basta adicionar um atributo à ação do comando:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Aqui estão outros padrões que o roteamento de atributos facilita.
Controlo de versões de API
Neste exemplo, "/api/v1/products" seria encaminhado para um controlador diferente de "/api/v2/products".
/api/v1/products
/api/v2/products
Segmentos URI sobrecarregados
Neste exemplo, "1" é um número de ordem, mas "pendente" corresponde a uma coleção.
/orders/1
/orders/pending
Múltiplos tipos de parâmetros
Neste exemplo, "1" é um número de encomenda, mas "2013/06/16" especifica uma data.
/orders/1
/orders/2013/06/16
Ativação do Roteamento de Atributos
Para permitir o encaminhamento por atributos, chame MapHttpAttributeRoutes durante a configuração. Este método de extensão está definido na classe System.Web.Http.HttpHttpConfigurationExtensions .
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
O encaminhamento por atributos pode ser combinado com o encaminhamento baseado em convenções . Para definir rotas baseadas em convenções, chame o método MapHttpRoute .
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Para mais informações sobre a configuração da Web API, consulte Configuring ASP.NET Web API 2.
Nota: Migração a partir da Web API 1
Antes da Web API 2, os modelos do projeto Web API geravam código assim:
protected void Application_Start()
{
// WARNING - Not compatible with attribute routing.
WebApiConfig.Register(GlobalConfiguration.Configuration);
}
Se o encaminhamento de atributos estiver ativado, este código lançará uma exceção. Se atualizar um projeto Web API existente para usar roteamento por atributos, certifique-se de atualizar este código de configuração para o seguinte:
protected void Application_Start()
{
// Pass a delegate to the Configure method.
GlobalConfiguration.Configure(WebApiConfig.Register);
}
Observação
Para mais informações, consulte Configurar a API Web com ASP.NET Hosting.
Adição de Atributos de Rota
Aqui está um exemplo de uma rota definida usando um atributo:
public class OrdersController : ApiController
{
[Route("customers/{customerId}/orders")]
[HttpGet]
public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }
}
A string "customers/{customerId}/orders" é o modelo URI para a rota. A Web API tenta associar o URI do pedido ao modelo. Neste exemplo, "clientes" e "encomendas" são segmentos literais, e "{customerId}" é um parâmetro variável. Os seguintes URIs corresponderiam a este modelo:
http://localhost/customers/1/ordershttp://localhost/customers/bob/ordershttp://localhost/customers/1234-5678/orders
Pode restringir o emparelhamento usando restrições, descritas mais adiante neste tópico.
Note que o parâmetro "{customerId}" no modelo de rota corresponde ao nome do parâmetro customerId no método. Quando a Web API invoca a ação do controlador, tenta associar os parâmetros da rota. Por exemplo, se o URI for http://example.com/customers/1/orders, a Web API tenta associar o valor "1" ao parâmetro customerId dentro da ação.
Um template de URI pode ter vários parâmetros:
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
Quaisquer métodos controladores que não tenham atributo de rota utilizam encaminhamento baseado em convenções. Assim, podes combinar ambos os tipos de roteamento no mesmo projeto.
Métodos HTTP
A Web API também seleciona ações com base no método HTTP do pedido (GET, POST, etc). Por defeito, a Web API procura uma correspondência sem distinção de maiúsculas ou minúsculas com o início do nome do método do controlador. Por exemplo, um método controlador chamado PutCustomers corresponde a um pedido HTTP PUT.
Pode ultrapassar esta convenção decorando o método com qualquer um dos seguintes atributos:
- [HttpDelete]
- [HttpGet]
- [HttpHead]
- [HttpOptions]
- [HttpPatch]
- [HttpPost]
- [HttpPut]
No exemplo seguinte, a Web API mapeia o método CreateBook para pedidos HTTP POST.
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
Para todos os outros métodos HTTP, incluindo métodos não padrão, use o atributo AcceptVerbs , que recebe uma lista de métodos HTTP.
// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }
Prefixos de Rota
Muitas vezes, as rotas num controlador começam todas com o mesmo prefixo. Por exemplo:
public class BooksController : ApiController
{
[Route("api/books")]
public IEnumerable<Book> GetBooks() { ... }
[Route("api/books/{id:int}")]
public Book GetBook(int id) { ... }
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
}
Pode definir um prefixo comum para um controlador inteiro usando o atributo [RoutePrefix] :
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... }
// GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... }
// POST api/books
[Route("")]
public HttpResponseMessage Post(Book book) { ... }
}
Use um tilde (~) no atributo de método para substituir o prefixo de rota.
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable<Book> GetByAuthor(int authorId) { ... }
// ...
}
O prefixo de rota pode incluir parâmetros:
[RoutePrefix("customers/{customerId}")]
public class OrdersController : ApiController
{
// GET customers/1/orders
[Route("orders")]
public IEnumerable<Order> Get(int customerId) { ... }
}
Restrições de Rota
As restrições de rota permitem restringir como os parâmetros no template de rota são combinados. A sintaxe geral é "{parameter:restrição}". Por exemplo:
[Route("users/{id:int}")]
public User GetUserById(int id) { ... }
[Route("users/{name}")]
public User GetUserByName(string name) { ... }
Aqui, a primeira rota só será selecionada se o segmento "id" do URI for um inteiro. Caso contrário, será escolhido o segundo caminho.
A tabela seguinte lista as restrições suportadas.
| Restrição | Descrição | Exemplo |
|---|---|---|
| alfa | Corresponde a letras maiúsculas ou minúsculas do alfabeto latino (a-z, A-Z) | {x:alpha} |
| booleano | Corresponde a um valor booleano. | {x:bool} |
| datetime | Corresponde a um valor DateTime . | {x:data-hora} |
| decimal | Corresponde a um valor decimal. | {x:decimal} |
| duplo | Corresponde a um valor de ponto flutuante de 64 bits. | {x:duplo} |
| float | Corresponde a um valor de ponto flutuante de 32 bits. | {x:float} |
| Identificador Globalmente Único (GUID) | Corresponde a um valor GUID. | {x:guid} |
| int | Corresponde a um valor inteiro de 32 bits. | {x:int} |
| length | Corresponde a uma corda com o comprimento especificado ou dentro de um intervalo especificado de comprimentos. | {x:comprimento(6)} {x:comprimento(1,20)} |
| long | Corresponde a um valor inteiro de 64 bits. | {x:long} |
| max | Corresponde a um inteiro com um valor máximo. | {x:max(10)} |
| comprimento máximo | Corresponde a uma cadeia de caracteres com comprimento máximo. | {x:maxlength(10)} |
| min | Corresponde a um inteiro com um valor mínimo. | {x:min(10)} |
| comprimento mínimo | Corresponde a uma corda com comprimento mínimo. | {x:minlength(10)} |
| intervalo | Corresponde a um inteiro dentro de um intervalo de valores. | {x:range(10,50)} |
| regex | Corresponde a uma expressão normal. | {x:regex(^\d{3}-\d{3}-\d{4}$)} |
Note que algumas das restrições, como "min", têm argumentos entre parênteses. Podes aplicar múltiplas restrições a um parâmetro, separadas por dois pontos.
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { ... }
Restrições de Rotas Personalizadas
Pode criar restrições de rota personalizadas implementando a interface IHttpRouteConstraint . Por exemplo, a seguinte restrição restringe um parâmetro a um valor inteiro não nulo.
public class NonZeroConstraint : IHttpRouteConstraint
{
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
long longValue;
if (value is long)
{
longValue = (long)value;
return longValue != 0;
}
string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
if (Int64.TryParse(valueString, NumberStyles.Integer,
CultureInfo.InvariantCulture, out longValue))
{
return longValue != 0;
}
}
return false;
}
}
O código seguinte mostra como registar a restrição:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
}
}
Agora podes aplicar a restrição nas tuas rotas:
[Route("{id:nonzero}")]
public HttpResponseMessage GetNonZero(int id) { ... }
Também pode substituir toda a classe DefaultInlineConstraintResolver implementando a interface IInlineConstraintResolver . Fazer isso substituirá todas as restrições incorporadas, a menos que a sua implementação do IInlineConstraintResolver as adicione especificamente.
Parâmetros URI Opcionais e Valores Padrão
Podes tornar um parâmetro URI opcional adicionando um ponto de interrogação ao parâmetro da rota. Se um parâmetro de rota for opcional, deve definir um valor padrão para o parâmetro do método.
public class BooksController : ApiController
{
[Route("api/books/locale/{lcid:int?}")]
public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}
Neste exemplo, /api/books/locale/1033 e /api/books/locale devolve o mesmo recurso.
Alternativamente, pode especificar um valor padrão dentro do modelo de rota, da seguinte forma:
public class BooksController : ApiController
{
[Route("api/books/locale/{lcid:int=1033}")]
public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
}
Isto é quase igual ao exemplo anterior, mas há uma ligeira diferença de comportamento quando o valor padrão é aplicado.
- No primeiro exemplo ("{lcid:int?}"), o valor padrão de 1033 é atribuído diretamente ao parâmetro do método, pelo que o parâmetro terá este valor exato.
- No segundo exemplo ("{lcid:int=1033}"), o valor padrão de "1033" passa pelo processo de ligação ao modelo. O vinculador de modelos padrão converterá "1033" no valor numérico 1033. No entanto, podes integrar um binder de modelo personalizado, que pode fazer algo diferente.
(Na maioria dos casos, a menos que tenha binders de modelos personalizados no seu pipeline, as duas formas serão equivalentes.)
Nomes das Rotas
Na Web API, cada rota tem um nome. Os nomes das rotas são úteis para gerar ligações, para que possa incluir uma ligação numa resposta HTTP.
Para especificar o nome da rota, defina a propriedade Nome no atributo. O exemplo seguinte mostra como definir o nome da rota, e também como usar o nome da rota ao gerar uma ligação.
public class BooksController : ApiController
{
[Route("api/books/{id}", Name="GetBookById")]
public BookDto GetBook(int id)
{
// Implementation not shown...
}
[Route("api/books")]
public HttpResponseMessage Post(Book book)
{
// Validate and add book to database (not shown)
var response = Request.CreateResponse(HttpStatusCode.Created);
// Generate a link to the new book and set the Location header in the response.
string uri = Url.Link("GetBookById", new { id = book.BookId });
response.Headers.Location = new Uri(uri);
return response;
}
}
Ordem do percurso
Quando o framework tenta associar um URI a uma rota, avalia as rotas numa ordem específica. Para especificar a ordem, defina a propriedade Ordem no atributo rota. Os valores mais baixos são avaliados primeiro. O valor padrão da ordem é zero.
Aqui está como é determinada a ordenação total:
Compare a propriedade Order do atributo Route.
Vê cada segmento de URI no modelo de rota. Para cada segmento, ordem da seguinte forma:
- Segmentos literais.
- Parâmetros de rota com restrições.
- Parâmetros de rota sem restrições.
- Segmentos de parâmetros 'wildcard' com restrições.
- Segmentos de parâmetro wildcard sem restrições.
No caso de empate, as rotas são ordenadas por uma comparação ordinal de strings que ignora maiúsculas e minúsculas (OrdinalIgnoreCase) do modelo de rota.
Aqui está um exemplo. Suponha que define o seguinte controlador:
[RoutePrefix("orders")]
public class OrdersController : ApiController
{
[Route("{id:int}")] // constrained parameter
public HttpResponseMessage Get(int id) { ... }
[Route("details")] // literal
public HttpResponseMessage GetDetails() { ... }
[Route("pending", RouteOrder = 1)]
public HttpResponseMessage GetPending() { ... }
[Route("{customerName}")] // unconstrained parameter
public HttpResponseMessage GetByCustomer(string customerName) { ... }
[Route("{*date:datetime}")] // wildcard
public HttpResponseMessage Get(DateTime date) { ... }
}
Estas rotas estão organizadas da seguinte forma.
- Ordens/Detalhes
- pedidos/{id}
- pedidos/{customerName}
- ordens/{*data}
- Ordens/pendentes
Note que "detalhes" é um segmento literal e aparece antes de "{id}", mas "pendente" aparece por último porque a propriedade Order é 1. (Este exemplo assume que não existem clientes chamados "detalhes" ou "pendentes". Em geral, tente evitar rotas ambíguas. Neste exemplo, um modelo de rota melhor para GetByCustomer é "clientes/{nomeDoCliente}")