ASP.NET Core Web API - Definindo o roteamento


Neste artigo veremos como definir o roteamento em aplicações ASP .NET Core Web API.

O roteamento na ASP.NET Core é o processo de mapeamento de requisições recebidas para a lógica do aplicativo que reside nos controladores e métodos Actions.

A ASP.NET Core mapeia a requisição recebida com base nas rotas configuradas no seu aplicativo e, para cada rota, você pode definir configurações específicas, como valores padrão, manipuladores de mensagens, restrições e assim por diante.

Nos projetos .NET Core, por padrão, os Controllers são decorados com métodos CRUD, especificando os respectivos verbos de ação HTTP: Get, Post, Put e Delete.

Existem algumas maneiras de controlar o roteamento em um aplicativo ASP.NET Core, e, as mais comuns são:

Roteamento convencional: Aa rota é determinada com base em convenções definidas em modelos de rota que, em tempo de execução, mapearão requisições para controladores e Actions (métodos).

Roteamento baseado em atributos: A rota é determinada com base nos atributos que você define em seus controladores e métodos. As definições nos atributos irão realizar o mapeamento para as Actions do controlador.

Neste artigo, vamos nos concentrar no roteamento baseado em atributos usado pelas Web API.

A principal diferença com o roteamento das aplicações Web API sobre as aplicações MVC é que elas usam o método HTTP, não o caminho do URI, para selecionar a Action. O roteamento de atributos também usa verbos HTTP para definir a Action dos métodos do controlador conforme a requisição.

No caso das Web APIs a recomendação é usar o roteamento baseado em atributos que permite anexar uma rota, como um atributo, a um controlador ou método Action específico, e, isso é feito usando o atributo [Route].

O roteamento baseado em atributos oferece controle preciso sobre os URIs, ao contrário do roteamento por convenção, e, em um cenário de recursos hierárquicos eles podem ser facilmente alcançados pelo Roteamento de Atributos, sem comprometer a escalabilidade das APIs.

Beneficíos do roteamento

A seguir vejamos alguns dos benefícios do roteamento:

Veremos a seguir exemplos práticos de roteamento basedo em atributos.

Criando o projeto Web API no VS 2019 Community

Vamos criar uma aplicação ASP .NET Core Web Application usando o template API no Visual Studio 2019 Community chamada APi_Roteamentos;

Abra o VS 2019 Community e selecione : Create a new Project

Selecionando em Language : C#, Platform: All Platforms e em Project Type: web podemos selecionar o template ASP .NET Core Web Application:

A seguir vamos informar o nome do projeto: APi_Roteamentos , sua localização e nome da solução, que é  o mesmo do projeto;

Finalmente selecione a plataforma .NET Core e ASP .NET Core 2.2 e o template API.

Marque a opção para configurar HTTP e não vamos usar Autenticação:

Ao final teremos um projeto Web API com um controlador chamado ValuesController definido na pasta Controllers.

Analisando o controlador ValuesController

Abaixo temos o código do controlador ValuesController que é gerado por padrão no projeto API:

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]

        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]

        public ActionResult<string> Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]

        public void Post([FromBody] string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]

        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]

        public void Delete(int id)
        {
        }
    }

Observe que a classe ValuesController é decorada com os atributos:

 [Route("api/[controller]")]
 [ApiController]

O token [ApiController] melhora a experiência do desenvolver habilitando diversos recursos como:

O token [controller] substitui os valores do nome do controlador a partir da Action ou classe onde a rota esta definida.

Assim a URI que vai acessar o método [HttpGet] sem parâmetro é api/values, e a que vai acessar o segundo método  [HttpGet("{id}")] é a api/values/1.

Considere os métodos Actions a seguir:

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]

        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // POST api/values
        [HttpPost]

        public void Post([FromBody] string value)
        {
        }
        ...

    }

Para o trecho de código fornecido acima, temos as mesmas URIs (api/values) mas os verbos HTTP são diferentes:

Isso faz com que o roteamento da API da Web seja diferente do roteamento MVC.

Vejamos a seguir alguns padrões de roteamentos que podem ser usados nas WEB APIs:

1- Versionamento

No próximo exemplo,  "Get  /api/values/v1/"  será roteado para o primeiro método Get enquanto que "Get /api/values/v2/" será roteado para o segundo método Get:

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet("v1")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("v2")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }
    ....
}

Aqui os métodos são iguais mas possuem um roteamento diferente que atua como um versionamento do método que possui duas versões v1 e v2 definidas em HttpGet.

2- Sobrecarga de URI

Neste exemplo, "id" é um parâmetro que pode ser passado como número, enquanto que "valor" será mapeado para uma coleção.

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet("{id}")]
        public ActionResult<IEnumerable<string>> Get(int id)
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("valor")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return Collection....;
        }
    ....
}

Dessa forma temos que :

  1. Get /api/values/123 - irá mapear para o primeiro método Get.
  1. Get /api/values/ativo  - irá mapear para o segundo método Get (a coleção).

3- Parâmetros com múltiplos tipos

Neste exemplo, "id" é um parâmetro que pode ser passado como o número ou como uma string:

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet("{id:int}")]
        public ActionResult<IEnumerable<string>> Get(int id)
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("id:alpha")]
        public ActionResult<IEnumerable<string>> Get(string id)
        {
             return new string[] { "value1", "value2", "valor3" };
        }
    ....
}

