Skip to main content

Optimizely

Personalized Optimizely CMS Website Search Experiences Azure AI Search & Personalizer

Istock 1628553826

In the last blog, we discussed Integrating the Optimizely CMS website with Azure AI search. Now let’s take a bit more advanced topic to serve Personalization experience with Azure AI search with Azure personalizer. Together, they enable you to serve dynamic customized content and search results across user  behaviour, preferences, and context.

What is Azure Personalizer?

Azure Personalizer Cognitive Service for Real-time Association using Reinforcement Learning. It gives you the ability to serve content or experiences that are most relevant to a user — informed by past behaviour and current context.

Benefits of AI Personalizer:

  • So it can study and evolve as people engage with it.
  • Amazingly helpful for ranking search results.
  • Can customize direct calls to action, highlighted articles, or goods.

How It Works with Azure AI Search and Optimizely

  1. The user performs a search on your Optimizely site.
  2. Azure AI Search simply gives a  list of matching documents
  3. These documents are sent to Azure Personalizer as “rankable actions.
  4. The personalized orders results using the context of the user.
  5. Your app serves personalized results and the user’s feedback helps Personalizer to learn & evolve further.

Set Up Azure Personalizer

  • Navigate to Azure Portal → Personalizer resource creation
  • Save your endpoint and API key.
  • In step 3, specify the Content that you want to be ranked (i.e., search results)

Integration Code

Model for Rankable Action

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicclass RankableDocument
{
publicstring Id {get; set; }
publicstring Title {get; set; }
publicstring Summary {get; set; }
publicstring Category {get; set; }
}
public class RankableDocument { public string Id { get; set; } public string Title { get; set; } public string Summary { get; set; } public string Category { get; set; } }
public class RankableDocument
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Summary { get; set; }
    public string Category { get; set; }
}

