Gerando um cliente Kiota em tempo de build no ASP.NET Core

Gerar o cliente Kiota automaticamente durante o build é a forma mais segura de manter o consumo de uma API ASP.NET Core sincronizado com o contrato publicado. Neste artigo, você vai combinar a geração do documento OpenAPI em tempo de build com um target MSBuild que invoca o Kiota apenas quando a especificação muda.

Por que automatizar o cliente Kiota

A integração entre serviços em aplicações .NET exige um processo eficiente para criar e manter clientes HTTP.

Manter clientes escritos à mão, por outro lado, é trabalhoso e propenso a erros, principalmente quando a API de origem evolui com frequência.

Cada novo endpoint, campo ou código de status precisa ser replicado manualmente no consumidor. Por isso, qualquer esquecimento vira um bug em produção.

O Kiota resolve esse problema porque gera clientes fortemente tipados a partir de documentos OpenAPI.

Quando o cliente Kiota nasce do mesmo build que produz a especificação, o contrato e o consumidor deixam de divergir. Em outras palavras, a fonte da verdade passa a ser o próprio código da API.

O que é o Kiota

O Kiota é uma ferramenta de linha de comando de código aberto, mantida pela Microsoft, que gera clientes de API a partir de documentos OpenAPI.

Ao contrário de abordagens que exigem uma biblioteca diferente para cada API consumida, o Kiota padroniza a experiência com um conjunto de pacotes base reutilizáveis.

O cliente gerado oferece tipagem forte, autocomplete na IDE e acesso completo aos recursos do protocolo HTTP. Além disso, a ferramenta produz apenas o código necessário para a superfície de API selecionada.

O Kiota também suporta linguagens como C#, TypeScript, Go, Java, Python, PHP e Ruby. Assim, ele se torna uma escolha versátil em ambientes poliglotas.

No .NET, todo cliente Kiota depende do pacote Microsoft.Kiota.Bundle, que agrupa abstrações, serialização e adaptadores de transporte HTTP.

Se você ainda não usou a ferramenta, vale ler antes o artigo como usar o Kiota para gerar SDKs de API no .NET, que cobre os comandos básicos.

Geração do documento OpenAPI em tempo de build

A partir do .NET 9, o ASP.NET Core inclui suporte nativo à geração de documentos OpenAPI por meio do pacote Microsoft.AspNetCore.OpenApi.

Por padrão, a aplicação gera esse documento em tempo de execução e o expõe por um endpoint HTTP, como /openapi/v1.json.

Em alguns cenários, porém, o arquivo precisa existir em disco. Pipelines de CI/CD, testes baseados em contrato e geradores de cliente são exemplos típicos.

Nesse caso, o pacote Microsoft.Extensions.ApiDescription.Server permite emitir o arquivo durante o dotnet build. A instalação é simples:

dotnet add package Microsoft.AspNetCore.OpenApi
dotnet add package Microsoft.Extensions.ApiDescription.Server

Com os dois pacotes referenciados, o build da API emite o arquivo OpenAPI automaticamente no diretório de saída do projeto.

Para personalizar o destino, configure a propriedade OpenApiDocumentsDirectory no .csproj da API:

<PropertyGroup>
  <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
  <OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)/../MyClient/OpenApi</OpenApiDocumentsDirectory>
</PropertyGroup>

Essa configuração posiciona o arquivo diretamente no projeto cliente. Dessa forma, o consumidor encontra o artefato pronto no momento do próprio build.

Segundo a documentação oficial de geração de documentos OpenAPI, o valor de OpenApiDocumentsDirectory é relativo ao arquivo de projeto.

A mesma documentação explica que a geração em build executa o ponto de entrada da aplicação com um servidor simulado. Portanto, códigos de inicialização que acessam banco ou configurações sensíveis podem precisar de uma condição extra.

O artigo OpenAPI no ASP.NET Core em tempo de build detalha essas opções com mais profundidade.

O fim do Microsoft.Extensions.ApiDescription.Client

Vale observar que, a partir do .NET 10, a Microsoft descontinuou o pacote Microsoft.Extensions.ApiDescription.Client.

Esse pacote fornecia o item OpenApiReference e o comando dotnet openapi, que geravam clientes durante o build. Agora, projetos que ainda o referenciam recebem avisos de compilação.

A recomendação oficial é migrar para a ferramenta de cada gerador, como o próprio Kiota. Com isso, desaparece a camada intermediária que o pacote oferecia.

