Kontent.Ai.Delivery.SourceGeneration 20.0.1

dotnet add package Kontent.Ai.Delivery.SourceGeneration --version 20.0.1
                    
NuGet\Install-Package Kontent.Ai.Delivery.SourceGeneration -Version 20.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kontent.Ai.Delivery.SourceGeneration" Version="20.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kontent.Ai.Delivery.SourceGeneration" Version="20.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Kontent.Ai.Delivery.SourceGeneration" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kontent.Ai.Delivery.SourceGeneration --version 20.0.1
                    
#r "nuget: Kontent.Ai.Delivery.SourceGeneration, 20.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kontent.Ai.Delivery.SourceGeneration@20.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kontent.Ai.Delivery.SourceGeneration&version=20.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Kontent.Ai.Delivery.SourceGeneration&version=20.0.1
                    
Install as a Cake Tool

Kontent.ai Delivery SDK for .NET

Stable Latest Downloads

The official .NET SDK for the Kontent.ai Delivery API, enabling you to retrieve content from your Kontent.ai projects with a modern, type-safe, and highly extensible client library.

Building an ASP.NET Core app? Check out Kontent.ai ASP.NET Core Extensions — a companion package that adds a <rich-text> tag helper for rendering Kontent.ai rich text in Razor views (with full IHtmlResolver integration), an <img-asset> tag helper for responsive images with automatic srcset/sizes, webhook signature validation middleware, and cache invalidation straight from a webhook notification.

Table of Contents

Installation

Install the SDK via NuGet Package Manager:

dotnet add package Kontent.Ai.Delivery

Or via the Package Manager Console:

Install-Package Kontent.Ai.Delivery

Optional packages:

Package Purpose
Kontent.Ai.Delivery.Caching FusionCache-backed memory and hybrid caching
Kontent.Ai.Delivery.SourceGeneration Compile-time type provider via source generation
dotnet add package Kontent.Ai.Delivery.Caching
dotnet add package Kontent.Ai.Delivery.SourceGeneration

The SDK targets net10.0, and all of these packages ship on one version. See the changelog for what each release changed.

Upgrade Guide

Upgrade guides are kept one per major under docs/upgrade/; skipping a major means reading them in sequence.

  • Coming from 19.x — read 19 → 20. The move to .NET 10 and the builder registration are the work; two behaviour changes compile unchanged and are listed first.
  • Coming from 18.x — read 18 → 19 first, then 19 → 20.

Quick Start

You need the package installed, .NET 10, and your environment ID — in Kontent.ai, under Environment settings → Environment ID. Swap homepage for a codename that exists in that environment.

using Kontent.Ai.Delivery;

await using var client = DeliveryClient.Create(delivery =>
    delivery.Options.Configure(options => options.EnvironmentId = "<your-environment-id>"));

var result = await client.GetItem("homepage").ExecuteAsync();

if (result.IsSuccess)
{
    Console.WriteLine(result.Value.Item.System.Name);
}
else
{
    Console.WriteLine($"{(int)result.StatusCode}: {result.Error?.Message}");
}

A call returns a result rather than throwing, so IsSuccess is the check every time — see Error Handling.

This is the standalone form, which suits a console app, a script or a test. In an application, register the client in your container instead: Setting Up the Delivery Client.

The quick start reads only system metadata. Element values are reachable without a model through IDynamicElements, but generated records are the recommended path: they give compile-time names, typed element values and automatic type filtering. Generate them with the model generator, then query them with GetItem<Article>(...) - see Content Models and Querying. Typed queries are the baseline throughout the guides below.

Documentation

This README covers installation, registration and configuration. Everything else lives beside it:

Guide What it answers
Querying Items, types, taxonomies, used-in lookups, filtering, ordering, projection, paging, languages, and what a result carries
Content Models Generated records, source-generated type resolution, linked items, dynamic access when the type is unknown until runtime
Caching Memory and hybrid caches, keys, expiration, dependency keys, webhook invalidation, purging, multi-tenancy
Rich Text Link and embedded-content resolvers, inline images, custom HTML nodes, reading blocks
Assets and Images Renditions, a custom asset domain, and ImageUrlBuilder transformations
Multiple Clients Named clients, preview vs production, multi-tenant and multi-brand setups
Performance Query shaping, rate limits, parallelism, monitoring
Extensibility Custom type providers, and the conventions that map elements to properties
Architecture How the SDK is put together, one invariant per boundary - start here to contribute
Upgrade Guides One per major: 18 → 19, 19 → 20

Setting Up the Delivery Client

The SDK is designed to work with .NET's dependency injection container. Register the IDeliveryClient in your Program.cs or Startup.cs:

Basic Registration

services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
    options.EnvironmentId = "your-environment-id";
}));

Registration from Configuration

