HomeDev guideAPI ReferenceGraphQL
Dev guideUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

Personalized search sample code

Describes how Optimizely Search & Navigation integrated with Optimizely Personalization can boost search results, based on website visitor behavior data, and extracted from the Personalization service.

This topic provides sample code for comparing search results that use .UsingPersonalization() against search results that do not. The examples use the Quicksilver Optimizely Customized Commerce sample site and the Personalized Search & Navigation APIs.

📘

Note

Recommendations and personalized search results currently only supports Optimizely Customized Commerce content.  Also, the .UsingPersonalization() syntax only boosts Optimizely Customized Commerce content properties.

Preparation

  1. Install the Quicksilver sample site.
  2. Install the NuGet packages EPiServer.Personalization.Commerce and EPiServer.Find.Personalization, see Personalized Search & Navigation.
  3. Add the following configuration from the Optimizely.Personalization.Commerce package to the web.config file (CMS 11) or appsettings.json (CMS 12) of the site.

  1. Verify that the data tracking and recommendations work as expected. 
1300
  1. Go to some product pages on the site so the personalization system can track and collect behavioral data.

Verify attributes

Create a PersonalizedFindPage page type

namespace EPiServer.Reference.Commerce.Site.Features.Personalization.Models {
  [ContentType(DisplayName = "PersonalizedFindPage", GUID = "e7e5cea1-10cf-4e1a-ab36-1c922d997e28", Description = "")]
  public class PersonalizedFindPage: PageData {
    [CultureSpecific]
    [Display(
      Name = "Main body",
      Description = "The main body will be shown in the main content area of the page, using the XHTML-editor you can insert for example text, images and tables.",
      GroupName = SystemTabNames.Content,
      Order = 1)]
    public virtual XhtmlString MainBody {
      get;
      set;
    }
  }
}

Create a PersonalizedFindPage page view model

public class PersonalizedFindPageViewModel {
  public PersonalizedFindPage CurrentPage {
    get;
    set;
  }
  public bool UsePersonalization {
    get;
    set;
  }
  public IEnumerable < PreferenceAttributeData > CurrentPersonalizationAttributes {
    get;
    set;
  }
  public string SearchTerm {
    get;
    set;
  }
}

Create a PersonalizedFindPage controller

private readonly IClient _client;
private readonly UrlResolver _urlResolver;
public PersonalizedFindPageController(IClient client, UrlResolver urlResolver) {
  _client = client;
}
public ActionResult Index(PersonalizedFindPage currentPage, string q, bool ? usePersonalization = null) {
  var viewModel = new PersonalizedFindPageViewModel {
    CurrentPage = currentPage, SearchTerm = q, UsePersonalization = usePersonalization.HasValue && usePersonalization.Value
  };

  System.Threading.Tasks.Task.Factory.StartNew(currentContext => {
      System.Web.HttpContext.Current = (HttpContext) currentContext;
      _client.Personalization().Refresh();
    },
    System.Web.HttpContext.Current.ApplicationInstance.Context);

  var prefData = _client.Personalization().Conventions.PreferenceRepository.Load();

  if (prefData != null) {
    var attributes = prefData.Attributes;
    viewModel.CurrentPersonalizationAttributes = attributes;
  }
  return View(viewModel);
}

Create a view for displaying the attributes

@using EPiServer.Core
@using EPiServer.Web.Mvc.Html
@model EPiServer.Reference.Commerce.Site.Features.Personalization.ViewModels.PersonalizedFindPageViewModel</pre>
<div>Current attributes from Personalization:</div>
  <table class="table">
    <tr>
      <th>Attribute name</th>
      <th>Attribute value</th>
      <th>Boost factor</th>
    </tr>
    @if (Model.CurrentPersonalizationAttributes != null)
      {
        foreach (var attribute in Model.CurrentPersonalizationAttributes)
          {
            <tr>
              <td>@attribute.Name</td>
              <td>@attribute.Value</td>
              <td>@attribute.Score</td>
            </tr>
          }
      }
  </table>

The view result will look like this:

794

Make sure the returned attributes are not empty.

Compare search results

This example visualizes the difference between search results with and without personalization.

📘

Note

Personalized Search & Navigation works only with free-text search queries, as described in Search.

First, add .UsingPersonalization() to the search query.

var resultsPersonalized = _client.Search()
  .For(q)
  .FilterForVisitor()
  .UsingPersonalization()
  .GetContentResult();

Then, add another search query without .UsingPersonalization(), for comparison.

var resultsWithoutPersonalized  = _client.Search()
  .For(q)
  .FilterForVisitor()
  .GetContentResult();

Add more properties to the model to preview the result on the front-end.

namespace EPiServer.Reference.Commerce.Site.Features.Personalization.ViewModels {
  public class PersonalizedFindPageViewModel {
    public PersonalizedFindPage CurrentPage {
      get;
      set;
    }
    public bool UsePersonalization {
      get;
      set;
    }
    public IEnumerable < PreferenceAttributeData > CurrentPersonalizationAttributes {
      get;
      set;
    }
    public IEnumerable < SearchResultModel > SearchResults {
      get;
      set;
    }
    public IEnumerable < SearchResultModel > PersonalizedSearchResults {
      get;
      set;
    }
    public string SearchTerm {
      get;
      set;
    }
  }

