Skip to content

Kontent.Ai.Management 9.0.0-rc.1

Pre-release
Pre-release

Choose a tag to compare

Targets .NET 10, completing the framework move that the 9.x line was always heading for, and upgrades Refit across four major versions. The result pattern, transport architecture and model conventions introduced in the earlier betas are unchanged — see the 9.0.0-beta-1 release notes for that overview.

Warning

Still a prerelease. Install with --prerelease — without that flag you get the stable 8.x API, which these notes do not describe.

Breaking changes

  • net8.0net10.0. There is no multi-targeting, so a project on .NET 8 cannot install this release — restore fails with NU1202. Move to .NET 10 first.

  • new FileContentSource(stream, …) now rejects a stream that cannot seek, with an ArgumentException naming the parameter. The upload endpoint needs the size up front: without a Content-Length the request goes out chunked and is refused with "the file is bigger than the maximal allowed limit (2 GB)" — regardless of the actual size, and verified against the live API. A non-seekable stream has no length to declare, so this overload could never produce a successful upload; the error simply arrived from the server, describing the wrong problem. It is now refused where the stream is passed in.

    Nothing that worked stops working — there was no combination in which a non-seekable stream uploaded successfully. If you were passing one, buffer it first or use the byte[]/file-path overload:

    // Before — always failed, with an error about a 2 GB limit
    var source = new FileContentSource(httpResponseStream, "photo.jpg", "image/jpeg");
    
    // After — buffer, so the length is known
    using var buffered = new MemoryStream();
    await httpResponseStream.CopyToAsync(buffered);
    var source = new FileContentSource(buffered.ToArray(), "photo.jpg", "image/jpeg");
  • The client interfaces no longer carry IDisposable / IAsyncDisposable; the concrete clients do. Disposal exists for one situation - a client built outside a container, which owns its own transport and must release it. Putting it on the interface meant every consumer holding IManagementClient was offered a Dispose() that, on the container path, released nothing and must not be called: the container owns that lifetime. ManagementClientBuilder.Build() now returns the concrete ManagementClient, which is IDisposable and IAsyncDisposable, so disposal stays available exactly where it means something.

    await using var client = ManagementClientBuilder…Build(); is unchanged, and so is every DI usage. The only code that breaks widened the builder result to the interface and then disposed it:

    // Before - no longer compiles, because the interface has no Dispose
    IManagementClient client = ManagementClientBuilderBuild();
    client.Dispose();
    
    // After - keep the concrete type, or just use var
    var client = ManagementClientBuilderBuild();
    client.Dispose();

    Container-resolved clients are still disposed by the container, which checks the runtime type rather than the registered service type - so nothing changes there.

  • The configureRefit parameter is gone from all five AddManagementClient overloads, and ManagementClientBuilder.ConfigureRefit is removed. The hook handed out the transport library's settings object, but every value it could reach was load-bearing rather than configurable — the parameter-key formatter matches the API's casing, and the serializer options carry the converters, naming policy and nesting limit the wire format depends on. Overriding them broke requests silently. Delete the argument or the builder call; the SDK's own tests only ever used it to assert the callback fired.

  • Enum values are now read case-sensitively. Only the exact wire token is accepted: "modular_content" binds, "MODULAR_CONTENT" and the C# member name "LinkedItems" now throw JsonException instead of being coerced. The Management API emits canonical tokens and ContentModelSnapshot.FromJson only ever consumes ToJson output, so this affects hand-written JSON. Writing is unchanged, and numeric tokens are still rejected in both directions.