// appsettings.json
{
  "DeliveryOptions": {
    "EnvironmentId": "your-environment-id",
    "UsePreviewApi": false
  }
}

// Program.cs
services.AddDeliveryClient(delivery => delivery.Options.BindConfiguration("DeliveryOptions"));        // in a host, from the container's IConfiguration
services.AddDeliveryClient(delivery => delivery.Options.Bind(configuration.GetSection("MyDeliverySection"))); // or a section you hold

The default section name is available as DeliveryOptions.DefaultConfigurationSectionName, so tooling that resolves the SDK's configuration from the same sources does not have to hard-code it.

Options is the client's OptionsBuilder<DeliveryOptions>, so everything the options system offers is there: Configure, Configure<TDependency>, Bind, BindConfiguration, PostConfigure, Validate. Binding is change-token backed: edits to the underlying source are picked up through IOptionsMonitor<DeliveryOptions> without rebuilding the container. Binding from configuration and customizing the pipeline are two steps on the same builder:

services.AddDeliveryClient(delivery =>
{
    delivery.Options.BindConfiguration("DeliveryOptions");
    delivery.TuneRetry(retry => retry.MaxRetryAttempts = 5);
});

Registration from Other DI Services

Use Options.Configure<IServiceProvider> when Delivery options need values from other registered services:

services.Configure<SiteOptions>(configuration.GetSection("Site"));

services.AddDeliveryClient(delivery => delivery.Options.Configure<IServiceProvider>((options, sp) =>
{
    var site = sp.GetRequiredService<IOptions<SiteOptions>>().Value;
    options.EnvironmentId = site.EnvironmentId;
}));

The callback must not resolve IDeliveryClient, IOptions<DeliveryOptions> or anything that depends on them: doing so re-enters the options factory, and the container recurses without bound.

API Mode Helpers

The members that set more than one property come as extension methods on DeliveryOptions, usable inside Configure and on any instance:

services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
    options.EnvironmentId = "your-environment-id";
    options.UsePreviewApi("your-preview-api-key");   // or UseProductionApi(), UseProductionApi(secureAccessApiKey), UseCustomEndpoint(url)
}));

Nothing to register. When your models carry the [ContentTypeCodename] attribute and the models project references Kontent.Ai.Delivery.SourceGeneration, the generator emits a GeneratedTypeProvider at compile time and the SDK discovers it at runtime, searching the entry assembly and its references:

services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
    options.EnvironmentId = "your-environment-id";
}));

Keep the attributed models in a single project for auto-discovery to be predictable. If they are split across compilations on purpose, register an ITypeProvider explicitly instead. The Content Models guide covers what the generator produces and the diagnostics it reports.

A model must be a class or a record class. The SDK hydrates elements on the instance it deserialized, so a struct would be copied and its values lost — the generator reports KDSG003, and the client throws NotSupportedException if a struct reaches it another way.

Registering a Custom Type Provider

To override the auto-discovered provider, register your own — before the client, or on the builder's Services. The SDK registers its default with TryAddSingleton, so yours wins either way:

services.AddDeliveryClient(delivery =>
{
    delivery.Services.AddSingleton<ITypeProvider, MyCustomTypeProvider>();
    delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
});

Without Dependency Injection

For console applications, scripts, or anywhere a container is not available, DeliveryClient.Create takes the same builder as AddDeliveryClient and runs the same registration inside a private container the client owns:

await using var client = DeliveryClient.Create(delivery => delivery.Options.Configure(o => o.EnvironmentId = "your-environment-id"));

// Anything the container path can do, this can do - caching, resilience, an explicit type provider.
await using var cachedClient = DeliveryClient.Create(delivery =>
{
    delivery.Options.Configure(o => o.EnvironmentId = "your-environment-id");
    delivery.UseMemoryCache(o => o.DefaultExpiration = TimeSpan.FromMinutes(30));
});

// Or from a pre-built options instance.
await using var fromOptions = DeliveryClient.Create(new DeliveryOptions { EnvironmentId = "your-environment-id" });

The builder is one type in both hosting modes:

  • .Options — the client's OptionsBuilder<DeliveryOptions> (Configure, Bind, BindConfiguration, …)
  • .HttpClient — the named IHttpClientBuilder the transport is built on (ConfigurePrimaryHttpMessageHandler, AddHttpMessageHandler, …)
  • .TuneRetry(...) — adjusts the default retry options
  • .ConfigureResilience(...) — replaces the default resilience pipeline
  • .Services and .Name — what everything else attaches to: a custom ITypeProvider, an ILoggerFactory, the caching package's UseMemoryCache / UseHybridCache / UseCacheManager