Dessa forma temos que :

  1. Get /api/values/123 - irá mapear para o primeiro método Get.
  1. Get /api/values/abc  - irá mapear para o segundo método Get.

4- Múltiplos Parâmetros

Neste exemplo, "id" e "autorid" são parâmetros que podem ser passados como um número inteiro:

    [Route("api/[controller]")]
    [ApiController]
    public class AutoresController : ControllerBase
    {
        [HttpGet("{id:int}/autor/{autorid:int}")]
        public ActionResult<IEnumerable<string>> Get(int id, int autorid)
        {
            return new string[] { "value1", "value2" };
        }      
    ....
}

Assim temos que :

a URI:  Get  api/livros/1/autor/5  onde 1 mapeia para o ‘id’ e ‘5’ para 'autorid' no método Action.

5- Múltiplas Rotas

O roteamento de atributos nos permite definir várias rotas para o mesmo controlador, Action ou Método.

Veja o exemplo a seguir:

    [Route("api/[controller]")]
    [ApiController]
    public class FilmesController : ControllerBase
    {
        [HttpPut("compra")]
        [HttpPost("checkout")]
        public ActionResult<List<Filme>> PedidoFilme(..)
        {
            return ...      
       }      
    ....
}

Conforme mostrado no exemplo, o método "PedidoFilme" retorna algum valor usando a classe de modelo "Filme".

O método define duas rotas:

  1. Uma com o verbo HTTP Put, usado principalmente para atualizar as operações no CRUD;
  2. Outro com o verbo HTTP Post, usado para criar ou adicionar dados.

Ambos estão se referindo ao mesmo método.

Nesse caso, os URIs abaixo seriam correspondentes às rotas:

URI 1   -> Put  api/filmes/compra
URI 2   -> Post api/filmes/checkout

Podemos também definir múltiplas rotas usando o atributo Route conforme abaixo:

[HttpGet("")]
[HttpGet("Home")]
[HttpGet("Home/Index")]
public string Index() {
    return "Método Index";       
}

Neste exemplo o mapeamento vai funcionar assim:

URL Corresponde ? Analisado como
/ Sim
 
Controller=Home
Action=Index
/Home Sim Controller=Home
Action=Index
/Home/Index Sim
 
Controller=Home
Action=Index
/Ola/Mundo Não  

6- Restrição de Rotas

A restrição de rota fornece controle sobre os parâmetros correspondentes usados na rota. A sintaxe para definir parâmetros na rota é "{parameter: constraint}".

Veja o exemplo a seguir:

    [Route("api/[controller]")]
    [ApiController]
    public class FilmesController : ControllerBase
    {
        [HttpGet("api/constraint/{id:int:min(1)}")]
        public ActionResult<IEnumerable<string>> Get(int id)
        {
            return ...       
       }      
    ....
}

A rota que mapeia para o método é :  api/constraint/1

Somente o valor inteiro será roteado para o método 'Get' respondendo com um recurso válido. Qualquer outro valor não inteiro não será atendido pelo método. Assim,  a uri :  api/constraint/abc não será roteada.

Aqui, poderia haver um problema. Vejamos...

Se o cliente chamar , api/constraint/0 , o request seria roteado para o método Get, o que está errado.

Para contornar esse problema, podemos adicionar outra restrição ao parâmetro para aceitar um valor maior que zero. Isso pode ser alcançado incluindo a restrição "min(1)", no qual 1 é argumento aceito pela restrição "min". Argumentos podem ser aceitos entre parênteses "()"

O namespace Microsoft.AspNetCore.Routing.Constraints define um conjunto de classes que podem ser usadas para definir restrições individuais.

Abaixo uma tabela das restrições que verificam o tipo de dados:

constraint Inline Classe Descrição
int {id:int} IntRouteConstraint Restringe um parâmetro de rota para representar apenas valores inteiros de 32 bits
alpha {id:alpha} AlphaRouteConstraint Restringe um parâmetro de rota para conter apenas letras minúsculas ou maiúsculas de A a Z no alfabeto inglês.
bool {id:bool} BoolRouteConstraint Restringe um parâmetro de rota para representar apenas valores booleanos.
datetime {id:datetime} DateTimeRouteConstraint Restringe um parâmetro de rota para representar apenas valores de DateTime.
decimal {id:decimal} DecimalRouteConstraint Restringe um parâmetro de rota para representar apenas valores decimais.
double {id:double} DoubleRouteConstraint Restringe um parâmetro de rota para representar apenas valores de ponto flutuante de 64 bits
float {id:float} FloatRouteConstraint Corresponde a um valor flutuante válido (na invariant culture)
guid {id:guid} GuidRouteConstraint Corresponde a um valor válido de Guid

Essas são algumas das possibilidades mostrando como o roteamento pode ser flexível.

Pegue o projeto completo aqui:  Api_AnalyzersDemo.zip

Ninguém jamais viu a Deus; o Deus unigênito (Jesus), que está no seio do Pai, é quem o revelou.
João 1:17,18


Referências:


José Carlos Macoratti