Múltiplos endpoints em uma rota no ASP.NET Core
Tags: dotnet, api
Published: 2020-12-28 16:04:52

Disponibilizar múltiplos *endpoints* em um recurso de uma API pode ser algo necessário quando quisermos deixar nossa API mais simples de ser consumida nos mais diversos cenários, por exemplo, quando uma aplicação quiser manipular um cliente, pode ser mais conveniente utilizar o CPF e não o id, e para isso precisaríamos de dois *endpoints* para consultar um cliente específico,

```
GET /cliente/{id}
GET /cliente/{cpf}
```

E como podemos lidar com esses dois *endpoints* de formas diferentes no ASP.NET Core? A resposta está no próprio mecanismo de rotas do *framework*.

O sistema de rotas do ASP.NET Core nos permite aplicar diversas restrições em um parâmetro de rota, isso permite invalidar rotas que não estejam dentro dos limites estabelecidos, resolver ambiguidades entre rotas e ter múltiplos *endpoints* baseados apenas nas limitações de seus parâmetros.

Existem várias restrições que o ASP.NET Core nos oferece, como de intervalos de valores, regex e de tipos, como pode ser observado na [documentação](https://docs.microsoft.com/aspnet/core/fundamentals/routing?view=aspnetcore-5.0#route-constraint-reference), neste artigo focarei em como usar restrições de tipo para criar múltiplos *endpoints* em uma rota.

O código de exemplo está disponível no [github](https://github.com/fernandovmp/RestricaoDeTiposRotas) com um arquivo do Insomnia caso queira usar para acompanhar o artigo.

###   Primeiros passos com restrições de rotas

Imagine a seguinte rota, onde o parâmetro **id** deve ser um número inteiro:

```
GET /produto/{id}
```

Se a definição do método que for manipular a requisição dessa rota for:

```csharp
[HttpGet("/produto/{id}")]
public ActionResult GetById(int id)
```

O que acontece se fizermos uma requisição à `/produto/abc`?

![Request sem restrição](https://dev-to-uploads.s3.amazonaws.com/i/t0veeiec89qt7xj7g1jn.png)

A resposta obtida é `400 Bad Request ` com um erro de validação para o parâmetro **id**, entretanto, mesmo que o valor não seja válid![RestriçãoRotas_Exemplo2](https://dev-to-uploads.s3.amazonaws.com/i/rv8eo0iw9nsy2rc6skmf.png)
 o, em nenhuma hipótese haveria um produto no banco de dados que tivesse como **id** 'abc' o que torna mais adequado dizer que o recurso **não existe** ao invés de dizer que a requisição é inválida. E como podemos fazer para mudar o comportamento e dizer que o recurso não existe?

Para isso, basta adicionar uma restrição ao parâmetro, as restrições de um parâmetro são adicionadas depois do nome do parâmetro na rota separados por dois pontos (`:`), exemplo: `{meuParametro:restricao1:restricao2:restricao3...n}`

Agora, se adicionarmos uma restrição ao parâmetro **id** de que este deve ser do tipo `int`, 

```csharp
[HttpGet("/produto/{id:int}")]
public ActionResult GetById(int id)
```

Ao fazermos novamente uma requisição à `/produto/abc` teremos como resposta um erro 404, isso pois não existe nenhuma rota corresponda a esse *endpoint* já que agora o ASP.NET Core irá apenas reconhecer como uma rota existente se o parâmetro **id** em `/produto/{id}` for um número inteiro. Dessa forma, nossa aplicação não retornará um erro 400 de validação devido ao valor passado não ser correspondente ao tipo do esperado pelo método da *controller*, assim deixamos livre para que outro método possa tratar uma mesma rota `/produto/{id}` que ao invés de receber um inteiro como parâmetro, recebe qualquer outro tipo de dado.

![Request com restrição de tipo](https://dev-to-uploads.s3.amazonaws.com/i/dub1zze9xa9i5d5ix7tu.png)



### Diferentes *endpoints* de acordo com o tipo do parâmetro

Suponha o seguinte cenário, temos na nossa API os dados dos usuários, cada usuário possuí um **id** único no banco de dados além de uma outra identificador também única, que é seu nome de usuário que pode ser alterado no futuro. Em algumas situações, seria difícil que o cliente que consumisse a API tivesse o **id** do usuário para consultar seus dados, sendo mais comodo consultar pelo nome de usuário, então teríamos de ter a seguinte rota:

```
GET /usuarios/{nomeUsuario}
```

Mas o nome de usuário pode ser alterado, então também precisaríamos prover uma forma única e consistente de buscar os dados desse usuário, ou seja, buscar pelo seu **id**, tendo também a rota:

```
GET /usuarios/{id}
```

Como fazemos então para manipularmos essas rotas por métodos diferentes? Basta aplicarmos uma restrição no parâmetro de uma das rotas para que se o valor atender a restrição, seja executado por um método diferente da requisição que não atender.

Para isso, vamos definir que a rota que consulta pelo **id** só existirá para valores onde o parâmetro seja um **Guid** e se não for, será então tratada como a rota para buscar pelo nome de usuário, que será uma **string**, assim teremos os seguintes métodos na *controller*:

```csharp
[HttpGet("/usuarios/{nomeUsuario}")]
public ActionResult GetByUsername(string nomeUsuario);

[HttpGet("/usuarios/{id:guid}")]
public ActionResult GetById(Guid id);
```

Perceba que o `GetById` tem uma restrição no seu parâmetro, o que faz com que mesmo tendo duas rotas para o mesmo nível de especialização da rota `/usuarios`,  elas não serão consideradas ambíguas pois há uma restrição que diferencia ambas.

Fazendo uma requisição para `GET /usuarios/fernando` obtemos a seguinte resposta:

![Request pelo nome de usuário](https://dev-to-uploads.s3.amazonaws.com/i/i7c1pgotofhk1u8osqw4.png)

E ao fazermos uma outra requisição para `GET /usuarios/0336c93f-645b-4756-9615-e3a6c8eeeaf2` temos uma resposta diferente já que ambas as requisições foram tratadas por métodos diferentes.

![Request pelo ID](https://dev-to-uploads.s3.amazonaws.com/i/tz6xrv0urek7cbcjzvyo.png)



### Conclusão

Podemos prover diversos *endpoints*  em uma rota de maneira simples ao restringirmos a existência de um *endpoint* apenas aos valores válidos de seus parâmetros, deixando espaço para tratarmos de maneira diferente a mesma rota para outro domínio de valores, assim nossos recursos ficam mais flexíveis para quem for consumir a API, além de respondermos ao cliente uma informação mais simples quando uma rota com parâmetro inválido é requisitada.