Changed

  • AddManagementClient gained the overloads its sibling SDKs already had, so the three register a client the same way: a pre-built options instance, and options configured with access to the IServiceProvider. Nothing was removed, and existing calls are unaffected — this closes gaps rather than reshaping the surface.

  • Cancellation now throws; other transport failures are results. Refit's upgrade changed the
    contract: exceptions raised in the HTTP pipeline are captured into the response rather than thrown.
    A network failure, DNS failure or resilience-pipeline rejection is therefore an unsuccessful result
    carrying the exception, consistent with how every other failure in this SDK is reported. Cancellation
    is the exception to that: when the caller's token fires, the OperationCanceledException is rethrown,
    so Task.IsCanceled, Task.WhenAll and cancellation handlers behave as they do everywhere else in
    .NET. Previously all of these threw. An expired HttpClient.Timeout is not cancellation, even
    though .NET surfaces it as a TaskCanceledException. This one matters on a write API: the request was
    sent and the server may have applied it, so it is reported as a failed result carrying the exception,
    never as the caller withdrawing a request that never happened.

  • Transport failures report status 0. IManagementResult now carries (HttpStatusCode)0 for that case rather than an invented code. Responses that did arrive are unaffected.

  • ManagementOptions.Timeout sets the ceiling on one call, covering every retry attempt and the waits between them. Defaults to 30 minutes.

Fixed

  • An empty pre-release label no longer produces a trailing hyphen in X-KC-SOURCE. A package identifying itself with [assembly: SourceTrackingHeader("MyPackage", 2, 0, 0, "")] was reported as MyPackage;2.0.0-, which is not a valid SemVer version. An empty label now counts as no label, matching what passing null already did.
  • GetFullFolderPath no longer starts a path with a separator. An ancestor folder with an empty name still contributed a segment, so a folder below it came back as \\Child rather than Child. Unnamed ancestors are now skipped.
  • A long upload is no longer cut off at 100 seconds. This SDK deliberately configures no per-attempt timeout, because an asset upload takes as long as the file is large and the link is slow. But HttpClient's own 100-second default bounds the whole call — every attempt and all the backoff between them — and nothing raised it, so it capped exactly the uploads the missing per-attempt timeout was meant to protect. It also silently truncated retries: a 429 carrying Retry-After: 60 spent most of the budget before the next attempt began. The ceiling is now ManagementOptions.Timeout, defaulting to 30 minutes — sized against the documented 2 GB asset limit, which is roughly what that carries over a 10 Mbps link.
  • Uploads always declare a Content-Length. A source that could not report its size sent the request chunked, and the endpoint rejects that outright — reporting "the file is bigger than the maximal allowed limit (2 GB)" no matter how small the file actually was. Every source now carries a length, so the request the SDK builds is one the API can accept.
  • Long-running applications pick up DNS changes instead of pinning the address resolved at startup. The registered client is a singleton and takes its HttpClient from IHttpClientFactory once, so the handler chain it holds was never rotated — the factory only hands a fresh chain to a new CreateClient call. Connections now recycle every two minutes, matching the factory's own default handler lifetime. This matters when the endpoint's address changes underneath a process that stays up for days: a failover, a scale event, or any re-pointing upstream. Configuring your own primary handler via configureHttpClient still overrides this, as before.

Dependencies

Shipped floors on Kontent.Ai.Management moved up, all .NET 10 aligned:

  • Microsoft.Extensions.* (Configuration.Abstractions, Logging.Abstractions, Options.ConfigurationExtensions, Options.DataAnnotations) 9.0.1510.0.10.
  • Microsoft.Extensions.Http.Resilience 9.6.010.8.0.
  • Refit and Refit.HttpClientFactory 10.2.014.0.1.

Internal

No consumer-visible effect:

  • Enum wire tokens now travel on [JsonStringEnumMemberName] and serialize through the built-in System.Text.Json converter. The custom converter existed only because that attribute did not exist on .NET 8. All 140 members across 36 enums keep their exact tokens, verified by round-trip — including the ones that are not snake_case (light-purple, fullScreen, asc, modular_content).
  • Refit 14 builds request logic at compile time rather than by reflection; the Management interfaces generate completely and gained that with no changes.

Installation

dotnet add package Kontent.Ai.Management --prerelease

Full changelog: src/management/CHANGELOG.md