CmsSlugs.Optimizely.Cms12
0.1.0
dotnet add package CmsSlugs.Optimizely.Cms12 --version 0.1.0
NuGet\Install-Package CmsSlugs.Optimizely.Cms12 -Version 0.1.0
<PackageReference Include="CmsSlugs.Optimizely.Cms12" Version="0.1.0" />
<PackageVersion Include="CmsSlugs.Optimizely.Cms12" Version="0.1.0" />
<PackageReference Include="CmsSlugs.Optimizely.Cms12" />
paket add CmsSlugs.Optimizely.Cms12 --version 0.1.0
#r "nuget: CmsSlugs.Optimizely.Cms12, 0.1.0"
#:package CmsSlugs.Optimizely.Cms12@0.1.0
#addin nuget:?package=CmsSlugs.Optimizely.Cms12&version=0.1.0
#tool nuget:?package=CmsSlugs.Optimizely.Cms12&version=0.1.0
CmsSlugs.Optimizely.Cms12
Optimizely (EPiServer) CMS 12 adapter for CmsSlugs.
Resolves (slug, culture) -> content from a fast index instead of a Find query on the hot path.
dotnet add package CmsSlugs.Optimizely.Cms12
This is the package you install. CmsSlugs (core) comes with it. Optionally add
CmsSlugs.Redis for a durable index; without it,
the index lives in memory and is rebuilt by a boot scan on every start. A SQL Server provider
(CmsSlugs.MsSql) exists in the repository but is not yet published.
Requirements
.NET 6+ — this package targets
net6.0. CMS 11 is not supported.Optimizely CMS 12 (
EPiServer.CMS.Core12.x).The Optimizely NuGet feed.
EPiServer.CMS.Coreis not published to nuget.org, so restoring this package needs Optimizely's feed configured alongside it — as any Optimizely solution already will have:dotnet nuget add source https://api.nuget.optimizely.com/v3/index.json -n optimizely
1. Implement ISlugSource
The one thing you must write: the bridge from your content to SlugEntry values. Reuse whatever
slug-building logic the solution already has. Keep it free of threading code — SlugIndexBuilder
owns the parallelism and calls WarmUp() once, single-threaded, before any parallel pass.
public sealed class CatalogSlugSource : ISlugSource
{
public IEnumerable<ContentReference> GetContentLinks() { /* cheap listing */ }
public IEnumerable<SlugEntry> Resolve(ContentReference contentLink) { /* one item -> entries */ }
public IEnumerable<SlugEntry> GetForContent(IContent content) { /* incremental updates */ }
public bool SupportsConcurrentResolve => true;
public void WarmUp() { }
}
Returning nothing from GetForContent means "not routable" — the content stays out of the index.
2. Register
[InitializableModule]
public sealed class CmsSlugsConfiguration : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
var services = context.Services;
services.AddCmsSlugs(); // resolver, builder, default store
services.AddSingleton<ISlugSource, CatalogSlugSource>();
services.AddSingleton<IPartialRouter, SlugPartialRouter>();
// services.AddCmsSlugsRedis("localhost:6379"); // optional durable store
}
public void Initialize(InitializationEngine context) { }
public void Uninitialize(InitializationEngine context) { }
}
Everything in AddCmsSlugs() uses TryAdd, so registering your own implementation first replaces
the default.
To configure SlugIndexOptions from appsettings.json, bind it yourself —
services.Configure<SlugIndexOptions>(config.GetSection("CmsSlugs")). ConfigureCmsSlugs(...)
(or the configure argument to AddCmsSlugs) runs as a post-configure, so a code override wins
over the bound values regardless of call order.
3. Route
public object? RoutePartial(StartPage content, UrlResolverContext context)
{
var segment = context.GetNextSegment();
if (segment.Next.IsEmpty) return null;
if (!_resolver.IsReady) return null; // boot window -> next router / 404
var culture = context.RequestedLanguage?.Name
?? context.ContentLanguage?.Name
?? CultureInfo.CurrentUICulture.Name;
if (_resolver.TryResolve(segment.Next.ToString(), culture, out var hit)
&& _contentLoader.TryGet<CatalogContentBase>(hit.ContentLink, out var target))
{
context.RemainingSegments = segment.Remaining;
return target;
}
return null; // miss -> next router / 404
}
In CMS 12, registering IPartialRouter in DI is the whole registration — PartialRouteHandler
receives every registered router.
Keeping the index current
- Content events update the index incrementally as editors publish, move or delete.
- Boot scan repopulates the in-memory store at startup.
- "Rebuild Slug Index" scheduled job does a full rebuild. Run it once after deploying with a durable store, since a durable store is not boot-scanned.
Always check IsReady before trusting a miss: during the boot window the index is not yet
authoritative.
Full setup and a working harness site: https://github.com/noelsefton/cmsslugs.
| 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 was computed. 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 was computed. 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. |
-
net6.0
- CmsSlugs (>= 0.1.0)
- EPiServer.CMS.Core (>= 12.0.3 && < 13.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.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 |
|---|---|---|
| 0.1.0 | 127 | 7/26/2026 |