Migrate an Alloy site from CMS 12 to CMS 13
Migrate an Optimizely Alloy site from Optimizely Search & Navigation to Optimizely Graph when upgrading to Optimizely Content Management System (CMS) 13 using the C# SDK.
This guide describes how to migrate an Optimizely Alloy site from Optimizely Search & Navigation to Optimizely Graph using the Graph C# SDK.
The migration replaces the Search & Navigation client and APIs with Graph equivalents while preserving search functionality in your site.
Migration overview
An Alloy site running on CMS 12 typically uses Optimizely Search & Navigation for site search. Search & Navigation is deprecated and replaced by Optimizely Graph, a GraphQL-based search platform with a C# SDK.
The migration involves the following changes:
| Area | CMS 12 (Search & Navigation) | CMS 13 (Graph) |
|---|---|---|
| NuGet packages | EPiServer.Find, EPiServer.Find.Cms | Optimizely.Graph.Cms.Query, Optimizely.Graph.AspNetCore |
| Search client | IClient / SearchClient.Instance | IGraphContentClient |
| Namespaces | EPiServer.Find.* | Optimizely.Graph.Cms.Query |
| Configuration | EPiServer:Find | Optimizely:Graph |
| Execution model | Mostly synchronous | Asynchronous (async / await) |
Prerequisites
Before you begin the migration, confirm the following:
- The Alloy site builds and runs on CMS 12.
- You use Git to manage source control.
- You have access to the following Optimizely Graph credentials:
- Gateway address
- App key
- Secret
- Single key
Create a new branch before starting the migration:
git checkout -b feature/graph-migrationUpgrade from CMS 12 to CMS 13
Upgrade the CMS platform before replacing Search & Navigation with Graph. This isolates the platform upgrade from the search migration and simplifies troubleshooting.
Update CMS packages
Open the .csproj project file and update the CMS packages to version 13.
<!-- CMS 12 -->
<PackageReference Include="EPiServer.CMS" Version="12.*" />
<PackageReference Include="EPiServer.CMS.AspNetCore" Version="12.*" />
<!-- CMS 13 -->
<PackageReference Include="EPiServer.CMS" Version="13.*" />
<PackageReference Include="EPiServer.CMS.AspNetCore" Version="13.*" />Update any additional EPiServer.* packages to versions compatible with CMS 13.
Restore and build the project
Run the following commands:
dotnet restore
dotnet buildResolve any build errors before continuing.
Keep the EPiServer.Find packages installed at this stage. Confirm that the CMS upgrade succeeds before changing the search implementation.
Verify the CMS upgrade
After upgrading to CMS 13, verify that the site functions correctly before modifying the search implementation.
Run the site
dotnet runVerification checklist
Confirm the following:
- The site starts without errors.
- The CMS edit interface loads correctly.
- Content pages render correctly.
- The existing Search & Navigation search implementation still works.
- Application logs contain no errors.
If automated tests exist, run them:
dotnet testCommit the CMS upgrade when verification succeeds.
git add -A
git commit -m "chore: upgrade CMS 12 to CMS 13"Upgrading the CMS separately makes Search & Navigation issues easier to identify and resolve.
Replace Search & Navigation NuGet packages
Remove the Search & Navigation packages and install the Graph packages.
Remove Search & Navigation packages
Remove the following packages from the project file:
<PackageReference Include="EPiServer.Find" />
<PackageReference Include="EPiServer.Find.Cms" />
<PackageReference Include="EPiServer.Find.Framework" />If the project contains the following package, remove it:
<PackageReference Include="Wangkanai.Detection" />Add Graph packages
Add the Graph packages:
<PackageReference Include="Optimizely.Graph.Cms.Query" Version="3.*" />
<PackageReference Include="Optimizely.Graph.AspNetCore" Version="3.*" />Restore packages:
dotnet restore
NoteBuild errors may appear at this stage because the code still references Search & Navigation APIs.
Update configuration
Remove the Search & Navigation configuration and add Graph configuration in appsettings.json.
Remove Search & Navigation configuration
"EPiServer": {
"Find": {
"ServiceUrl": "https://...",
"DefaultIndex": "YOUR_INDEX_NAME"
}
}Add Graph configuration
"Optimizely": {
"Graph": {
"GatewayAddress": "https://cg.optimizely.com",
"AppKey": "YOUR_APP_KEY",
"Secret": "YOUR_SECRET",
"SingleKey": "YOUR_SINGLE_KEY"
}
}Retrieve these values from the Graph management portal.
Update service registration
Update Startup.cs to remove Search & Navigation services and register Graph services.
Remove Search & Navigation services
Remove the following service registrations:
services.AddFind()services.AddDetection()app.UseDetection()
Register Graph services
Add the following registrations:
services.AddContentGraph();
services.AddGraphContentClient();
services.AddVisitorGroupsCore();Add Graph middleware:
app.UseGraphTrackingScripts();Update namespaces
Replace Search & Navigation namespaces with Graph namespaces.
| Search & Navigation namespace | Graph namespace |
|---|---|
EPiServer.Find | Optimizely.Graph.Cms.Query |
EPiServer.Find.Api | Optimizely.Graph.Cms.Query |
EPiServer.Find.Cms | Optimizely.Graph.Cms.Query |
EPiServer.Find.Framework | Optimizely.Graph.Cms.Query |
Remove unused namespaces such as:
EPiServer.Find.Framework.StatisticsEPiServer.Find.Helpers.TextEPiServer.Find.UIWangkanai.Detection.*
Replace the search client
Search & Navigation uses IClient or SearchClient.Instance.
Graph uses IGraphContentClient, which is registered for dependency injection.
Example: Search & Navigation client
private readonly IClient _client;
public SearchService(IClient client)
{
_client = client;
}Example: Graph client
private readonly IGraphContentClient _client;
public SearchService(IGraphContentClient client)
{
_client = client;
}Create a query interface
Graph queries use interfaces instead of concrete page types.
Create an interface that represents searchable content.
Example:
[ContentType]
public interface ISitePageData : IContent
{
string MetaTitle { get; set; }
string TeaserText { get; set; }
}Update the base page type to implement the interface:
public abstract class SitePageData : PageData, ISitePageData
{
}Rewrite search queries
Replace Search & Navigation query APIs with Graph query APIs.
API mapping
| Search & Navigation API | Graph API |
|---|---|
.Search<T>() | .QueryContent<T>() |
.For() | .SearchFor() |
.Filter() | .Where() |
.Take() | .Limit() |
.GetResult() | await GetAsContentAsync() |
.TotalMatching | .Total |
Graph queries require asynchronous execution.
Example query
var result = await _client.QueryContent<ISitePageData>()
.SearchFor(query)
.UsingFullText()
.Limit(pageSize)
.Skip((page - 1) * pageSize)
.IncludeTotal()
.GetAsContentAsync();Update asynchronous methods
Optimizely Graph queries are asynchronous. Update calling methods to support async execution.
Example
Before:
public ActionResult Search(string q)
{
var results = _searchService.Search(q);
return View(results);
}After:
public async Task<ActionResult> Search(string q)
{
var results = await _searchService.SearchAsync(q);
return View(results);
}Replace UnifiedSearch
UnifiedSearchIf the site uses UnifiedSearch, replace it with a Graph query that uses the interface created earlier.
Example:
var result = await _client.QueryContent<ISitePageData>()
.SearchFor(query)
.UsingFullText()
.Limit(pageSize)
.Skip(offset)
.IncludeTotal()
.GetAsContentAsync();Update Razor views
Add the Graph tag helper:
@addTagHelper *, Optimizely.Graph.AspNetCoreAdd tracking setup to the layout:
<graph-tracking-setup />If you use click tracking, update search result links:
<graph-trackable-link url="@Url.ContentUrl(hit.ContentLink)"
track-url="@Model.GetTrackUrl(hit)">
@hit.Name
</graph-trackable-link>Remove remaining Search & Navigation code
Search the project for any remaining Search & Navigation references and remove them.
Examples include:
FindOptionsIFindStatisticsClientSearchSection- Search & Navigation initialization modules
Build and test
Build the project:
dotnet buildRun tests:
dotnet testStart the site and verify that search works correctly:
dotnet runTroubleshooting
Search returns no results
Verify the following:
- Graph credentials in
appsettings.jsonare correct. services.AddContentGraph()is registered.- Content has synchronized to Optimizely Graph.
Inspect the generated GraphQL query:
var query = _client.QueryContent<ArticlePage>().Limit(10);
Console.WriteLine(query.ToGraphQL());Total results return zero
Confirm the query includes:
.IncludeTotal()Without this method, the total count is not returned.
Updated 6 days ago