Send Info to Personalizer with Context:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
privateobjectGetUserContext(HttpRequestBase request)
{
returnnew
{
timeOfDay = DateTime.Now.Hour,
device = request.Browser.IsMobileDevice ? "mobile" : "desktop",
userAgent = request.UserAgent,
language = request.UserLanguages?.FirstOrDefault() ?? "en"
};
}
publicasync Task<List<RankableDocument>>GetPersonalizedResultsAsync(List<RankableDocument> documents, string userId)
{
var contextFeatures = new[]{GetUserContext(Request)};
var actions = documents.Select(doc =>new
{
id = doc.Id,
features = new[]
{
new{ category = doc.Category},
new{ title = doc.Title}
}
});
_eventId = Guid.NewGuid().ToString();
var request = new
{
contextFeatures = contextFeatures,
actions = actions,
excludedActions = newstring[]{},
eventId = _eventId,
deferActivation = false
};
var client = newHttpClient();
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "--YOUR API KEY ---");
var response = await client.PostAsync("--Endpoint--/personalizer/v1.0/rank",
newStringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"));
var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var topActionId = result.RootElement.GetProperty("rewardActionId").GetString();
return documents.OrderByDescending(d => d.Id == topActionId).ToList();
}
private object GetUserContext(HttpRequestBase request) { return new { timeOfDay = DateTime.Now.Hour, device = request.Browser.IsMobileDevice ? "mobile" : "desktop", userAgent = request.UserAgent, language = request.UserLanguages?.FirstOrDefault() ?? "en" }; } public async Task<List<RankableDocument>> GetPersonalizedResultsAsync(List<RankableDocument> documents, string userId) { var contextFeatures = new[] { GetUserContext(Request) }; var actions = documents.Select(doc => new { id = doc.Id, features = new[] { new { category = doc.Category }, new { title = doc.Title } } }); _eventId = Guid.NewGuid().ToString(); var request = new { contextFeatures = contextFeatures, actions = actions, excludedActions = new string[] {}, eventId = _eventId, deferActivation = false }; var client = new HttpClient(); client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "--YOUR API KEY ---"); var response = await client.PostAsync("--Endpoint--/personalizer/v1.0/rank", new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")); var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); var topActionId = result.RootElement.GetProperty("rewardActionId").GetString(); return documents.OrderByDescending(d => d.Id == topActionId).ToList(); }
private object GetUserContext(HttpRequestBase request)
{
    return new
    {
        timeOfDay = DateTime.Now.Hour,
        device = request.Browser.IsMobileDevice ? "mobile" : "desktop",
        userAgent = request.UserAgent,
        language = request.UserLanguages?.FirstOrDefault() ?? "en"
    };
}
public async Task<List<RankableDocument>> GetPersonalizedResultsAsync(List<RankableDocument> documents, string userId)
{
    var contextFeatures = new[] { GetUserContext(Request) };

    var actions = documents.Select(doc => new
    {
        id = doc.Id,
        features = new[]
        {
            new { category = doc.Category },
            new { title = doc.Title }
        }
    });

    _eventId = Guid.NewGuid().ToString();

    var request = new
    {
        contextFeatures = contextFeatures,
        actions = actions,
        excludedActions = new string[] {},
        eventId = _eventId,
        deferActivation = false
    };

    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "--YOUR API KEY ---");
    var response = await client.PostAsync("--Endpoint--/personalizer/v1.0/rank",
        new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"));

    var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
    var topActionId = result.RootElement.GetProperty("rewardActionId").GetString();

    return documents.OrderByDescending(d => d.Id == topActionId).ToList();
}

Now let’s consider our previous example of search page controller & view and extend it

Search Controller

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicclass AzureSearchPageController : PageController<AzureSearchPage>
{
privatestaticstring _eventId;
publicasync Task<ActionResult>Index(AzureSearchPage currentPage, string q = "")
{
var results = new List<RankableDocument>();
if(!string.IsNullOrEmpty(q))
{
var url = $"https://<search-service>.search.windows.net/indexes/<index-name>/docs?api-version=2021-04-30-Preview&search={q}";
usingvar client = newHttpClient();
client.DefaultRequestHeaders.Add("api-key", "<your-query-key>");
var response = await client.GetStringAsync(url);
var doc = JsonDocument.Parse(response);
results = doc.RootElement.GetProperty("value")
.EnumerateArray()
.Select(x =>new RankableDocument
{
Id = x.GetProperty("id").GetString(),
Title = x.GetProperty("name").GetString(),
Category = x.GetProperty("type").GetString(),
Summary = x.GetProperty("content").GetString()
}).ToList();
results = awaitGetPersonalizedResultsAsync(results, "user123");
}
ViewBag.Results = results;
ViewBag.Query = q;
ViewBag.EventId = _eventId;
returnView(currentPage);
}
[HttpPost]
publicasync Task<ActionResult>Reward(string eventId, double rewardScore)
{
usingvar client = newHttpClient();
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "<your-api-key>");
var rewardUrl = $"<your-endpoint>/personalizer/v1.0/events/{eventId}/reward";
var result = await client.PostAsync(rewardUrl, newStringContent(rewardScore.ToString(), Encoding.UTF8, "application/json"));
returnJson(new{ success = result.IsSuccessStatusCode});
}
}
public class AzureSearchPageController : PageController<AzureSearchPage> { private static string _eventId; public async Task<ActionResult> Index(AzureSearchPage currentPage, string q = "") { var results = new List<RankableDocument>(); if (!string.IsNullOrEmpty(q)) { var url = $"https://<search-service>.search.windows.net/indexes/<index-name>/docs?api-version=2021-04-30-Preview&search={q}"; using var client = new HttpClient(); client.DefaultRequestHeaders.Add("api-key", "<your-query-key>"); var response = await client.GetStringAsync(url); var doc = JsonDocument.Parse(response); results = doc.RootElement.GetProperty("value") .EnumerateArray() .Select(x => new RankableDocument { Id = x.GetProperty("id").GetString(), Title = x.GetProperty("name").GetString(), Category = x.GetProperty("type").GetString(), Summary = x.GetProperty("content").GetString() }).ToList(); results = await GetPersonalizedResultsAsync(results, "user123"); } ViewBag.Results = results; ViewBag.Query = q; ViewBag.EventId = _eventId; return View(currentPage); } [HttpPost] public async Task<ActionResult> Reward(string eventId, double rewardScore) { using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "<your-api-key>"); var rewardUrl = $"<your-endpoint>/personalizer/v1.0/events/{eventId}/reward"; var result = await client.PostAsync(rewardUrl, new StringContent(rewardScore.ToString(), Encoding.UTF8, "application/json")); return Json(new { success = result.IsSuccessStatusCode }); } }
public class AzureSearchPageController : PageController<AzureSearchPage>
{
    private static string _eventId;

