HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

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:

AreaCMS 12 (Search & Navigation)CMS 13 (Graph)
NuGet packagesEPiServer.Find, EPiServer.Find.CmsOptimizely.Graph.Cms.Query, Optimizely.Graph.AspNetCore
Search clientIClient / SearchClient.InstanceIGraphContentClient
NamespacesEPiServer.Find.*Optimizely.Graph.Cms.Query
ConfigurationEPiServer:FindOptimizely:Graph
Execution modelMostly synchronousAsynchronous (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-migration

Upgrade 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 build

Resolve 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 run

Verification 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 test

Commit 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
📘

Note

Build 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 namespaceGraph namespace
EPiServer.FindOptimizely.Graph.Cms.Query
EPiServer.Find.ApiOptimizely.Graph.Cms.Query
EPiServer.Find.CmsOptimizely.Graph.Cms.Query
EPiServer.Find.FrameworkOptimizely.Graph.Cms.Query

Remove unused namespaces such as:

  • EPiServer.Find.Framework.Statistics
  • EPiServer.Find.Helpers.Text
  • EPiServer.Find.UI
  • Wangkanai.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 APIGraph 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

If 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.AspNetCore

Add 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:

  • FindOptions
  • IFindStatisticsClient
  • SearchSection
  • Search & Navigation initialization modules

Build and test

Build the project:

dotnet build

Run tests:

dotnet test

Start the site and verify that search works correctly:

dotnet run

Troubleshooting

Search returns no results

Verify the following:

  • Graph credentials in appsettings.json are 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.