HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityDoc feedbackLog In

Alloy MVC with Content Graph & Strawberry Shake

Create a standard Alloy MVC sample site, and use Optimizely Content Graph for the search page using Strawberry Shake.

After you created a Content Graph account, and synchronized content from the Alloy MVC site to the account, use this account during this tutorial in a read-only mode.

Visual Studio Code is used in this tutorial with some extensions, but you could use Visual Studio or another IDE.

  1. Add GraphQL extensions to Visual Studio Code by installing the following extensions in Visual Studio Code:

    • GraphQL: Language Feature Support
    • GraphQL
      These extensions in Visual Studio Code let you write GraphQL queries with IntelliSense against Content Graph.
  2. Add the Strawberry Shake CLI tools.

    1. Create a new folder called ContentGraph: mkdir ContentGraph
    2. Go to the folder ContentGraph: cd ContentGraph
    3. Create a manifest file by running the dotnet new command: dotnet new tool-manifest
    4. Install Strawberry Shake tools locally: dotnet tool install StrawberryShake.Tools --local
  3. Create Alloy MVC site.

    1. Create a new folder for the site called AlloyMvcGraphQL: mkdir AlloyMvcGraphQL
    2. Go to the folder AlloyMvcGraphQL: cd AlloyMvcGraphQL
    3. Install dotnet EPiServerTemplates: dotnet new --install "EPiServer.Templates"
    4. Create the Alloy MVC site: dotnet new epi-alloy-mvc
    5. Add the latest Optimizely Content Management System (CMS) package: dotnet add package EPiServer.Cms
    6. Start the site: dotnet run
    7. Browse to the site, add the admin user, and make sure the site is working properly: https://localhost:5000
  4. Install the required packages for Content Graph and Strawberry Shake.

    1. Add Strawberry Shake Transport Http: dotnet add package StrawberryShake.Transport.Http
    2. Add Strawberry Shake CodeGeneration CSharp Analyzers: dotnet add package StrawberryShake.CodeGeneration.CSharp.Analyzers
    3. Add Microsoft Extensions DependencyInjection: dotnet add package Microsoft.Extensions.DependencyInjection
    4. Add Microsoft Extensions Http: dotnet add package Microsoft.Extensions.Http
    5. Add Optimizely ContentGraph CMS: dotnet add package Optimizely.ContentGraph.Cms
  5. Open folder AlloyMvcGraphQL in Visual Studio Code, which contains the Alloy MVC site you created.

  6. Update appsettings.json with the following after "AllowedHosts": "*"

    ,
      "Optimizely": {
        "ContentGraph": {
          "GatewayAddress": "https://cg.optimizely.com",
          "AppKey": "",
          "Secret": "",
          "SingleKey": "eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ",
          "AllowSendingLog": "true",
          "SynchronizationEnabled":  false
        }
      }
    
  7. Update Startup.cs to use Content Graph; add using EPiServer.DependencyInjection;

  8. Update constructor to handle IConfiguration.

        private readonly IConfiguration _configuration;
    
        public Startup(IWebHostEnvironment webHostingEnvironment, IConfiguration configuration)
        {
            _webHostingEnvironment = webHostingEnvironment;
            _configuration = configuration;
        }
    
  9. In ConfigureServices method add:

            services.ConfigureContentApiOptions(o =>
            {
                o.IncludeInternalContentRoots = true;
                o.IncludeSiteHosts = true;
                o.EnablePreviewFeatures = true;
                o.SetValidateTemplateForContentUrl(true);
            });
            services.AddContentDeliveryApi(); // required, for further configurations, please visit: https://docs.developers.optimizely.com/content-cloud/v1.5.0-content-delivery-api/docs/configuration
            services.AddContentGraph(_configuration);
    
  10. Create GraphQL client to the Alloy site using the CLI tools; add the following to AlloyMvcGraphQL.csproj.

        <Target Name="Generate client" BeforeTargets="BeforeBuild">
            <Exec Command="dotnet graphql init https://cg.optimizely.com/content/v2?auth=eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ -n ContentGraphClient" />
        </Target>
    
  11. Build the project: dotnet build

  12. Add Content Graph queries.

    1. Create a new folder called GraphQL: mkdir GraphQL
    2. Add the searchContent.graphql file in GraphQL folder.
    3. Add the following query to the file searchContent.graphql:
      query SearchContent(
        $locale: Locale
        $query: String
      ) {
        Content(
          locale: [$locale]
          where: {
            ContentType: {
              in: "Page"
            }
            _fulltext: {
              contains: $query
            }
          }
        ) {
          items {
            Name
            Url
            _fulltext
          }
          total
        }
      }
      
  13. Build the project: dotnet build

  14. Check to see that the generated file ContentGraphClient.StrawberryShake.cs is under Generated folder.

  15. Update Startup.cs to register the client.

    1. Add the following in ConfigureServices method.
      services
        .AddContentGraphClient()
        .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://cg.optimizely.com/content/v2?auth=eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ"));
      
    2. Build the project again to update the GraphQL client: dotnet build
  16. Update search functionality in Alloy using Content Graph in the search controller in Alloy. The following code updates SearchPageController in Controller folder to use the query.

    using AlloyMvcGraphQL.Models.Pages;
    using AlloyMvcGraphQL.Models.ViewModels;
    using Microsoft.AspNetCore.Mvc;
    
    namespace AlloyMvcGraphQL.Controllers;
    
    public class SearchPageController : PageControllerBase<SearchPage>
    {
        private readonly IContentGraphClient _contentGraphClient;
        private static Lazy<LocaleSerializer> _lazyLocaleSerializer = new Lazy<LocaleSerializer>(() => new LocaleSerializer());
    
        public SearchPageController(IContentGraphClient contentGraphClient)
        {
            _contentGraphClient = contentGraphClient;
        }
    
        public ViewResult Index(SearchPage currentPage, string q)
        {
            var searchHits = new List<SearchContentModel.SearchHit>();
            var total = 0;
    
            if (q != null)
            {
                var locale = _lazyLocaleSerializer.Value.Parse(currentPage.Language.TwoLetterISOLanguageName.ToUpper());
    
                var result = _contentGraphClient.SearchContent.ExecuteAsync(
                    locale: locale,
                    query: q)
                    .GetAwaiter().GetResult();
    
                var searchWords = q.Split(" ");
                foreach (var item in result.Data.Content.Items)
                {
                    searchHits.Add(new SearchContentModel.SearchHit()
                    {
                        Title = item.Name,
                        Url = item.Url,
                        Excerpt = GetExcerpt(q, searchWords, item._fulltext)
                    }); ;
                }
    
                total = result.Data.Content.Total.GetValueOrDefault();
            }
    
            var model = new SearchContentModel(currentPage)
            {
                Hits = searchHits,
                NumberOfHits = total,
                SearchServiceDisabled = false,
                SearchedQuery = q
            };
    
            return View(model);
        }
    
        private string GetExcerpt(string query, IEnumerable<string> querySplitInWords, IEnumerable<string> resultTextsForHit)
        {
            var matchingTexts = new Dictionary<string, int>();
            foreach (var resultText in resultTextsForHit)
            {
                foreach (var searchedWord in querySplitInWords)
                {
                    if (resultText.Contains(searchedWord))
                    {
                        if (!matchingTexts.ContainsKey(resultText))
                        {
                            matchingTexts[resultText] = 0;
                        }
    
                        matchingTexts[resultText]++;
                    }
                }
    
                if (resultText.Contains(query))
                {
                    matchingTexts[resultText] = matchingTexts[resultText] * 10;
                }
            }
    
            string excerpt;
            if (matchingTexts.Any())
            {
                excerpt = string.Join(" .", matchingTexts.OrderByDescending(x => x.Value).Select(x => x.Key));
            }
            else
            {
                excerpt = string.Join(" .", resultTextsForHit);
            }
    
            return excerpt?[0..Math.Min(excerpt.Length, 500)];
        }
    }
    
  17. Browse to the search page and try it out.

    1. Start the site: dotnet run
    2. Browse to https://localhost:5000/en/search