Estrutura da solução com API e cliente Kiota

Antes de configurar qualquer target, vale visualizar como os projetos se organizam na solução.

O exemplo deste artigo usa dois projetos lado a lado: a API ASP.NET Core e uma biblioteca chamada MyClient, que hospeda o cliente Kiota.

MySolution/
├── MyApi/
│   └── MyApi.csproj
└── MyClient/
    ├── MyClient.csproj
    ├── OpenApi/
    │   ├── myapi.json
    │   └── myapi.json.hash
    └── Generated/

A pasta OpenApi recebe a especificação emitida pelo build da API. Já a pasta Generated guarda apenas o código que o Kiota produz.

Essa separação deixa claro o que é contrato, o que é código gerado e o que é código escrito pela equipe.

Além disso, ela permite que outros consumidores reaproveitem a mesma biblioteca. Basta referenciar o projeto MyClient em vez de gerar um novo cliente em cada aplicação.

Instalação do Kiota como ferramenta local

Para integrar o Kiota ao pipeline de build, a abordagem recomendada é instalá-lo como ferramenta .NET local. Assim, a versão fica registrada no arquivo .config/dotnet-tools.json, junto ao repositório.

Caso o manifesto de ferramentas ainda não exista, crie-o com o comando abaixo:

dotnet new tool-manifest

Em seguida, adicione o Kiota ao manifesto:

dotnet tool install Microsoft.OpenApi.Kiota

A instalação local garante que toda a equipe e os agentes de CI usem exatamente a mesma versão da ferramenta.

Depois de um dotnet tool restore, o comando dotnet kiota fica disponível sem nenhuma instalação global na máquina.

Essa escolha também evita diferenças sutis no código gerado. Afinal, versões distintas do gerador podem produzir arquivos ligeiramente diferentes e poluir os pull requests.

Configuração do projeto do cliente Kiota

O projeto que vai consumir a API precisa referenciar o pacote de runtime do Kiota:

dotnet add package Microsoft.Kiota.Bundle

Com a dependência instalada, o próximo passo é ensinar o .csproj a invocar o Kiota durante o build.

Para isso, o MSBuild oferece o elemento <Target> com o atributo AfterTargets, que executa ações depois de etapas específicas da compilação.

A estrutura básica de um target que gera o cliente Kiota fica assim:

<Target Name="GenerateKiotaClient" AfterTargets="Build">
  <Exec
    Command="dotnet kiota generate -l CSharp -c MyApiClient -n MyApp.Client -d ./OpenApi/myapi.json -o ./Generated --clean-output --exclude-backward-compatible"
    WorkingDirectory="$(ProjectDir)" />
</Target>

Esse target executa o Kiota depois de cada build bem-sucedido e regenera todo o código do cliente.

Parâmetros que definem o cliente Kiota

O parâmetro -l define a linguagem de saída, enquanto -c e -n escolhem o nome da classe raiz e o namespace.

O parâmetro -d aponta para o arquivo OpenAPI que o projeto da API produziu.

Já o parâmetro -o define o diretório de saída do código gerado.

A flag --clean-output remove arquivos obsoletos do diretório antes de gerar os novos. Dessa forma, endpoints removidos da API também desaparecem do cliente.

Por fim, a flag --exclude-backward-compatible reduz o tamanho do cliente. Ela remove sobrecargas de compatibilidade retroativa que raramente fazem falta em projetos internos.

Detecção de mudanças via hash no cliente Kiota

Executar o Kiota a cada build custa tempo, principalmente quando a especificação OpenAPI continua igual.

A solução é comparar o hash atual do arquivo OpenAPI com um hash salvo em disco. Então, o target só invoca a geração quando os valores divergem.

O MSBuild oferece a task <FileHash> para calcular checksums de arquivos. Além disso, o atributo Condition controla quando cada task roda.

A abordagem completa segue este padrão no .csproj:

<Target Name="GenerateKiotaClient" AfterTargets="Build">

  <!-- Calcula o hash atual do arquivo OpenAPI -->
  <FileHash Files="$(ProjectDir)OpenApi/myapi.json" Algorithm="SHA256">
    <Output TaskParameter="Hash" ItemName="CurrentHash" />
  </FileHash>

  <!-- Lê o hash salvo anteriormente, se existir -->
  <ReadLinesFromFile File="$(ProjectDir)OpenApi/myapi.json.hash"
                     Condition="Exists('$(ProjectDir)OpenApi/myapi.json.hash')">
    <Output TaskParameter="Lines" ItemName="SavedHashLines" />
  </ReadLinesFromFile>

  <PropertyGroup>
    <CurrentHashValue>@(CurrentHash)</CurrentHashValue>
    <SavedHashValue>@(SavedHashLines)</SavedHashValue>
  </PropertyGroup>

  <!-- Executa o Kiota apenas se o hash mudou -->
  <Exec
    Command="dotnet kiota generate -l CSharp -c MyApiClient -n MyApp.Client -d ./OpenApi/myapi.json -o ./Generated --clean-output --exclude-backward-compatible"
    WorkingDirectory="$(ProjectDir)"
    Condition="'$(CurrentHashValue)' != '$(SavedHashValue)'" />

  <!-- Persiste o hash atual para comparação futura -->
  <WriteLinesToFile
    File="$(ProjectDir)OpenApi/myapi.json.hash"
    Lines="$(CurrentHashValue)"
    Overwrite="true"
    Condition="'$(CurrentHashValue)' != '$(SavedHashValue)'" />

</Target>

Com essa configuração, o Kiota só roda quando alguém modifica de fato o documento OpenAPI.

Repare que o hash só muda depois que a geração termina. Assim, se o comando falhar, o próximo build tenta gerar o cliente Kiota novamente.

Adicione o arquivo .hash ao controle de versão. Desse modo, o pipeline de CI também aproveita a detecção incremental.

Quando o arquivo OpenAPI ainda não existe

Em um clone novo do repositório, o arquivo OpenAPI pode não existir antes do primeiro build da API.

Nesse caso, condicione o target com Condition="Exists('caminho/do/arquivo.json')". Com isso, o build do consumidor não falha enquanto a especificação ainda não foi emitida.

Registro do cliente Kiota com injeção de dependência

Com o código gerado disponível, registre o cliente Kiota no contêiner de injeção de dependência por meio de métodos de extensão.

O padrão recomendado usa IHttpClientFactory para gerenciar o ciclo de vida do HttpClient. Por isso, o tutorial oficial cria uma fábrica que recebe o HttpClient pelo construtor:

using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;
using MyApp.Client;

public class MyApiClientFactory
{
    private readonly IAuthenticationProvider _authenticationProvider;
    private readonly HttpClient _httpClient;

    public MyApiClientFactory(HttpClient httpClient)
    {
        // API interna sem autenticação neste exemplo
        _authenticationProvider = new AnonymousAuthenticationProvider();
        _httpClient = httpClient;
    }

    public MyApiClient CreateClient() =>
        new(new HttpClientRequestAdapter(_authenticationProvider, httpClient: _httpClient));
}

Em seguida, um método de extensão concentra todo o registro:

public static class MyApiClientServiceCollectionExtensions
{
    public static IServiceCollection AddMyApiClient(
        this IServiceCollection services,
        string baseUrl)
    {
        // Registra os handlers padrão do Kiota no contêiner
        services.AddKiotaHandlers();

        services.AddHttpClient<MyApiClientFactory>(client =>
        {
            client.BaseAddress = new Uri(baseUrl);
        }).AttachKiotaHandlers();

        services.AddTransient(sp =>
            sp.GetRequiredService<MyApiClientFactory>().CreateClient());

        return services;
    }
}

Os métodos AddKiotaHandlers() e AttachKiotaHandlers() vêm do tutorial oficial de injeção de dependência com Kiota. Você os copia para o projeto, porque eles não fazem parte do pacote.

Eles adicionam os middlewares do Kiota ao pipeline do HttpClient, como retry, redirecionamento e tratamento de erros.

O registro em Program.cs fica com uma única linha:

builder.Services.AddMyApiClient("https://my-api.example.com");

A partir desse ponto, o MyApiClient fica disponível para injeção em qualquer serviço, controller ou handler da aplicação.

Fluxo completo de trabalho

O fluxo resultante é totalmente automatizado e se comporta da seguinte forma a cada dotnet build.

Primeiro, o projeto da API gera o documento OpenAPI atualizado com Microsoft.Extensions.ApiDescription.Server e o grava no diretório configurado no projeto cliente.

Depois, o projeto cliente compara o hash do arquivo recebido com o hash persistido da última geração.

Caso haja divergência, o Kiota regenera o cliente e o target salva o novo hash.

Em builds seguintes sem alteração na API, o target detecta hashes iguais e ignora o Kiota. Consequentemente, o tempo total de compilação cai.

