# Kiota, dependency injection and resilience

**Date:** 2025-02-12  
**Author:** Kees C. Bakker  
**Categories:** .NET / C#  
**Original:** https://keestalkstech.com/kiota-dependency-injection-and-resilience/

![Kiota, dependency injection and resilience](https://keestalkstech.com/wp-content/uploads/2025/02/arvid-skywalker-Kdio_Qt7PM4-unsplash.jpg)

---

My new years resolution is to generate HTTP clients for services with an Open API spec. [Microsoft released Kiota](https://learn.microsoft.com/en-us/openapi/kiota/overview), a tool that generates C# classes from specs. In this blog, I'll be looking at dependency injection and [resilience](https://keestalkstech.com/implementing-http-resilience-by-microsoft/#comments).

## Caveats

There are many (many!) ways to implement and configure Kiota clients. In this blog I'll limit myself to the following:

- Services don't use any form of authentication.
- My clients are generated into the same folder as sub folders.
- The HTTP clients must use the [Microsoft.Extensions.Http.Resilience](https://www.nuget.org/packages/Microsoft.Extensions.Http.Resilience) package to implement resilience. This is borrowed from [Implementing HTTP Resilience by Microsoft](https://keestalkstech.com/implementing-http-resilience-by-microsoft/).
- I want a cookie cutter approach that makes it easier to replicate the most common pattern.

## One script to rule them all

First, we'll create a script to generate our clients. In this example we'll generate 2 clients; one based on the [Swagger IO Pet Store example](https://petstore.swagger.io/) and one based on [a custom spec](https://github.com/KeesCBakker/keestalkstech-code-gallery/blob/main/12.kiota-and-resilience/httpstatus-open-api.yml) for the [httpstat.us service](https://httpstat.us/).

```
﻿#! "dotnet-script"
#nullable enable

// 1. Install Kiota
Console.WriteLine("🔄 Installing (or updating) Kiota tool...");
Run("dotnet tool install --global Microsoft.OpenApi.Kiota", Environment.CurrentDirectory, quiet: false);

// 2. Make sure HTTP project is present
Console.WriteLine("🔄 Ensure HTTP projects is configured correctly...");
if (!Directory.Exists(HttpClientsDir))
  throw new DirectoryNotFoundException($"❌ HTTP client project folder not found at '{HttpClientsDir}'");

// 3. Install packages
Run("dotnet add package Microsoft.Extensions.DependencyInjection");
Run("dotnet add package Microsoft.Extensions.DependencyInjection.Abstractions");
Run("dotnet add package Microsoft.Extensions.Options");
Run("dotnet add package Microsoft.Extensions.Http.Resilience");
Run("dotnet add package Microsoft.Extensions.Resilience");
Run("dotnet add package Microsoft.Kiota.Bundle");

// 4. Generate Kiota clients
Console.WriteLine("🔄 Generating Kiota clients...");

GenerateKiotaClient(
    openApiUrl: "https://petstore.swagger.io/v2/swagger.json",
    clientName: "PetStore",
    includePath: "/pet/**"
);

GenerateKiotaClient(
    openApiUrl: "httpstatus-open-api.yml",
    clientName: "HttpStatus"
);

// -- Config ---
static string Namespace => "Ktt.Resilience.Clients.Kiota";
static string HttpClientsDir => Path.GetFullPath(Namespace);
static string Folder = "HttpClients";

// --- Helpers ---
void Run(string fullCommand, string? workingDir = null, bool quiet = true)
{
  var parts = fullCommand.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
  var psi = new ProcessStartInfo
  {
    FileName = parts[0],
    Arguments = parts.Length > 1 ? parts[1] : "",
    WorkingDirectory = workingDir ?? HttpClientsDir,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false
  };

  using var proc = Process.Start(psi)
      ?? throw new Exception($"Failed to start: {parts[0]}");

  string stdout = proc.StandardOutput.ReadToEnd();
  string stderr = proc.StandardError.ReadToEnd();
  proc.WaitForExit();

  if (!quiet && !string.IsNullOrWhiteSpace(stdout))
    Console.WriteLine(stdout);

  if (!string.IsNullOrWhiteSpace(stderr))
    Console.Error.WriteLine(stderr);

  if (proc.ExitCode != 0)
    throw new Exception($"{parts[0]} exited with code {proc.ExitCode}");
}

void GenerateKiotaClient(string openApiUrl, string clientName, string includePath = "/**")
{
  if (!openApiUrl.Contains("://"))
    openApiUrl = Path.Combine("..", openApiUrl);

  var outputDir = string.IsNullOrEmpty(Folder) ? clientName : $"{Folder}/{clientName}";
  var @namespace = string.IsNullOrEmpty(Folder) ? Namespace : $"{Namespace}.{Folder}";

  Console.WriteLine($"▶ Generating client '{clientName}'...");
  Run($"""
    kiota generate 
      --openapi {openApiUrl} 
      --language CSharp 
      --output {outputDir} 
      --namespace-name {@namespace}.{clientName} 
      --class-name {clientName}Client 
      --exclude-backward-compatible 
      --clean-output 
      --clear-cache 
      --include-path {includePath}
  """.ReplaceLineEndings(" ").Trim(), quiet: false);

  Console.WriteLine($"✔ Generated client: '{clientName}'");
}
```

We can generate the client with the [.NET Script tool](https://github.com/dotnet-script/dotnet-script):

```sh
dotnet tool install -g dotnet-script
dotnet script ./generate-kiota-clients.csx
```

I've used a [Powershell script](https://github.com/KeesCBakker/keestalkstech-code-gallery/blob/ad617c65cc5c7e15de33defbc8664e5ae103ecef/12.http-and-resilience/generate-kiota-clients.ps1) in a previous version of this article. The main drawback is the dependency upon PowerShell which is technically cross-platform, but functionally a big hurdle.

## Kiota & Dependency Injection

Now, the official guide on [dependency injection](https://learn.microsoft.com/en-us/openapi/kiota/tutorials/dotnet-dependency-injection#create-extension-methods) states that we should add extension methods and create a factory to create our final client. As most of my services will not include authentication, it does not make sense to create a factory for every client. Instead I'll provide some reusable extension methods.

First, we'll need to add the extension methods mentioned in the docs:

```csharp
public static IServiceCollection AddKiotaHandlers(this IServiceCollection services)
{
    // Dynamically load the Kiota handlers from the Client Factory
    var kiotaHandlers = KiotaClientFactory.GetDefaultHandlerActivatableTypes();

    // And register them in the DI container
    foreach (var handler in kiotaHandlers)
    {
        services.AddTransient(handler);
    }

    return services;
}

public static IHttpClientBuilder AttachKiotaHandlers(this IHttpClientBuilder builder)
{
    // Dynamically load the Kiota handlers from the Client Factory
    var kiotaHandlers = KiotaClientFactory.GetDefaultHandlerActivatableTypes();
    // And attach them to the http client builder
    foreach (var handler in kiotaHandlers)
    {
        builder.AddHttpMessageHandler((sp) => (DelegatingHandler)sp.GetRequiredService(handler));
    }

    return builder;
}
```

The script in the docs references some obsolete methods. I've replaced them with the recommended methods.

### More extension methods ahead

Now, we can create 2 extension methods:

1. A method that creates HTTP client with configuration, resilience and Kiota handlers added to them. Let's name it `AddKiotaHttpClientWithResilienceHandler` (sorry for the long name).
2. A method that takes the HTTP client and creates a `HttpClientRequestAdapter`, named `AddKiotaClient`.

### A named HTTP client

I have not found a way to bind the HTTP client directly to Kiota client class. The next best thing is to created a *named* HTTP client that we can use later on.

```csharp
public static IHttpStandardResiliencePipelineBuilder AddKiotaHttpClientWithResilienceHandler(
    this IServiceCollection services,
    string sectionName
    )
{
    services.AddKiotaHandlers();

    var httpClientBuilder = services
        // bind the configuration section
        .AddNamedOptionsForHttpClient<HttpClientOptions>(sectionName)
        // add the HttpClient itself, configure the base URL
        .AddHttpClient(sectionName, (serviceProvider, client) =>
        {
            var monitor = serviceProvider.GetRequiredService<IOptionsMonitor<HttpClientOptions>>();
            var config = monitor.Get(sectionName);
            if (config?.BaseUrl != null)
            {
                client.BaseAddress = new Uri(config.BaseUrl);
            }
        });

    var resilienceBuilder = httpClientBuilder.AddStandardResilienceHandler();
    httpClientBuilder.AttachKiotaHandlers();

    return resilienceBuilder;
}
```

This code looks very similar to the code of [Dependency injection 2: http client with resilience pipeline](https://keestalkstech.com/implementing-http-resilience-by-microsoft/#dependency-injection-2-http-client-with-resilience-pipeline). Here we *do not* provide a generic class to the .`AddHttpClient`, we just add a name to the client. Note how we `AttachKiotaHandlers` to our `resilienceBuilder`.

This code allows us to get the client from `IServiceProvider sp`:

```charp
var factory = sp.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient(sectionName);
```

### A Kiota Client factory

Next, we can build a Kiota client with an anonymous authentication provider:

```csharp
public static IHttpStandardResiliencePipelineBuilder AddKiotaClient<TClass>(
    this IServiceCollection services,
    string sectionName
)
    where TClass : class
{
    var resilienceBuilder = services.AddKiotaHttpClientWithResilienceHandler(sectionName);

    services
        .AddSingleton<AnonymousAuthenticationProvider>()
        .AddTransient(sp =>
        {
            var factory = sp.GetRequiredService<IHttpClientFactory>();
            var client = factory.CreateClient(sectionName);

            var provider = sp.GetRequiredService<AnonymousAuthenticationProvider>();

            var adapter = new HttpClientRequestAdapter(provider, httpClient: client);
            var instance = Activator.CreateInstance(typeof(TClass), adapter);
            return (TClass)instance!;
        });

    return resilienceBuilder;
}
```

Unfortunately, it is not possible to constraint the generic TClass to have a constructor that takes a `IRequestAdapter`. Let's do the next best thing and use the `Activator` that uses reflection to create the object. If the constructor is missing on the object, we'll get a `System.MissingMethodException`.

## Hook it up

The last things we should do is configure those classes in our dependency injection:

```csharp
services.AddKiotaClient<PetStoreClient>("HttpClients:PetStore");
services.AddKiotaClient<HttpStatusClient>("HttpClients:HttpStatus")
    .Configure(config =>
    {
        config.Retry.OnRetry = async args =>
        {
            Console.WriteLine($"Retry {args.AttemptNumber}: Retrying after {args.RetryDelay} due to {args.Outcome.Result?.StatusCode}");
            await Task.CompletedTask;
        };
    });
```

And we can add the following to our configuration:

```json
{
  "HttpClients": {
    "PetStore": {
      "BaseUrl": "https://petstore.swagger.io/v2"
    },
    "HttpStatus": {
      "BaseUrl": "https://httpstat.us",
      "Retry": {
        "MaxRetryAttempts": 4
      }
    }
  }
}
```

## Conclusion

It is not very hard to generate your C# client based on Open API specs and add it to your application in such a way that you have both configuration and resilience. I'm still a bit on the fence when I look at the result that is generated by Kiota. It looks pretty verbose. [I agree with some of the hate Kiota gets online](https://news.ycombinator.com/item?id=41247083#:~:text=Which%20sucks%2C%20because%20the%20alternatives%20aren%E2%80%99t%20much%20better.%20Maybe%20I%E2%80%99m%20missing%20something%20but%20the%20swagger/openapi%20client%20generators%20shouldn%E2%80%99t%20be%20this%20bad%20as%20a%20rule%E2%80%A6).

[Others point out](https://stenbrinke.nl/blog/openapi-api-client-generation/#:~:text=Ending%20up%20with%20pretty%20API%20Clients) that the way the generated code looks, might have more to do with the specification you used. I ended up adding more definitions to my YML and that already made the client look way better 😮‍💨.

In Node.js / TypeScript I use [swagger-typescript-api](https://www.npmjs.com/package/swagger-typescript-api), which for now feels a bit easier to use.

The code is in GitHub, so check it out: [code gallery / 12. HTTP and resilience](https://github.com/KeesCBakker/keestalkstech-code-gallery/tree/main/12.http-and-resilience).

## Changelog

- 2025-06-02: Improved the client generation script. It is now cross-platform using a csx-script.
- 2025-02-12: Initial article.
