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.
-
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.
-
Add the Strawberry Shake CLI tools.
- Create a new folder called ContentGraph:
mkdir ContentGraph
- Go to the folder ContentGraph:
cd ContentGraph
- Create a manifest file by running the dotnet new command:
dotnet new tool-manifest
- Install Strawberry Shake tools locally:
dotnet tool install StrawberryShake.Tools --local
- Create a new folder called ContentGraph:
-
Create Alloy MVC site.
- Create a new folder for the site called AlloyMvcGraphQL:
mkdir AlloyMvcGraphQL
- Go to the folder AlloyMvcGraphQL:
cd AlloyMvcGraphQL
- Install dotnet EPiServerTemplates:
dotnet new --install "EPiServer.Templates"
- Create the Alloy MVC site:
dotnet new epi-alloy-mvc
- Add the latest Optimizely Content Management System (CMS) package:
dotnet add package EPiServer.Cms
- Start the site:
dotnet run
- Browse to the site, add the admin user, and make sure the site is working properly:
https://localhost:5000
- Create a new folder for the site called AlloyMvcGraphQL:
-
Install the required packages for Content Graph and Strawberry Shake.
- Add Strawberry Shake Transport Http:
dotnet add package StrawberryShake.Transport.Http
- Add Strawberry Shake CodeGeneration CSharp Analyzers:
dotnet add package StrawberryShake.CodeGeneration.CSharp.Analyzers
- Add Microsoft Extensions DependencyInjection:
dotnet add package Microsoft.Extensions.DependencyInjection
- Add Microsoft Extensions Http:
dotnet add package Microsoft.Extensions.Http
- Add Optimizely ContentGraph CMS:
dotnet add package Optimizely.ContentGraph.Cms
- Add Strawberry Shake Transport Http:
-
Open folder AlloyMvcGraphQL in Visual Studio Code, which contains the Alloy MVC site you created.
-
Update appsettings.json with the following after "AllowedHosts": "*"
, "Optimizely": { "ContentGraph": { "GatewayAddress": "https://cg.optimizely.com", "AppKey": "", "Secret": "", "SingleKey": "eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ", "AllowSendingLog": "true", "SynchronizationEnabled": false } }
-
Update Startup.cs to use Content Graph; add
using EPiServer.DependencyInjection;
-
Update
constructor
to handleIConfiguration
.private readonly IConfiguration _configuration; public Startup(IWebHostEnvironment webHostingEnvironment, IConfiguration configuration) { _webHostingEnvironment = webHostingEnvironment; _configuration = configuration; }
-
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);
-
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>
-
Build the project:
dotnet build
-
Add Content Graph queries.
- Create a new folder called GraphQL:
mkdir GraphQL
- Add the searchContent.graphql file in GraphQL folder.
- 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 } }
- Create a new folder called GraphQL:
-
Build the project:
dotnet build
-
Check to see that the generated file ContentGraphClient.StrawberryShake.cs is under Generated folder.
-
Update Startup.cs to register the client.
- Add the following in
ConfigureServices
method.services .AddContentGraphClient() .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://cg.optimizely.com/content/v2?auth=eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ"));
- Build the project again to update the GraphQL client:
dotnet build
- Add the following in
-
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)]; } }
-
Browse to the search page and try it out.
- Start the site:
dotnet run
- Browse to
https://localhost:5000/en/search
- Start the site:
Updated about 2 months ago