Create returns the concrete DeliveryClient, which owns the container it was built from — disposing it tears that down, which is why the examples use await using. Keep the result as var (or DeliveryClient); widening it to IDeliveryClient drops the disposal, because the interface deliberately does not carry it. A client resolved from a container is owned by that container, so there is nothing for you to dispose there. IDeliveryClientFactory is the other half of that split: it resolves named clients from a container you own, while Create builds a standalone one over a container it owns. Invalid options surface as OptionsValidationException from Create.

UseCustomEndpoint(...) sets the same endpoint for both Production and Preview. Real deployments usually differ, so if you need both modes on custom domains, register separate named clients and give each its own endpoint.

Caching

Caching is the first thing to add to a production application - it is also the SDK's answer to rate limits. It ships separately:

dotnet add package Kontent.Ai.Delivery.Caching

Attach it to a client in the same callback that configures it:

services.AddDeliveryClient(delivery =>
{
    delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
    delivery.UseMemoryCache(o => o.DefaultExpiration = TimeSpan.FromHours(1));
});

UseHybridCache is the L1+L2 form for multi-instance deployments, backed by any IDistributedCache.

Running more than one instance? Register an IFusionCacheBackplane as well. Part of the invalidation state lives in each instance rather than in the shared cache, so without one an instance can go on serving content another already evicted. The SDK picks the backplane up from the container automatically.

Caching is transparent once attached: cacheable queries are cached, keyed by their parameters. Dynamic queries, preview clients and WaitForLoadingNewContent(true) are deliberately never cached.

The Caching Guide has the rest - hybrid setup, cache keys, expiration, dependency keys for output caching, webhook invalidation, purging, and multi-tenant scenarios.

Rich Text

A rich text element is structured content, not an HTML string. Render it with ToHtmlAsync():

var result = await client.GetItem<Article>("my-article").ExecuteAsync();
var html = await result.Value.Elements.BodyCopy.ToHtmlAsync();

The default resolver handles text, images and standard HTML. To control how content item links and embedded content render, build a resolver and pass it in. A resolver's return value is inserted as HTML unescaped, so encode every element value you interpolate (HtmlEncoder is in System.Text.Encodings.Web); the rendered output of resolveChildren is already HTML and must not be encoded:

var resolver = new HtmlResolverBuilder()
    .WithContentItemLinkResolver("article", async (link, resolveChildren) =>
        $"<a href=\"/articles/{HtmlEncoder.Default.Encode(link.Metadata?.UrlSlug ?? "")}\">{await resolveChildren(link.Children)}</a>")
    .WithContentResolver<Tweet>(t => $"<blockquote>{HtmlEncoder.Default.Encode(t.Elements.TweetText ?? "")}</blockquote>")
    .Build();

var html = await result.Value.Elements.BodyCopy.ToHtmlAsync(resolver);

Rich text is also enumerable, when you want the blocks rather than the HTML.

The Rich Text Customization Guide covers the rest: registering the resolver in DI, async and nested resolution, inline images, custom HTML nodes, and the extension methods for reading blocks out of an element.

Rendering in Razor? Kontent.Ai.AspNetCore has a <rich-text> tag helper that picks the registered IHtmlResolver up for you.

Configuration Options

The DeliveryOptions class provides comprehensive configuration:

services.AddDeliveryClient(delivery => delivery.Options.Configure(options =>
{
    // Required: Your Kontent.ai environment ID
    options.EnvironmentId = "your-environment-id";

    // Preview API settings
    options.UsePreviewApi = false;
    options.PreviewApiKey = "your-preview-api-key";

    // Secured production API (if enabled in Kontent.ai)
    options.UseSecureAccess = false;
    options.SecureAccessApiKey = "your-secure-api-key";

    // Retry and resilience settings
    options.EnableResilience = true;

    // Default image rendition preset
    options.DefaultRenditionPreset = "default";

    // Custom asset domain (rewrites all asset URLs to use your CDN)
    options.CustomAssetDomain = "https://assets.example.com";

    // Custom endpoints (for proxy scenarios, set independently)
    options.ProductionEndpoint = "https://deliver.kontent.ai";
    options.PreviewEndpoint = "https://preview-deliver.kontent.ai";
}));

options.UseCustomEndpoint(...) is a convenience method that sets both endpoints to the same URL. For distinct preview/production custom domains, configure separate clients and set endpoints per client.

The HTTP client and the resilience pipeline are configured on the same builder. HttpClient is the named IHttpClientBuilder the transport is built on, so every Microsoft.Extensions.Http extension applies, and whatever you configure there runs after the SDK's own setup. Leave HttpClient.Timeout alone: the SDK sets it as the ceiling on the whole call, and DeliveryOptions.Timeout is the way to change that ceiling, so an override there silently caps the retry sequence:

services.AddDeliveryClient(delivery =>
{
    delivery.Options.Configure(options => options.EnvironmentId = "your-environment-id");
    delivery.HttpClient.ConfigureHttpClient(client => client.DefaultRequestHeaders.Add("X-App", "my-app"));
    delivery.TuneRetry(retry =>
    {
        retry.MaxRetryAttempts = 5;
        retry.Delay = TimeSpan.FromSeconds(2);
    });
});

Tuning retries

TuneRetry receives the SDK's initialized HttpRetryStrategyOptions before the default pipeline is assembled. Change only the settings you need; the remaining defaults, including Retry-After handling and the 30-second per-attempt timeout, stay in place. Replacing ShouldHandle or DelayGenerator replaces that part of the retry behavior.

Callbacks run in registration order with fresh options for each pipeline construction, in both AddDeliveryClient and DeliveryClient.Create. They do not run when EnableResilience is false or ConfigureResilience replaces the pipeline. Tuning retains the default pipeline's timeout behavior.

Timeouts

Two clocks bound a request, and they are not the same one:

  • Per attempt — the default resilience pipeline cancels any single HTTP attempt after 30 seconds and retries it on a fresh connection. By default, up to four attempts, each with its own budget.
  • The whole call — DeliveryOptions.Timeout covers every attempt and the waits between them.

Timeout is unset by default, which keeps the SDK's own rule: the default pipeline bounds each attempt, so the call runs as long as its retries need; with EnableResilience = false or a pipeline of your own, HttpClient's 100-second default applies, because nothing else is known to bound the request.

Set it and it always wins, whatever the pipeline:

services.AddDeliveryClient(delivery => delivery.Options.Configure(o =>
{
    o.EnvironmentId = "your-environment-id";
    o.Timeout = TimeSpan.FromMinutes(5);
}));

Timeout.InfiniteTimeSpan removes the ceiling outright. Note that it outranks Retry-After: when the API rate-limits you, the pipeline waits exactly as long as the server asked, but the call is still cut short if your ceiling runs out first.

Error Handling

The SDK uses a result pattern instead of throwing exceptions for API errors. This makes error handling explicit and predictable.

Checking for Errors

var result = await client.GetItem<Article>("my-article").ExecuteAsync();

if (result.IsSuccess)
{
    var article = result.Value;
    // Process article
}
else
{
    // Handle error
    var error = result.Error;
    Console.WriteLine($"Error: {error.Message}");
    Console.WriteLine($"Status: {result.StatusCode}");

    // Error details for debugging/logging
    if (error.RequestId != null)
        Console.WriteLine($"Request ID: {error.RequestId}");

    if (error.ErrorCode.HasValue)
        Console.WriteLine($"Error Code: {error.ErrorCode}");
}

IError Properties

Property Description
Message Human-readable error description
RequestId Unique request ID for Kontent.ai support
ErrorCode Kontent.ai-specific error code
SpecificCode More specific error code
Exception Underlying exception (for network errors, etc.)

Source Tracking (for Tool Authors)

Every request the SDK sends carries two analytics headers:

  • X-KC-SDKID — identifies this SDK. Always nuget.org;Kontent.Ai.Delivery;<version>. Not configurable.
  • X-KC-SOURCE — identifies a library built on top of the SDK. Set only when a caller assembly opts in. Omitted otherwise.

End-user applications need do nothing here. This matters only if you publish a library that wraps the Delivery SDK. If you do, add one of these at assembly level (AssemblyInfo.cs, or a top-level file); at request time the SDK walks the call stack, finds your assembly and reads the attribute:

// Name and version from the assembly — the usual case.
[assembly: DeliverySourceTrackingHeader]

// Override the name (your package id differs from your assembly name), version still from the assembly.
[assembly: DeliverySourceTrackingHeader("Acme.Kontent.Ai.AwesomeTool")]

// Pin both, independent of assembly metadata.
[assembly: DeliverySourceTrackingHeader("Acme.Kontent.Ai.AwesomeTool", 1, 2, 3, "beta")]

Contributing

Contributions are welcome. Use GitHub Issues for bug reports and feature requests, and open pull requests in this repository for code contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.


Questions or feedback? Visit our GitHub Issues or check the Kontent.ai Developer Hub.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
20.0.1 267 9/18/2026
20.0.0 163 9/16/2026
20.0.0-rc.3 688 9/8/2026
20.0.0-rc.2 340 8/12/2026
20.0.0-rc.1 194 8/7/2026
19.4.0 1,340 8/3/2026
19.3.1 129 7/30/2026
19.3.0 194 6/24/2026
19.2.0 3,025 5/4/2026
19.1.0 140 4/27/2026
19.0.0 326 4/19/2026
19.0.0-rc5 169 4/13/2026
19.0.0-rc4 174 3/18/2026
19.0.0-rc3 122 3/6/2026
19.0.0-rc2 130 3/4/2026
19.0.0-rc1 379 2/20/2026