    public async Task<ActionResult> Index(AzureSearchPage currentPage, string q = "")
    {
        var results = new List<RankableDocument>();

        if (!string.IsNullOrEmpty(q))
        {
            var url = $"https://<search-service>.search.windows.net/indexes/<index-name>/docs?api-version=2021-04-30-Preview&search={q}";
            using var client = new HttpClient();
            client.DefaultRequestHeaders.Add("api-key", "<your-query-key>");
            var response = await client.GetStringAsync(url);

            var doc = JsonDocument.Parse(response);
            results = doc.RootElement.GetProperty("value")
                .EnumerateArray()
                .Select(x => new RankableDocument
                {
                    Id = x.GetProperty("id").GetString(),
                    Title = x.GetProperty("name").GetString(),
                    Category = x.GetProperty("type").GetString(),
                    Summary = x.GetProperty("content").GetString()
                }).ToList();

            results = await GetPersonalizedResultsAsync(results, "user123");
        }

        ViewBag.Results = results;
        ViewBag.Query = q;
        ViewBag.EventId = _eventId;
        return View(currentPage);
    }

    [HttpPost]
    public async Task<ActionResult> Reward(string eventId, double rewardScore)
    {
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "<your-api-key>");

        var rewardUrl = $"<your-endpoint>/personalizer/v1.0/events/{eventId}/reward";
        var result = await client.PostAsync(rewardUrl, new StringContent(rewardScore.ToString(), Encoding.UTF8, "application/json"));

        return Json(new { success = result.IsSuccessStatusCode });
    }
}

Search Page View

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@model AzureSearchPage
<h1>Personalized Search Results</h1>
<form method="get">
<input type="text" name="q"value="@ViewBag.Query" placeholder="Search..." />
<button type="submit">Search</button>
</form>
<ul>
@foreach(var result in ViewBag.Resultsas List<RankableDocument>)
{
<li>
<h4>@result.Title</h4>
<p>@result.Summary</p>
<button onclick="sendReward('@ViewBag.EventId', 1.0)">Like</button>
<button onclick="sendReward('@ViewBag.EventId', 0.0)">Not Relevant</button>
</li>
}
</ul>
<script>
function sendReward(eventId, score){
fetch('/AzureSearchPage/Reward', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eventId: eventId, rewardScore: score })
}).then(r =>{
if(r.ok)alert("Thanks! Your feedback was recorded.");
});
}
</script>
@model AzureSearchPage <h1>Personalized Search Results</h1> <form method="get"> <input type="text" name="q" value="@ViewBag.Query" placeholder="Search..." /> <button type="submit">Search</button> </form> <ul> @foreach (var result in ViewBag.Results as List<RankableDocument>) { <li> <h4>@result.Title</h4> <p>@result.Summary</p> <button onclick="sendReward('@ViewBag.EventId', 1.0)">Like</button> <button onclick="sendReward('@ViewBag.EventId', 0.0)">Not Relevant</button> </li> } </ul> <script> function sendReward(eventId, score) { fetch('/AzureSearchPage/Reward', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ eventId: eventId, rewardScore: score }) }).then(r => { if (r.ok) alert("Thanks! Your feedback was recorded."); }); } </script>
@model AzureSearchPage
<h1>Personalized Search Results</h1>
<form method="get">
    <input type="text" name="q" value="@ViewBag.Query" placeholder="Search..." />
    <button type="submit">Search</button>
</form>

<ul>
@foreach (var result in ViewBag.Results as List<RankableDocument>)
{
    <li>
        <h4>@result.Title</h4>
        <p>@result.Summary</p>
        <button onclick="sendReward('@ViewBag.EventId', 1.0)">Like</button>
        <button onclick="sendReward('@ViewBag.EventId', 0.0)">Not Relevant</button>
    </li>
}
</ul>
<script>
function sendReward(eventId, score) {
    fetch('/AzureSearchPage/Reward', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ eventId: eventId, rewardScore: score })
    }).then(r => {
        if (r.ok) alert("Thanks! Your feedback was recorded.");
    });
}
</script>

With Azure AI Search delivering relevant results and Azure Personalizer re-ranking them based on real-time context, your Optimizely site becomes an intelligent experience engine.

This blog has also been published here.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Naveed Ul-Haq

Naveed is a UK-based technical architect and technology evangelist passionate about crafting scalable digital experiences. As an Optimizely MVP and Subject Matter Expert in Content Cloud and Commerce Cloud, he specializes in building robust .NET-based CMS and eCommerce solutions. His technical toolkit includes .NET Core, DevOps practices, and cloud computing—particularly within Microsoft Azure. Naveed holds multiple certifications: Certified Software Architect, Optimizely Content Cloud Developer, Optimizely Commerce Cloud Developer, Optimizely B2B Commerce Developer, and Microsoft Certified: Azure Developer Associate. Outside of work, Naveed enjoys spending quality time with his family and diving into a good book.

More from this Author

Categories
Follow Us