Esse modelo é especialmente valioso em monorepos, onde API e cliente convivem no mesmo repositório. Afinal, qualquer mudança de contrato chega ao consumidor sem intervenção manual.

Para garantir a ordem correta, o projeto cliente pode referenciar o projeto da API com ReferenceOutputAssembly="false". Assim, o MSBuild compila a API antes, mas não acopla os assemblies.

Adicionando o target de limpeza

Para manter a consistência ao executar dotnet clean, vale adicionar um target que remova os arquivos gerados:

<Target Name="CleanKiotaClient" AfterTargets="CoreClean">
  <RemoveDir Directories="$(ProjectDir)Generated" />
  <Delete Files="$(ProjectDir)OpenApi/myapi.json.hash" />
</Target>

Esse target descarta tanto o código gerado quanto o hash persistido. Portanto, o próximo build força uma regeneração limpa do cliente Kiota.

Versionar ou não o cliente Kiota gerado

Uma dúvida recorrente é se o código gerado pelo Kiota deve entrar no repositório.

Commitar os arquivos gerados dá visibilidade imediata às mudanças de contrato nos pull requests. Além disso, facilita a revisão e dispensa a geração em pipelines que não executam dotnet build.

Não commitar, por outro lado, simplifica o repositório. No entanto, o CI passa a depender sempre do build completo antes de qualquer etapa que use o cliente.

A própria nota de descontinuação da Microsoft sugere commitar o código gerado ou rodar a geração em uma etapa própria de build.

Para projetos internos em monorepo, a recomendação é commitar o diretório Generated e tratá-lo como um artefato versionado e rastreável.

Boas práticas

Nunca escreva código manual dentro do diretório de saída do Kiota, porque a próxima geração vai sobrescrever qualquer modificação.

Extensões, provedores de autenticação e registros de DI, portanto, ficam fora da pasta Generated.

O arquivo de especificação versionado funciona como contrato formal entre produtor e consumidor. Por isso, ele precisa refletir fielmente o estado atual da API.

Em ambientes com vários consumidores, publique o arquivo OpenAPI como artefato de CI. Assim, projetos externos geram o próprio cliente Kiota sem acesso ao código-fonte da API.

Para APIs com autenticação, troque o AnonymousAuthenticationProvider por um provedor adequado. O pacote Microsoft.Kiota.Authentication.Azure, por exemplo, integra o cliente ao Azure Identity.

Se a geração falhar, use --log-level debug no comando. Também ajuda definir ConsoleToMSBuild="true" na task <Exec>, que envia a saída para o log do build.

Conclusão

A combinação da geração de OpenAPI em build do ASP.NET Core com targets MSBuild do Kiota cria um pipeline de integração totalmente automatizado.

A detecção de mudanças via hash elimina regenerações desnecessárias. Dessa forma, o processo continua rápido mesmo em projetos grandes.

O resultado é um fluxo em que toda alteração de contrato chega ao cliente Kiota sozinha, com rastreabilidade no controle de versão e sem etapas manuais.

Se quiser comparar com outras formas de documentar e expor a especificação, veja também o artigo sobre documentação de APIs com OpenAPI, Swagger e Scalar.

FAQ

O que acontece se o arquivo OpenAPI não existir durante o build do cliente?

O target pode receber a condição Exists('caminho/do/arquivo.json') para não falhar enquanto o arquivo ainda não foi gerado.

É possível usar o Kiota com APIs externas fora do repositório?

Sim. O parâmetro -d do kiota generate aceita tanto caminhos locais quanto URLs de especificações públicas ou internas.

O Kiota suporta OAuth2 e Microsoft Entra ID?

Sim. O pacote Microsoft.Kiota.Authentication.Azure oferece um provedor baseado no Azure Identity, compatível com os fluxos OAuth2 do Entra ID.

Por que o Microsoft.Extensions.ApiDescription.Client foi descontinuado no .NET 10?

O pacote tinha pouca manutenção e abstrações muito acopladas a geradores específicos. Por isso, a Microsoft delegou a cada gerador o próprio fluxo de configuração.

O código gerado pelo Kiota é compatível com Native AOT?

O suporte a Native AOT no Kiota ainda evolui. Para esse cenário, confira a documentação oficial da versão em uso, porque a compatibilidade varia conforme os recursos utilizados.

Como depurar a geração do cliente em caso de falha?

Use --log-level debug no kiota generate e ConsoleToMSBuild="true" na task <Exec> para ver a saída detalhada no log do build.