Kontent.Ai.Delivery 20.0.0-rc.2
Pre-releaseBreaking changes
-
The caching package's registration class is renamed to
DeliveryCacheServiceCollectionExtensions. It and the Delivery SDK both declaredKontent.Ai.Delivery.ServiceCollectionExtensions, so two packages owned one full type name — and sinceKontent.Ai.Delivery.Cachingdepends onKontent.Ai.Delivery, every consumer has both and could name neither: referring to it wasCS0433, with no way to disambiguate. Nothing that compiled before stops compiling. The namespace is unchanged, sousing Kontent.Ai.Delivery;and everyservices.AddDeliveryMemoryCache(...)/AddDeliveryHybridCache(...)/AddDeliveryCacheManager(...)call is exactly as it was; only code that named the type explicitly is affected, and that could not have built. -
The source generator emits its marker attribute as
internal.ContentTypeCodenameAttributeis generated into each referencing compilation, so apublicone put the same type name into every assembly that uses the generator. Two such projects referencing each other stopped compiling withCS0436/CS0433, and the only fix available to the consumer was to drop a project reference. Emitting itinternal— standard practice for generated marker attributes — gives each assembly its own copy. Code that only applies the attribute to its own models is unaffected; code that exposed it across an assembly boundary was in the broken configuration already.
Added
-
ConfigureFusionCacheonDeliveryCacheOptions, fromKontent.Ai.Delivery.Caching, configures the underlying cache withFusionCacheOptionstyped:services.AddDeliveryMemoryCache(opts => opts .ConfigureFusionCache(fusion => fusion.DefaultEntryOptions.EagerRefreshThreshold = 0.8f));
The
ConfigureFusionCacheOptionsproperty it sets stays as it was,Action<object>?, because it is declared inKontent.Ai.Delivery.Abstractionsand that package deliberately references nothing. The extension lives where FusionCache is already referenced, so the cast happens once here instead of in every caller.
Fixed
-
DeliveryClientBuilder.Build()documents the exception it actually throws. It promisedInvalidOperationExceptionfor invalid configuration; the validation runs in the options pipeline, so what surfaces isOptionsValidationException. Now pinned by a test, and the inline note about why it fires during the build is corrected too. -
The caching package no longer re-registers the dependency extractor the SDK already registers, and the interface no longer describes a no-op implementation that does not exist — there is one implementation.
-
IDeliveryClientsays which queries are cached. Languages, single content elements and used-in queries always reach the API; that was a decision nowhere written down. -
DeliverySourceTrackingHeaderAttributeis sealed, and bothWithEnvironmentIdoverloads describe setting the environment rather than constructing the builder. -
A rich-text document is disposed once parsed. The AngleSharp document was left to finalization on every rich-text element mapped; the parsed blocks hold plain strings and lists rather than document nodes, so nothing needed it to stay alive.
-
A client name containing a tab or newline is rejected like one containing a space. The rule trimmed and then looked for spaces, so other whitespace passed validation and left a name that is invisible at the point of failure. The caching package also carried its own copy of the rule, which is now the shared one.
-
Dynamic queries carry their dependency keys, on every page.
GetItem/GetItemswithout a typed model returned results whoseDependencyKeyswerenull, while the typed queries forwarded them — so output-cache tagging, which those keys exist for, had nothing to tag with on the dynamic path. Paging through a dynamic listing dropped them the same way from the second page on. -
ImageUrlBuilderkeeps a query the asset URL already carries. Transformations were applied as a relative reference with its own query, which replaces the base URL's query outright. An asset URL produced by a default rendition preset therefore lost its rendition the moment any transformation was added. The two are merged now, with an explicit transformation winning where both set the same key. -
A cache miss in raw-JSON mode hydrates once instead of twice. The factory already builds the value to collect its dependency keys, and the payload it stored was then parsed and mapped a second time to answer the same call. The call that produced the value now reuses it; a cache hit or a background refresh still rehydrates, as it must.
-
The source generator no longer pins compilations in the IDE's incremental cache. Its pipeline model carried a
Location, which holds itsSourceTreealive — and pipeline values are retained for as long as the generator is loaded, so every edit accumulated another rooted syntax tree and the compilation behind it. The position is stored as a path and spans, and theLocationis rebuilt only when a diagnostic is reported. -
Options handed to the SDK prebuilt are copied by reflection rather than property by property.
DeliveryOptions.CopyTolisted the properties it carried, which keeps compiling when an option is added and silently stops carrying it — a value the caller set that the client never sees. It now uses the same copier the other SDKs do. -
The
X-KC-SOURCEheader keeps naming the integration that made the call. Attribution matched the SDK assembly by full name, which carries the version — and nothing pinsAssemblyVersion, so the reference an integration recorded when it was built stopped matching on the first SDK release after that. The header then went silently missing for every consumer who had not rebuilt. Matching is now by simple name. -
Rich-text tag resolvers keep their place in registration order, and the description is no longer dispatch.
WithHtmlNodeResolver(tagName, ...)registrations were lifted into a lookup consulted before any predicate resolver, so a tag resolver won however late it was registered — against the documented "evaluated in registration order, first match wins". Membership of that lookup was decided by whether the resolver's description started withTag=, so a predicate resolver a caller happened to describe that way was silently promoted into it. Registering the same tag twice threw anArgumentExceptionfromBuild(), where every other registration is resolved by order. All three now follow the one documented rule: one ordered pass, first match wins, tag registrations included. The public builder API is unchanged. -
Cache keys are scoped to the environment they were fetched from. A key was built from the query alone, so "the item
article" produced the same key in every environment. Two applications sharing one distributed cache and pointing at different environments served each other's content, silently and in both directions. The environment id is now part of the key prefix, ahead of which an explicitKeyPrefixstill separates clients within one environment. Existing distributed cache entries are not readable under the new keys and are simply missed once, then rewritten — nothing to migrate, but expect one cold start after upgrading. -
An application's own
JsonSerializerOptionsregistration is no longer taken over as the SDK's wire serializer.AddDeliveryClientlooked for a singleton registered underJsonSerializerOptionsand, finding one, used it to read every API response. Registering that type is an ordinary thing for an application to do, and the options it registers do not carryContentItemConverterFactory- without which no raw item JSON is captured, hydration has nothing to map from, and typed models come back empty. Nothing threw and nothing was logged. The SDK now keeps its serializer under a type only it names, so an application's registration stays the application's, and a registration made through a factory or under a service key no longer splits Refit and the mappers onto different serializers. -
A distributed cache no longer strips taxonomy and multiple-choice data out of content types. The distributed tier's serializer was built without the SDK's own converters, so it wrote content type elements by their declared type:
TaxonomyElement.TaxonomyGroupandMultipleChoiceElement.Optionswent in and never came out. A node reading such an entry back got a plainContentElement- anInvalidCastExceptionfor anything casting toITaxonomyElementorIMultipleChoiceElement, and missing data for anything that did not. The writing node was unaffected because it answers from its own memory tier, so this surfaced only on a second instance, which is the case a distributed cache exists for.ContentElementConverternow writes an element by its runtime type - the wire's owntypefield is the discriminator on the way back - and the distributed tier uses the SDK's serializer unless one is supplied. -
A request is bounded again when the SDK's own resilience pipeline is not the one installed.
HttpClient.Timeoutwas set toTimeout.InfiniteTimeSpanunconditionally, on the premise that the resilience pipeline owns timing - but the 30-second per-attempt timeout that premise rests on exists only whileEnableResilienceis left on and noconfigureResiliencehook replaces the default pipeline. SettingEnableResilience = false, or supplying a pipeline that adds no timeout of its own, therefore left a call with no attempt timeout, no overall timeout and no ceiling of any kind, so a connection that stopped responding hung the caller indefinitely. The ceiling is now lifted only for the default pipeline; otherwiseHttpClient's 100-second default applies, as it did before this SDK moved to a resilience pipeline. A custom pipeline that legitimately needs longer can raise it throughconfigureHttpClient. -
An attempt the resilience pipeline timed out is now retried instead of failing the whole call. The default pipeline wraps retry around a 30-second per-attempt timeout, so a hung attempt reaches the retry as Polly's
TimeoutRejectedException- a type the SDK's transient classifier did not recognise. The single situation that per-attempt timeout exists for, a connection that stops responding and that a fresh attempt would recover from, therefore failed the whole call after 30 seconds with no retries at all.
Installation
dotnet add package Kontent.Ai.Delivery --prereleaseFull changelog: src/delivery/CHANGELOG.md