  public class SearchResultModel {
    public string Url {
      get;
      set;
    }
    public Uri Image {
      get;
      set;
    }
    public double Score {
      get;
      set;
    }
    public string Name {
      get;
      set;
    }
    public string Brand {
      get;
      set;
    }
    public IEnumerable < string > AvailableColors {
      get;
      set;
    }
  }
}

The controller:

namespace EPiServer.Reference.Commerce.Site.Features.Personalization.Controllers {
  public class PersonalizedFindPageController: PageController < PersonalizedFindPage > {
    private readonly IClient _client;
    private readonly UrlResolver _urlResolver;
    public PersonalizedFindPageController(IClient client, UrlResolver urlResolver) {
      _client = client;
      _urlResolver = urlResolver;
    }
    public ActionResult Index(PersonalizedFindPage currentPage, string q, bool ? usePersonalization = null) {
      var viewModel = new PersonalizedFindPageViewModel {
        CurrentPage = currentPage, SearchTerm = q, UsePersonalization = usePersonalization.HasValue && usePersonalization.Value
      };

      System.Threading.Tasks.Task.Factory.StartNew(currentContext => {
          System.Web.HttpContext.Current = (HttpContext) currentContext;
          _client.Personalization().Refresh();
        },
        System.Web.HttpContext.Current.ApplicationInstance.Context);

      var prefData = _client.Personalization().Conventions.PreferenceRepository.Load();

      if (prefData != null) {
        var attributes = prefData.Attributes;
        viewModel.CurrentPersonalizationAttributes = attributes;
      }

      if (!string.IsNullOrEmpty(q)) {
        var resultsPersonalized = _client.Search < FashionProduct > ()
          .For(q)
          .FilterForVisitor()
          .Track()
          .UsingPersonalization()
          .GetContentResult();

        var resultsWithoutPersonalized = _client.Search < FashionProduct > ()
          .For(q)
          .FilterForVisitor()
          .Track()
          .GetContentResult();

        if (resultsWithoutPersonalized != null) {
          viewModel.SearchResults = GetSearchResultModels(resultsWithoutPersonalized);
        }
        if (resultsPersonalized != null) {
          viewModel.PersonalizedSearchResults = GetSearchResultModels(resultsPersonalized);
        }
      }
      return View(viewModel);
    }

    private IEnumerable < SearchResultModel > GetSearchResultModels(IContentResult < FashionProduct > contentResult) {
      return contentResult.Select(fashionProduct => new SearchResultModel {
        Name = fashionProduct.Name,
          Brand = fashionProduct.Brand,
          AvailableColors = fashionProduct.AvailableColors,
          Score = contentResult.SearchResult.Hits.First(x => x.Document.ContentLink == fashionProduct.ContentLink).Score.GetValueOrDefault(),
          Url = _urlResolver.GetUrl(fashionProduct.ContentLink),
      });
    }
  }
}

The view for displaying the attributes:

@using EPiServer.Web.Mvc.Html
@model EPiServer.Reference.Commerce.Site.Features.Personalization.ViewModels.PersonalizedFindPageViewModel
<div>
  @Html.PropertyFor(m => m.CurrentPage.MainBody)
</div>

@using (Html.BeginForm("", "", FormMethod.Get, new { @class = "form-inline" }))
  {
    <div class="form-group">
      <input type="text" name="q" id="q" value="@Model.SearchTerm" class="form-control"  />
      <button type="submit" class="btn btn-primary">
        <span class="glyphicon glyphicon-search" aria-hidden="true"></span> Find!
      </button>
    </div>
  }

@if (!string.IsNullOrEmpty(Model.SearchTerm))
  {
    <h2>Your search for <b>@Model.SearchTerm</b> resulted in the following hits:</h2>
    <h3>With personalization</h3>
    <div class="row">
      <div class="col-lg-12">
        <div class="row">
          <div class="col-lg-5">Product</div>
          <div class="col-lg-2">Score</div>
        </div>    
        @foreach (var hit in Model.PersonalizedSearchResults)
          {
            <div class="row">
              <div class="col-lg-5"><a href="@hit.Url">@hit.Name</a></div>
              <div class="col-lg-2">@hit.Score</div>
            </div>
          }
      </div>
    </div>
              
    <h3>Without personalization</h3>
    <div class="row">
      <div class="col-lg-12">
        <div class="row">
          <div class="col-lg-5">Product</div>
          <div class="col-lg-2">Score</div>
        </div>  
        @foreach (var hit in Model.SearchResults)
          {
            <div class="row">
              <div class="col-lg-5">@hit.Name</div>
              <div class="col-lg-2">@hit.Score</div>
            </div>
          }
      </div>
    </div>
  }

<h3>Current attributes from Personalization:</h3>
<table class="table">
  <tr>
    <th>Attribute name</th>
    <th>Attribute value</th>
    <th>Boost factor</th>
  </tr>
  @if (Model.CurrentPersonalizationAttributes != null)
    {
      foreach (var attribute in Model.CurrentPersonalizationAttributes)
        {
          <tr>
            <td>@attribute.Name</td>
            <td>@attribute.Value</td>
            <td>@attribute.Score</td>
          </tr>
        }
    }
</table>

Result

The search query that uses .UsingPersonalization gives boosted results using visitor attributes and orders the search results differently.

1150