Se for necessário consumir os dados de uma API, como vimos, este processo pode ser facilitado com o uso da biblioteca Flurl. Mesmo não sendo complexo, ainda irá requerer códigos redundantes e qualquer alteração na API, irá necessitar de modificações no client. Entretanto, este processo pode ser facilitado e praticamente automatizado com uso das ferramentas de OpenAPI do .NET.
OpenAPI
Quase sinônimo de Swagger, OpenAPI é uma série de especificações definidas pela comunidade em conjunto com a Open API Iniciative, para descrição e documentação de APIs.
Totalmente independente de uma linguagem de programação, a especificação OpenAPI permite que tanto humanos quanto aplicações compreendam e explorem um serviço sem necessitar ter acesso ao seu código fonte ou documentação adicional.
Quando corretamente definida, um consumidor poderá interagir com o serviço utilizando uma implementação simples ou nenhuma dependendo das ferramentas utilizadas.
Microsoft.dotnet-openapi
Por permitir definir todos os aspectos da API, algumas ferramentas conseguem gerar clients ao analisar uma especificação OpenAPI. Uma ferramenta clássica que faz este tipo de procedimento é o Swagger, que disponibiliza uma interface para uma análise visual e testável da especificação.
Já outras ferramentas geram códigos que podem ser adicionados à uma aplicação. Com isso, o desenvolvedor não precisa se preocupar em implementar o client da API no projeto. Para o .NET uma ferramenta clássica que realiza este tipo de procedimento é a NSwag, entretanto, na versão 5 desta biblioteca, a Microsoft disponibiliza a ferramenta Microsoft.dotnet-openapi que também realiza este procedimento.
Esta global tool cria todo o código necessário para consumir os dados de uma API de acordo com a especificação OpenAPI indicada. Caso haja alguma alteração na especificação, ela também pode ser utilizada para atualizar os códigos. Com isso, o desenvolvedor não precisa digitar nenhuma linha para poder consumir os dados da API.
Api de exemplo
Para exemplificar a ferramenta Microsoft.dotnet-openapi, irei utilizar a especificação OpenAPI gerada pela API criada com a biblioteca Carter, pois ela está completa e não requer nenhuma modificação.
Caso queira utilizá-la você pode obter o projeto no meu Github.
Gerando os códigos do client
Neste exemplo o client será definido em uma aplicação à parte da API, entretanto, nada impede que ele seja definido no mesmo projeto, ou dentro da mesma solução.
Assim, inicialmente será criado um projeto console:
dotnet new console -n OpenAPiClient
Em seguida, adicione a global tool:
dotnet tool install -g Microsoft.dotnet-openapi
Com isso, no nível do arquivo de configuração do projeto (*.csproj
), execute o comando abaixo:
dotnet openapi add url https://localhost:5001/openapi --output-file CarterApi.json
Por ser uma URL, acima é utilizado o add url
. Caso queira especificar um arquivo de configuração, pode ser utilizado o comando add file
, e.g.:
dotnet openapi add file CarterApi.json
O comando add url
irá baixar da url informada a especificação OpenAPI e irá salvar uma cópia dela no projeto:
E no arquivo de configuração dele, será adicionado a configuração abaixo:
<ItemGroup>
<OpenApiReference Include="CarterApi.json" SourceUrl="https://localhost:5001/openapi" />
</ItemGroup>
Assim, caso queira, isso pode ser definido manualmente.
Se não quiser utilizar a ferramenta de linha de comando, no Visual Studio Community (Professional e Enterprise), isso também pode ser definido com a opção “Connected Service” (Add > Connected Service, ao clicar com o botão direito do mouse sobre projeto).
Com esta configuração, a ferramenta já criará os códigos necessários para se conectar à API:
class Program
{
static async Task Main(string[] args)
{
using var httpClient = new HttpClient();
var apiClient = new CarterApiClient("https://localhost:5001", httpClient);
await apiClient.PostPessoaAsync(new Pessoa{
Id = 1,
Nome = "Carlos",
Idade = 33
});
await apiClient.PostPessoaAsync(new Pessoa{
Id = 2,
Nome = "Maria",
Idade = 33
});
var pessoas = await apiClient.GetPessoaAsync();
foreach(var pessoa in pessoas)
{
Console.WriteLine($"{pessoa.Id} - {pessoa.Nome} - {pessoa.Idade}");
}
await apiClient.PutPessoaAsync(1, new Pessoa{
Id = 1,
Nome = "José",
Idade = 12
});
var jose = await apiClient.GetPessoaByIdAsync(1);
Console.WriteLine($"{jose.Id} - {jose.Nome} - {jose.Idade}");
await apiClient.DeletePessoaAsync(1);
pessoas = await apiClient.GetPessoaAsync();
foreach(var pessoa in pessoas)
{
Console.WriteLine($"{pessoa.Id} - {pessoa.Nome} - {pessoa.Idade}");
}
}
}
O nome do client sempre será o mesmo nome do arquivo de configuração definido no projeto, seguindo de Client
:
var apiClient = new CarterApiClient("https://localhost:5001", httpClient);
O nome padrão é OpenApiClient
.
Note que ele também criou classes equivalentes as consumidas pela api:
await apiClient.PostPessoaAsync(new Pessoa{
Id = 1,
Nome = "Carlos",
Idade = 33
});
Por fim, o nome dos métodos serão criados de acordo com o atributo operationId
da operação definida na especificação do OpenAPI:
{
"openapi": "3.0.1",
"info": {
"title": "Carter <3 OpenApi",
"version": "3.0.0"
},
"paths": {
"/pessoa": {
"post": {
"tags": [
"Pessoa"
],
"description": "Adiciona uma pessoa",
"operationId": "Pessoa_PostPessoa",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pessoa"
}
}
}
},
"responses": {
"200": {
"description": "Pessoa adicionada"
}
}
}
},
}
}
Atualizando configuração
Caso a configuração esteja definindo um arquivo do disco. Este pode ser gerado pelo próprio projeto ou outro da solução. Sempre que for modificado, a ferramenta irá gerar um novo client.
Se for o arquivo de uma API online, como no caso deste artigo, quando ela for modificada, é necessário atualizar o arquivo do projeto. Isso pode ser feito com o comando refresh
:
dotnet openapi refresh https://localhost:5001/openapi
E caso queria, a configuração pode ser removida com o comando remove
:
dotnet openapi remove CarterApi.json
Finalizando
O Microsoft.dotnet-openapi é uma ótima ferramenta para a geração de clients de APIs especificadas com o OpenAPI. O seu uso facilita e muito o desenvolvimento, permitindo que o desenvolvedor não precisa se preocupar com a criação de client.
A sua única desvantagem é que não fornece muitas opções de customização do código gerado. Caso queira adicionar algum recurso customizado ao client, é necessário criar uma partial classe e adicionar o recurso nela. Nunca altere o código gerado pela ferramenta, pois ele pode ser recriado quando a API for atualizada.