diff --git a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Dashboard.aspx.cs b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Dashboard.aspx.cs index 3b6d62a..dd05fd4 100644 --- a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Dashboard.aspx.cs +++ b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Dashboard.aspx.cs @@ -4,7 +4,7 @@ using CMS.Helpers; using CMS.Modules; -using Kentico.Xperience.AlgoliaSearch.Attributes; +using Kentico.Xperience.AlgoliaSearch.Models; using Kentico.Xperience.AlgoliaSearch.Services; using System; @@ -23,7 +23,7 @@ protected void Page_Load(object sender, EventArgs e) var siteIndexes = algoliaRegistrationService.RegisteredIndexes.Where(i => i.SiteNames == null || i.SiteNames.Contains(CurrentSiteName)); if (siteIndexes.Count() == 0) { - ShowInformation("No Algolia indexes registered. See our instructions to read more about creating and registering Algolia indexes."); + ShowInformation("No Algolia indexes registered. See our instructions to read more about creating and registering Algolia indexes."); return; } @@ -42,7 +42,7 @@ private void ShowTaskCount() } - private void LoadIndexes(IEnumerable indexes) + private void LoadIndexes(IEnumerable indexes) { var indexesToList = new List(); var indexStatistics = algoliaSearchService.GetStatistics(); diff --git a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_IndexedContent.aspx.cs b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_IndexedContent.aspx.cs index dc887cd..c13e4ea 100644 --- a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_IndexedContent.aspx.cs +++ b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_IndexedContent.aspx.cs @@ -1,6 +1,7 @@ using CMS.Helpers; using Kentico.Xperience.AlgoliaSearch.Attributes; +using Kentico.Xperience.AlgoliaSearch.Models; using System; using System.Collections.Generic; @@ -12,7 +13,7 @@ namespace Kentico.Xperience.AlgoliaSearch.Pages public partial class AlgoliaSearch_IndexedContent : AlgoliaUIPage { private string indexName; - private RegisterAlgoliaIndexAttribute registerAlgoliaIndexAttribute; + private AlgoliaIndex algoliaIndex; protected override void OnLoad(EventArgs e) @@ -26,16 +27,16 @@ protected override void OnLoad(EventArgs e) return; } - var registerIndexAttribute = algoliaRegistrationService.RegisteredIndexes.FirstOrDefault(i => i.IndexName == indexName); - if (registerIndexAttribute == null || registerIndexAttribute.Type == null) + var foundIndex = algoliaRegistrationService.RegisteredIndexes.FirstOrDefault(i => i.IndexName == indexName); + if (foundIndex == null) { - ShowError("Unable to load index search model class."); + ShowError($"Error loading registered Algolia index '{indexName}.'"); return; } - ShowInformation($"The indexed columns and pages are defined in the class {registerIndexAttribute.Type}. To modify them, please contact your developer."); + ShowInformation($"The indexed columns and pages are defined in the class {foundIndex.Type}. To modify them, please contact your developer."); - registerAlgoliaIndexAttribute = registerIndexAttribute; + algoliaIndex = foundIndex; LoadProperties(); LoadPaths(); } @@ -44,7 +45,7 @@ protected override void OnLoad(EventArgs e) private void LoadProperties() { var indexedProperties = new List(); - var modelProperties = registerAlgoliaIndexAttribute.Type.GetProperties(); + var modelProperties = algoliaIndex.Type.GetProperties(); foreach (var property in modelProperties) { @@ -72,7 +73,7 @@ private void LoadProperties() private void LoadPaths() { var includedContent = new List(); - var includedPathAttributes = registerAlgoliaIndexAttribute.Type.GetCustomAttributes(typeof(IncludedPathAttribute), false); + var includedPathAttributes = algoliaIndex.Type.GetCustomAttributes(typeof(IncludedPathAttribute), false); foreach (var includedPathAttribute in includedPathAttributes) { var includedPath = includedPathAttribute as IncludedPathAttribute; diff --git a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Preview.aspx.cs b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Preview.aspx.cs index 2796344..a5ac931 100644 --- a/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Preview.aspx.cs +++ b/CMS/CMSModules/Kentico.Xperience.AlgoliaSearch/Pages/AlgoliaSearch_Preview.aspx.cs @@ -21,8 +21,8 @@ public partial class AlgoliaSearch_Preview : AlgoliaUIPage protected override void OnLoad(EventArgs e) { base.OnLoad(e); - indexName = QueryHelper.GetString("indexName", ""); - if (indexName == null) + indexName = QueryHelper.GetString("indexName", String.Empty); + if (String.IsNullOrEmpty(indexName)) { ShowError("Unable to load index name."); searchPnl.Visible = false; @@ -31,8 +31,8 @@ protected override void OnLoad(EventArgs e) if (!RequestHelper.IsPostBack()) { - string searchText = QueryHelper.GetString("searchtext", ""); - if (!string.IsNullOrEmpty(searchText)) + string searchText = QueryHelper.GetString("searchtext", String.Empty); + if (!String.IsNullOrEmpty(searchText)) { var searchIndexService = Service.Resolve(); var searchIndex = searchIndexService.InitializeIndex(indexName); diff --git a/README.md b/README.md index c8cbcbd..65279fa 100644 --- a/README.md +++ b/README.md @@ -24,36 +24,24 @@ We recommend that you to create a new [.NET Standard 2.0](https://docs.microsoft } ``` -4. In the live-site project's startup code, call the `AddAlgolia()` extension method: - -```cs -// Startup.cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddAlgolia(Configuration); -} -``` - -5. In your administration project's `web.config` files's `appSettings` section, add the following keys: +4. In your administration project's `web.config` files's `appSettings` section, add the following keys: ```xml ``` +5. Register the `IAlgoliaIndexRegister` service and any indexes by following [this guide](#gear-creating-and-registering-an-algolia-index). 6. (Optional) Import the [Xperience Algolia module](#chart_with_upwards_trend-algolia-search-application-for-administration-interface) in your Xperience website. ## :gear: Creating and registering an Algolia index An Algolia index and its attributes are defined within a single class file, in which your custom class extends the [`AlgoliaSearchModel`](https://github.com/Kentico/xperience-algolia/blob/master/src/Models/AlgoliaSearchModel.cs) class. Within the class, you define the attributes of the index by creating properties which match the names of Xperience page fields to index. The Xperience fields available may come from the `TreeNode` object, `SKUTreeNode` for products, or any custom page type fields. -The index is registered via the [`RegisterAlgoliaIndex`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/RegisterAlgoliaIndexAttribute.cs) attribute which requires the type of the search model class and the code name of the Algolia index. Optionally, you can provide a list of `SiteNames` to which the index is assigned. If not provided, pages from all sites are included. - ```cs using Kentico.Xperience.AlgoliaSearch.Models; using System; -[assembly: RegisterAlgoliaIndex(typeof(AlgoliaSiteSearchModel), AlgoliaSiteSearchModel.IndexName, SiteNames = new string[] { "DancingGoatCore" })] namespace DancingGoat { public class AlgoliaSiteSearchModel : AlgoliaSearchModel @@ -71,6 +59,43 @@ namespace DancingGoat > :ab: The property names (and names used in the [SourceAttribute](#source-attribute)) are __case-insensitive__. This means that your search model can contain an "articletext" property, or an "ArticleText" property- both will work. +Indexes must be registered during application startup in both the administration application and the live-site application. In the __administration__ project, create a [custom module](https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code) and use the `OnPreInit` method to create an `IAlgoliaIndexRegister`, add your indexes, and register the service: + +```cs +protected override void OnPreInit() +{ + base.OnPreInit(); + + Service.Use(new DefaultAlgoliaIndexRegister() + .Add(AlgoliaSiteSearchModel.IndexName) + ); +} +``` + +The `Add` method also accepts an optional list of `SiteNames` to which the index is assigned. If not provided, pages from all sites are included. In the __live-site__ project's startup code, call the `AddAlgolia()` extension method and add your indexes to the `IAlgoliaIndexRegister`: + +```cs +// Startup.cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddAlgolia(Configuration, + new DefaultAlgoliaIndexRegister() + .Add(AlgoliaSiteSearchModel.IndexName) + ); +} +``` + +If you're developing your search solution in multiple environments (e.g. "DEV" and "STG"), it is recommended that you create a unique Algolia index per environment. With this approach, the search functionality can be tested in each environment individually and changes to the index structure or content will not affect other environments. This can be implemented any way you'd like, including some custom service which transforms the index names. The simplest approach would be to prepend some environment name to the index, which is stored in the application settings: + +```cs +var environment = ConfigurationManager.AppSettings["Environment"]; +Service.Use(new DefaultAlgoliaIndexRegister() + .Add($"{environment}-{AlgoliaSiteSearchModel.IndexName}") +); +``` + +> :triangular_flag_on_post: The method of registering indexes via [`RegisterAlgoliaIndex`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/RegisterAlgoliaIndexAttribute.cs) is still supported, but no longer recommended. + ### Determining which pages to index While the above sample code will create an Algolia index, pages in the content tree will not be indexed until one or more [`IncludedPathAttribute`](https://github.com/Kentico/xperience-algolia/blob/master/src/Attributes/IncludedPathAttribute.cs) attributes are applied to the class. The `IncludedPathAttribute` has three properties to configure: @@ -89,48 +114,44 @@ All pages under the specified __AliasPath__ will be indexed regardless of the [p Below is an example of an Algolia index which includes multiple paths and page types: ```cs -[assembly: RegisterAlgoliaIndex(typeof(AlgoliaSiteSearchModel), AlgoliaSiteSearchModel.IndexName)] -namespace DancingGoat +[IncludedPath("/Articles/%", PageTypes = new string[] { Article.CLASS_NAME })] +[IncludedPath("/Store/%", PageTypes = new string[] { "DancingGoatCore.Brewer", "DancingGoatCore.Coffee", "DancingGoatCore.ElectricGrinder", "DancingGoatCore.FilterPack", "DancingGoatCore.ManualGrinder", "DancingGoatCore.Tableware" })] +public class AlgoliaSiteSearchModel : AlgoliaSearchModel { - [IncludedPath("/Articles/%", PageTypes = new string[] { Article.CLASS_NAME })] - [IncludedPath("/Store/%", PageTypes = new string[] { "DancingGoatCore.Brewer", "DancingGoatCore.Coffee", "DancingGoatCore.ElectricGrinder", "DancingGoatCore.FilterPack", "DancingGoatCore.ManualGrinder", "DancingGoatCore.Tableware" })] - public class AlgoliaSiteSearchModel : AlgoliaSearchModel - { - public const string IndexName = "DancingGoatSiteSearch"; + public const string IndexName = "DancingGoatSiteSearch"; - [Searchable, Retrievable] - public string DocumentName { get; set; } + [Searchable, Retrievable] + public string DocumentName { get; set; } - [Url, Retrievable] - [Source(new string[] { nameof(SKUTreeNode.SKU.SKUImagePath), nameof(Article.ArticleTeaser) })] - public string Thumbnail { get; set; } + [Url, Retrievable] + [Source(new string[] { nameof(SKUTreeNode.SKU.SKUImagePath), nameof(Article.ArticleTeaser) })] + public string Thumbnail { get; set; } - [Searchable] - [Source(new string[] { nameof(SKUTreeNode.DocumentSKUDescription), nameof(Article.ArticleText) })] - public string Content { get; set; } + [Searchable] + [Source(new string[] { nameof(SKUTreeNode.DocumentSKUDescription), nameof(Article.ArticleText) })] + public string Content { get; set; } - [Searchable, Retrievable] - [Source(new string[] { nameof(SKUTreeNode.DocumentSKUShortDescription), nameof(Article.ArticleSummary) })] - public string ShortDescription { get; set; } + [Searchable, Retrievable] + [Source(new string[] { nameof(SKUTreeNode.DocumentSKUShortDescription), nameof(Article.ArticleSummary) })] + public string ShortDescription { get; set; } - [Retrievable] - public int SKUID { get; set; } + [Retrievable] + public int SKUID { get; set; } - [Facetable, Retrievable] - public decimal? SKUPrice { get; set; } + [Facetable, Retrievable] + public decimal? SKUPrice { get; set; } - [Retrievable] - public int SKUPublicStatusID { get; set; } + [Retrievable] + public int SKUPublicStatusID { get; set; } - [Retrievable] - public DateTime DocumentCreatedWhen { get; set; } + [Retrievable] + public DateTime DocumentCreatedWhen { get; set; } - [Facetable] - public string CoffeeProcessing { get; set; } + [Facetable] + public string CoffeeProcessing { get; set; } - [Facetable] - public bool CoffeeIsDecaf { get; set; } - } + [Facetable] + public bool CoffeeIsDecaf { get; set; } } ``` @@ -445,7 +466,6 @@ Algolia provides [autocomplete](https://www.algolia.com/doc/ui-libraries/autocom ```cshtml @inject IConfiguration configuration - @{ var algoliaOptions = configuration.GetSection(AlgoliaOptions.SECTION_NAME).Get(); } @@ -700,6 +720,7 @@ private SearchResponse Search(IAlgoliaFacetFilter filter Facets = facetsToRetrieve }; + var searchIndex = _indexService.InitializeIndex(AlgoliaSiteSearchModel.IndexName); return searchIndex.Search(query); } @@ -942,6 +963,7 @@ You can also log an event when a visitor simply views a page with the `LogPageVi public async Task Detail([FromServices] ArticleRepository articleRepository) { var article = articleRepository.GetCurrent(); + await _insightsService.LogPageViewed(article.DocumentID, "Article viewed", AlgoliaSiteSearchModel.IndexName); return new TemplateResult(article); @@ -1011,7 +1033,7 @@ In the appropriate controller, create the action which accepts the facet paramet ```cs [HttpPost] -public Task FacetClicked(string facet) +public async Task FacetClicked(string facet) { if (String.IsNullOrEmpty(facet)) { @@ -1066,7 +1088,6 @@ endpoints.MapControllerRoute( ```cshtml @inject IConfiguration configuration - @{ var algoliaOptions = configuration.GetSection(AlgoliaOptions.SECTION_NAME).Get(); } @@ -1105,7 +1126,7 @@ endpoints.MapControllerRoute(