Digizuite.Optimizely
4.4.0
dotnet add package Digizuite.Optimizely --version 4.4.0
NuGet\Install-Package Digizuite.Optimizely -Version 4.4.0
<PackageReference Include="Digizuite.Optimizely" Version="4.4.0" />
<PackageVersion Include="Digizuite.Optimizely" Version="4.4.0" />
<PackageReference Include="Digizuite.Optimizely" />
paket add Digizuite.Optimizely --version 4.4.0
#r "nuget: Digizuite.Optimizely, 4.4.0"
#:package Digizuite.Optimizely@4.4.0
#addin nuget:?package=Digizuite.Optimizely&version=4.4.0
#tool nuget:?package=Digizuite.Optimizely&version=4.4.0
Digizuite DAM for Optimizely
Requirements
| .NET | Optimizely CMS | EF Core |
|---|---|---|
| 6.0 | 12.16.0 or later (CMS.UI 12.16.0+) | 7 - 9 |
| 8.0 | 12.24.0 or later (CMS.UI 12.24.0+, CMS.Core 12.19.0+) | 8 - 9 |
| 10.0 | 12.24.0 or later (CMS.UI 12.24.0+, CMS.Core 12.19.0+) | 10 |
Optimizely CMS 13 is not supported.
Version 4.4.0 adds .NET 8 and .NET 10 support; .NET 6 is still supported, so existing sites can
update without retargeting. Note that the Optimizely CMS 12 packages are themselves built for
net6.0 on every version - it is your site's target framework that moves, never the CMS packages.
On .NET 6 the floors are the same as in 4.3.1, so 4.4.0 is a drop-in update. On .NET 8 and .NET 10
they follow the EPiServer.CMS 12.24.0 metapackage. Security advisories in the CMS dependency
closure (for example in older CMS 12 releases) are resolved by upgrading Optimizely CMS in your site.
SQL Server compatibility level
On .NET 8 and .NET 10 the connector runs on EF Core 8 / 10, which translate Contains() over a list
into OPENJSON. That requires the database to be at compatibility level 130 (SQL Server 2016) or
higher. A database restored or attached from an older SQL Server keeps its original compatibility
level even on a modern engine, so this is easy to hit unintentionally - the symptom is
SqlException: Incorrect syntax near '$' (error 102) on the first asset load.
The connector therefore defaults to compatibility level 120, which uses the older IN (...)
translation and works against any database. No action is needed when upgrading. The setting has no
effect on .NET 6, where EF Core 7 always uses IN (...).
If your database is at compatibility level 130 or higher and you want the optimized translation:
{
"DigizuiteDamSettings": {
"SqlServerCompatibilityLevel": 160
}
}
To check and raise your database instead:
SELECT name, compatibility_level FROM sys.databases WHERE name = DB_NAME();
ALTER DATABASE [YourDb] SET COMPATIBILITY_LEVEL = 130;
Managed Integration
Managed Integration lets the KeyShot DAM push asset events (create, update, delete) to your
site. It is new in 4.4.0 and is separate from the existing Digizuite integration
(DigizuiteWebhookElement) - the two use different DAM APIs and can be enabled independently.
Add a ManagedIntegration section to digizuitesettings.json:
{
"DigizuiteDamSettings": {
"ManagedIntegration": {
"Enabled": true,
"Name": "Optimizely-prod",
"SiteUrl": "https://www.example.com",
"Secret": "<provided by KeyShot>"
}
}
}
| Setting | Default | Description |
|---|---|---|
Enabled |
true |
Whether the DAM should send events. false still registers the integration, but registers it disabled, so it is a real off switch. Remove the whole section to opt out entirely. Nothing is registered until SiteUrl and Secret are also set. |
Name |
Optimizely |
The integration's name in the DAM, and its upsert identity. See the warning below. |
SiteUrl |
- | This site's publicly reachable base url. The receiving path is appended to it. |
Secret |
- | The shared secret, sent as the x-dgz-managed-integration header. Issued by KeyShot - see below. |
CloudflareContentCacheEnabled |
true |
Whether the site is served through the Cloudflare content cache. true registers the integration against the Cloudflare worker, which forwards to the site. false registers the site's own url, so the DAM sends events directly. See below. |
CdnHostNames |
empty | Extra FQDNs (no scheme) for the CDN purge, when assets are served from more than one domain. Ignored when CloudflareContentCacheEnabled is false. |
MaxBatchSize |
100 |
Assets per notification. The DAM's own ceiling is 200. |
RequestTimeout |
60 |
Seconds the DAM waits for a response. |
LanguageId |
DAM default | DAM language id for metadata in the payload. |
RegisterOnStartup |
true |
Whether to create/update the integration in the DAM at startup. |
AcknowledgeAssets |
true |
Whether to report an integration state per synced asset, which marks it tracked. |
No change to Startup.cs is needed - AddDigizuiteDAM() wires up both halves. When the section
is absent, nothing is registered with the DAM and no middleware is added.
How the chain works
With CloudflareContentCacheEnabled set to false there is no worker: the integration is
registered directly against {SiteUrl}/keyshot/managedintegration/invoke, and the DAM sends
events straight to your site. The rest of this section describes the default, true.
The integration is not registered against your site directly. It is registered against the
Cloudflare worker at {DAM api url}/managed-integration, which purges the CDN cache and then
forwards the notification to {SiteUrl}/keyshot/managedintegration/invoke.
Two consequences worth knowing:
- The worker answers the DAM itself and never reads your site's response. A failed forward is logged in the worker and lost - it does not appear in the DAM's failed-notification list.
- This requires the CDN Accelerated API to be enabled on your DAM.
Two things that catch people out
Name must be unique per environment. Registration upserts by name: the DAM is asked for
its integrations, matched on this exact string, and the match is updated in place. Two
environments pointing at the same DAM with the same name will overwrite each other's endpoint.
Leaving Name unset logs a warning at startup for exactly this reason.
Secret is not a value you choose. The Cloudflare worker validates the header against its
own configured secret and answers 401 if it differs, so it has to be the secret issued by the
KeyShot service team. The worker forwards the header unchanged, which is what your site checks.
(With CloudflareContentCacheEnabled set to false there is no worker, so the value only has to
match between the DAM integration and your site.)
Setting the secret in production
AddDigizuiteDAM reads digizuitesettings.json through its own configuration builder, which
has no environment-variable provider - DigizuiteDamSettings__ManagedIntegration__Secret
binds to nothing. Supply it through the setup lambda instead, which is applied last and so wins
over the JSON file:
services.AddDigizuiteDAM(cfg => cfg.ManagedIntegration = new ManagedIntegrationElement
{
Name = "Optimizely-prod",
SiteUrl = "https://www.example.com",
Secret = Configuration["KeyShot:ManagedIntegrationSecret"] // env var, Key Vault, ...
});
What happens when an event arrives
For each asset the DAM reports as created or updated:
- The stored blobs are compared against the notification, so only what went stale is dropped
and re-fetched on next use:
- Each rendition (transcode) is judged on the
timestampquery parameter of its url, which moves when the DAM regenerates it - for example when a video's thumbnail is replaced. A rendition whose timestamp is unchanged is left alone. A missing timestamp, or a rendition that no longer exists, counts as changed. - The source file is judged on its MD5 (
hashMd5). If it differs, the source blob is wiped, along with the asset's crops and other derived items. If it matches they are left alone — this is the whole point of the comparison, since re-fetching a large file no-one changed is expensive.
- Each rendition (transcode) is judged on the
- The stored record is updated with the asset from the notification. No call back to the DAM is needed: the payload already carries the full asset.
- The memory cache for the asset is cleared, in every language, and the other servers in the cluster are told to do the same.
Assets reported as deleted are removed completely — record, blobs and cache.
Two things worth knowing:
- Only the language the integration is registered with is refreshed in place. The payload carries one language, so the asset's records in any other language are dropped rather than left holding metadata of unknown age; they are re-read from the DAM on next access.
- Access rights are not part of the payload, so the stored security entries are kept as they are. A permission change in the DAM therefore does not reach the site through this route until something else refreshes the asset. If that matters for your setup, say so — it needs a separate lookup per asset, which is why it is not done by default.
An asset the site has never loaded is left alone: there is nothing cached to update. The one exception is a new crop (an asset derived from another): it is stored under its parent, together with its access list, so it shows up in the parent's list of crops.
Verifying it
Set Digizuite.Optimizely to Information in your logging configuration. At startup you should
see one line naming the integration, the worker endpoint and the forward url. When an asset
changes in the DAM, you should see ManagedIntegration: received batch ... once per event.
If the startup line never appears, the section is missing or SiteUrl/Secret are blank. If it
appears but no batches arrive, check that SiteUrl is reachable from the internet and that the
secret matches - a mismatch is answered with 401 by the worker and leaves no trace on your side.
Each batch also logs what it did, which is the quickest way to see the hash comparison working:
ManagedIntegration: applied batch updated=1 cropsAdded=0 blobsWiped=0 alreadyCurrent=0 notCached=0 cleared=0 deleted=0 failed=0 assetIds=[1234].
blobsWiped=0on a metadata-only edit, and1after replacing the source file or a rendition such as a video thumbnail. If it is1every time, your DAM is not populatinghashMd5and the connector is falling back to always wiping - correct, but slower than it needs to be.alreadyCurrentcounts assets that were already up to date, for example when the same change is sent again. They are skipped.clearedcounts assets whose payload could not be used, which fall back to being dropped rather than updated. Everything still works, just with a re-fetch per asset. If it is every asset, the integration is registered without the connector's metadata fields - the connector fills those in at startup, so this normally means registration did not run.
Get Started
DAM for Optimizely enables the full potential of the Digizuite DAM and provides access to all digital assets at any time from one single source of truth. Read more about all the benefits here: http://www.damforoptimizely.com To get started, you should add the Digizuite DAM services in your startup class. The simplest option to see it work right away is to try out the integration with a shared, readonly, default DAM. You can use that by just adding this service registration in your startup:
services.AddDigizuiteDAM();
With this done, you can already now try to go to edit-mode, and look in the Digizuite widget in the Asset panel to navigate the folders and assets on the DAM. You are also able to drag and drop them into your content and use them in your test or developer environment.
Usually you will want to connect to your own DAM - and set many other settings. You can see an example of how is should be done here: https://keyshot.atlassian.net/wiki/spaces/DD/pages/5628297794/DFO+4.4.0+-+3+Setting+up+Optimizely
Key Features
- Digizuite Content Provider. Realtime mapping of Digizuite assets and folders into Optimizely
- Digizuite Asset Browser. Powerful UI extension that allows you to easily find the assets you are looking for.
- Multi-lingual assets. Assets from Digizuite can contain metadata in multiple languages - as opposed to Optimizelys default behavior.
- Pseudo streaming of video. With the integration your Optimizely installation can proxy videos streaming live from the DAM.
- Automatic encoding, conversion and optimization of all assets from Digizuite.
- Full CRUD capabilities of Assets in Digizuite. You can both Create new assets (upload), Read and display assets, Update metadata on assets, and Delete assets from the Episerver channel in the DAM - all from within Optimizelys Edit mode.
- Easy editor cropping to predefined formats. Editors can now from the right-click menu create crops of any asset from a predefined list of standardized crops that goes across your organization.
- Developer managed media format selection. When developers render your assets they can decide between all the DAM-configured mediaformats and output the format that is needed, where it's needed.
- Validation attributes for developers. Developers can control which assets editors can use in different locations, simply by attaching an attribute in the content model And much, much more. Learn more here: https://www.damforoptimizely.com/
Configuration
Most of the configuration of the integration to Digizuite can be done the configuration options in the Startup class. Once you have your own Digizuite DAM instance configured to work with DAM For Optimizely you can update the connection settings to ensure you connect to the proper DAM. At that time it would also be a good idea to customize the mapping of access control roles between Optimizely and Digizuite. There are also numerous other parameters you can set to your preferences. Read more here: https://keyshot.atlassian.net/wiki/spaces/DD/pages/5628297794/DFO+4.4.0+-+3+Setting+up+Optimizely
Developer customizations
Since DAM for Optimizely seamlessly integrates Digizuite assets directly into Optimizely as content, you can of course work with them as you would with any Optimizely based content media - including customizing their renderings, modifying their models and so on. But we have included a number of additional cool technologies you can implement as a developer. Learn more about typical customizations here: https://keyshot.atlassian.net/wiki/spaces/DD/pages/5628297981/DFO+4.4.0+-+Developers+Guide
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Digizuite.Sdk (>= 5.10.14.1)
- EPiServer.CMS.AspNetCore (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.AspNetCore.HtmlHelpers (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.Core (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.TinyMce (>= 4.5.0 && < 6.0.0)
- EPiServer.CMS.UI (>= 12.24.0 && < 13.0.0)
- EPiServer.Framework (>= 12.19.0 && < 13.0.0)
- EPiServer.Framework.AspNetCore (>= 12.19.0 && < 13.0.0)
- Microsoft.EntityFrameworkCore.Design (>= 10.0.11 && < 11.0.0)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.11 && < 11.0.0)
- Polly (>= 7.2.3 && < 9.0.0)
- Razor.Templating.Core (>= 2.1.0 && < 4.0.0)
-
net6.0
- Digizuite.Sdk (>= 5.10.14.1)
- EPiServer.CMS.AspNetCore (>= 12.16.0 && < 13.0.0)
- EPiServer.CMS.AspNetCore.HtmlHelpers (>= 12.16.0 && < 13.0.0)
- EPiServer.CMS.Core (>= 12.16.0 && < 13.0.0)
- EPiServer.CMS.TinyMce (>= 3.0.1 && < 6.0.0)
- EPiServer.CMS.UI (>= 12.16.0 && < 13.0.0)
- EPiServer.Framework (>= 12.16.0 && < 13.0.0)
- EPiServer.Framework.AspNetCore (>= 12.16.0 && < 13.0.0)
- Microsoft.EntityFrameworkCore.Design (>= 7.0.0 && < 10.0.0)
- Microsoft.EntityFrameworkCore.SqlServer (>= 7.0.0 && < 10.0.0)
- Polly (>= 7.2.3 && < 9.0.0)
- Razor.Templating.Core (>= 2.1.0 && < 3.0.0)
-
net8.0
- Digizuite.Sdk (>= 5.10.14.1)
- EPiServer.CMS.AspNetCore (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.AspNetCore.HtmlHelpers (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.Core (>= 12.19.0 && < 13.0.0)
- EPiServer.CMS.TinyMce (>= 4.5.0 && < 6.0.0)
- EPiServer.CMS.UI (>= 12.24.0 && < 13.0.0)
- EPiServer.Framework (>= 12.19.0 && < 13.0.0)
- EPiServer.Framework.AspNetCore (>= 12.19.0 && < 13.0.0)
- Microsoft.EntityFrameworkCore.Design (>= 8.0.30 && < 10.0.0)
- Microsoft.EntityFrameworkCore.SqlServer (>= 8.0.30 && < 10.0.0)
- Polly (>= 7.2.3 && < 9.0.0)
- Razor.Templating.Core (>= 2.1.0 && < 3.0.0)
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 |
|---|---|---|
| 4.4.0 | 73 | 10/7/2026 |
| 4.3.1 | 929 | 7/6/2026 |
| 4.3.0 | 152 | 7/3/2026 |
| 4.3.0-beta-1 | 116 | 7/2/2026 |
| 4.2.7 | 481 | 2/11/2026 |
| 4.2.6 | 167 | 2/10/2026 |
| 4.2.5 | 535 | 1/15/2026 |
| 4.2.4 | 433 | 12/15/2025 |
| 4.2.3 | 1,137 | 11/13/2025 |
| 4.2.2 | 380 | 10/15/2025 |
| 4.2.1 | 600 | 9/5/2025 |
| 4.2.0 | 461 | 8/12/2025 |
| 4.1.5 | 987 | 9/12/2025 |
| 4.1.4 | 370 | 8/27/2025 |
| 4.1.3 | 701 | 7/21/2025 |
| 4.1.2 | 846 | 6/24/2025 |
| 4.1.2-beta.2 | 212 | 6/19/2025 |
| 4.1.2-beta.1 | 239 | 6/18/2025 |
| 4.1.1 | 406 | 5/7/2025 |
| 4.0.9 | 297 | 9/11/2025 |
4.4.0 - Adds support for .NET 8 and .NET 10 alongside .NET 6.
For existing customers on .NET 6 this is a drop-in update unless your code calls connector
internals: configuration and the documented extension points are unchanged or only extended,
but some unused public helpers and models were removed and DigizuiteServiceWrapper has a new
constructor (see Breaking changes in the documentation). The Optimizely CMS, TinyMCE and EF Core
floors stay within what 4.3.1 already allowed (EPiServer.CMS.UI 12.16). The only dependency
change is Razor.Templating.Core 1.6.0 -> 2.x.
If you retarget your site to net8.0 or net10.0, the floors follow the EPiServer.CMS 12.24.0
metapackage: EPiServer.CMS.UI 12.24.0, EPiServer.CMS.Core 12.19.0 and TinyMCE 4.5.0, plus EF Core
8 or 10 respectively. Optimizely CMS 13 is not supported.
New setting DigizuiteDamSettings:SqlServerCompatibilityLevel (default 120). EF Core 8 and later
translate Contains() over a list into OPENJSON, which requires the database to be at compatibility
level 130 or higher; the default of 120 keeps the previous IN (...) translation and works against
any database. It has no effect on .NET 6. This also fixes the failure for customers who had
already moved to EF Core 8 or 9 on 4.3.1 and hit "Incorrect syntax near '$'".
Response headers are now set idempotently instead of throwing ArgumentException when the header
was already set upstream.
New feature: Managed Integration. The KeyShot DAM can now push asset events to the site through
the Cloudflare content cache, which purges the CDN and forwards to
{SiteUrl}/keyshot/managedintegration/invoke. Configured under
DigizuiteDamSettings:ManagedIntegration; requires the CDN Accelerated API on the DAM and a
secret issued by KeyShot. The integration is created or updated in the DAM at startup, matched
on its configured Name. No change to Startup.cs is needed, and nothing happens unless the
section is present. This is opt-in and does not affect the existing Digizuite integration
(DigizuiteWebhookElement), which continues to work unchanged.
On an asset event the connector compares the source file's MD5 against its stored copy and wipes
the asset's blobs, crops and derived items only when the file actually changed; the stored record
is then updated from the notification itself, with no call back to the DAM, and the memory cache
is cleared across the cluster. Deleted assets are removed entirely. Only the language the
integration is registered with is refreshed in place - other languages are dropped and re-read on
next access - and access rights, which are not part of the payload, are left as they are.
See the README for setup and verification.