diff --git a/16/umbraco-cms/reference/searching/examine/indexing.md b/16/umbraco-cms/reference/searching/examine/indexing.md index 268c3babe97..cb8b4fdd099 100644 --- a/16/umbraco-cms/reference/searching/examine/indexing.md +++ b/16/umbraco-cms/reference/searching/examine/indexing.md @@ -144,12 +144,14 @@ Remember to register `ConfigureMemberIndexOptions` in your composer. ## Creating your own index -The following example will show how to create an index that will only include nodes based on the document type _product_. +### A custom Umbraco content index + +The following example will show how to create an index that will only include nodes based on the **Product**. {% hint style="info" %} -We always recommend that you use the existing built in ExternalIndex. You should then query based on the NodeTypeAlias instead of creating a new separate index based on that particular node type. However, should the need arise, the example below will show you how to do it. +It is recommended to use the existing built-in `ExternalIndex`. You should then query based on the `NodeTypeAlias` instead of creating a new separate index based on that particular node type. However, should the need arise, the example below will show you how to do it. -Take a look at our [Examine Quick Start](quick-start.md) to see some examples of how to search the ExternalIndex. +Take a look at the [Examine Quick Start](quick-start.md) guide to see some examples of how to search the ExternalIndex. {% endhint %} To create this index we need five things: @@ -161,7 +163,7 @@ To create this index we need five things: 5. An `INotificationHandler` implementation that updates the index when content changes. 6. A composer that adds all these services to the runtime. -### ProductIndex +#### ProductIndex ```csharp using Examine.Lucene; @@ -190,7 +192,7 @@ public class ProductIndex : UmbracoExamineIndex } ``` -### ConfigureProductIndexOptions +#### ConfigureProductIndexOptions ```csharp using Examine; @@ -238,7 +240,7 @@ public class ConfigureProductIndexOptions : IConfigureNamedOptions } ``` -### ProductIndexPopulator +#### ProductIndexPopulator ```csharp using Examine; @@ -326,7 +328,7 @@ This is only an example of how you could do indexing. In this example, we're ind In certain scenarios only published content should be added to the index. To achieve that, you will need to implement your own logic to filter out unpublished content. This can be somewhat tricky as the published state can vary throughout an entire structure of content nodes in the content tree. For inspiration on how to go about such filtering, you can look at the [ContentIndexPopulator in Umbraco](https://github.com/umbraco/Umbraco-CMS/blob/c878567633a6a3354c1414ccd130c9be518b25f0/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs#L115). {% endhint %} -### ProductIndexingNotificationHandler +#### ProductIndexingNotificationHandler The index will only update its content when you manually trigger an index rebuild in the Examine dashboard. This is not always the desired behavior for a custom index. @@ -452,7 +454,7 @@ public class ProductIndexingNotificationHandler : INotificationHandler GetBooks() => + [ + new() { Id = 1, Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", PublishedYear = 1925 }, + new() { Id = 2, Title = "To Kill a Mockingbird", Author = "Harper Lee", PublishedYear = 1960 }, + new() { Id = 3, Title = "1984", Author = "George Orwell", PublishedYear = 1949 }, + new() { Id = 4, Title = "Pride and Prejudice", Author = "Jane Austen", PublishedYear = 1813 }, + new() { Id = 5, Title = "The Catcher in the Rye", Author = "J.D. Salinger", PublishedYear = 1951 } + ]; +} +``` + +As with the previous example, define an index. At this time, Umbraco data isn't being indexed; the implementation inherits directly from `LuceneIndex`: + +```csharp +using Examine.Lucene; +using Examine.Lucene.Providers; +using Microsoft.Extensions.Options; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class BookIndex : LuceneIndex +{ + public BookIndex( + ILoggerFactory loggerFactory, + string name, + IOptionsMonitor indexOptions) + : base(loggerFactory, name, indexOptions) + { + } +} +``` + +The index is customized and fields are defined as before via `IConfigureNamedOptions`: + +```csharp +using Examine; +using Examine.Lucene; +using Microsoft.Extensions.Options; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureBookIndexOptions : IConfigureNamedOptions +{ + public void Configure(string? name, LuceneDirectoryIndexOptions options) + { + if (name?.Equals("BookIndex") is false) + { + return; + } + + options.FieldDefinitions = new( + new("id", FieldDefinitionTypes.Integer), + new("title", FieldDefinitionTypes.FullText), + new("author", FieldDefinitionTypes.FullText), + new("publishedYear", FieldDefinitionTypes.Integer) + ); + } + + public void Configure(LuceneDirectoryIndexOptions options) + => Configure(string.Empty, options); +} +``` + +And once again, a composer is required to register the necessary components: + +```csharp +using Examine; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +[ComposeAfter(typeof(AddExamineComposer))] +public class ExamineComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddExamineLuceneIndex("BookIndex"); + + builder.Services.ConfigureOptions(); + } +} +``` + +With this in place, the details of the index will be available under the **Settings** > **Examine Management** > **Indexes** screen. + +To verify indexing and querying, a controller can be used: + +```csharp +using Examine; +using Microsoft.AspNetCore.Mvc; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +[ApiController] +[Route("/umbraco/api/books")] +public class BooksController : ControllerBase +{ + private readonly IExamineManager _examineManager; + + public BooksController(IExamineManager examineManager) => _examineManager = examineManager; + + [HttpPost("populateIndex")] + public async Task PopulateIndex() + { + if (_examineManager.TryGetIndex("BookIndex", out IIndex? index) == false) + { + throw new InvalidOperationException("Book index not found"); + } + + List books = BookData.GetBooks(); + foreach (Book book in books) + { + index.IndexItems( + [ + ValueSet.FromObject( + book.Id.ToString(), + "Book", + book), + ]); + } + + return Ok("Done"); + } + + [HttpGet("query")] + public async Task Query(string q) + { + if (_examineManager.TryGetIndex("BookIndex", out IIndex? index) == false) + { + throw new InvalidOperationException("Book index not found"); + } + + var results = index.Searcher.Search(q) + .Select(x => new BookDto(x.Values["title"], x.Values["author"], int.Parse(x.Values["publishedYear"]))) + .ToList(); + return Ok(results); + } + + private class BookDto + { + public BookDto(string title, string author, int publishedYear) + { + Title = title; + Author = author; + PublishedYear = publishedYear; + } + + public string Title { get; } + + public string Author { get; } + + public int PublishedYear { get; } + } +} +``` + + + + + diff --git a/17/umbraco-cms/reference/searching/examine/indexing.md b/17/umbraco-cms/reference/searching/examine/indexing.md index bd2eea25ed7..088c3013f54 100644 --- a/17/umbraco-cms/reference/searching/examine/indexing.md +++ b/17/umbraco-cms/reference/searching/examine/indexing.md @@ -144,12 +144,14 @@ Remember to register `ConfigureMemberIndexOptions` in your composer. ## Creating your own index -The following example will show how to create an index that will only include nodes based on the document type _product_. +### A custom Umbraco content index + +The following example will show how to create an index that will only include nodes based on the **Product**. {% hint style="info" %} -We always recommend that you use the existing built in ExternalIndex. You should then query based on the NodeTypeAlias instead of creating a new separate index based on that particular node type. However, should the need arise, the example below will show you how to do it. +It is recommended to use the existing built-in `ExternalIndex`. You should then query based on the `NodeTypeAlias` instead of creating a new separate index based on that particular node type. However, should the need arise, the example below will show you how to do it. -Take a look at our [Examine Quick Start](quick-start.md) to see some examples of how to search the ExternalIndex. +Take a look at the [Examine Quick Start](quick-start.md) guide to see some examples of how to search the ExternalIndex. {% endhint %} To create this index we need five things: @@ -161,7 +163,7 @@ To create this index we need five things: 5. An `INotificationHandler` implementation that updates the index when content changes. 6. A composer that adds all these services to the runtime. -### ProductIndex +#### ProductIndex ```csharp using Examine.Lucene; @@ -190,7 +192,7 @@ public class ProductIndex : UmbracoExamineIndex } ``` -### ConfigureProductIndexOptions +#### ConfigureProductIndexOptions ```csharp using Examine; @@ -238,7 +240,7 @@ public class ConfigureProductIndexOptions : IConfigureNamedOptions } ``` -### ProductIndexPopulator +#### ProductIndexPopulator ```csharp using Examine; @@ -326,7 +328,7 @@ This is only an example of how you could do indexing. In this example, we're ind In certain scenarios only published content should be added to the index. To achieve that, you will need to implement your own logic to filter out unpublished content. This can be somewhat tricky as the published state can vary throughout an entire structure of content nodes in the content tree. For inspiration on how to go about such filtering, you can look at the [ContentIndexPopulator in Umbraco](https://github.com/umbraco/Umbraco-CMS/blob/c878567633a6a3354c1414ccd130c9be518b25f0/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs#L115). {% endhint %} -### ProductIndexingNotificationHandler +#### ProductIndexingNotificationHandler The index will only update its content when you manually trigger an index rebuild in the Examine dashboard. This is not always the desired behavior for a custom index. @@ -452,7 +454,7 @@ public class ProductIndexingNotificationHandler : INotificationHandler GetBooks() => + [ + new() { Id = 1, Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", PublishedYear = 1925 }, + new() { Id = 2, Title = "To Kill a Mockingbird", Author = "Harper Lee", PublishedYear = 1960 }, + new() { Id = 3, Title = "1984", Author = "George Orwell", PublishedYear = 1949 }, + new() { Id = 4, Title = "Pride and Prejudice", Author = "Jane Austen", PublishedYear = 1813 }, + new() { Id = 5, Title = "The Catcher in the Rye", Author = "J.D. Salinger", PublishedYear = 1951 } + ]; +} +``` + +As with the previous example, define an index. At this time, Umbraco data isn't being indexed; the implementation inherits directly from `LuceneIndex`: + +```csharp +using Examine.Lucene; +using Examine.Lucene.Providers; +using Microsoft.Extensions.Options; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class BookIndex : LuceneIndex +{ + public BookIndex( + ILoggerFactory loggerFactory, + string name, + IOptionsMonitor indexOptions) + : base(loggerFactory, name, indexOptions) + { + } +} +``` + +The index is customized and fields are defined as before via `IConfigureNamedOptions`: + +```csharp +using Examine; +using Examine.Lucene; +using Microsoft.Extensions.Options; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureBookIndexOptions : IConfigureNamedOptions +{ + public void Configure(string? name, LuceneDirectoryIndexOptions options) + { + if (name?.Equals("BookIndex") is false) + { + return; + } + + options.FieldDefinitions = new( + new("id", FieldDefinitionTypes.Integer), + new("title", FieldDefinitionTypes.FullText), + new("author", FieldDefinitionTypes.FullText), + new("publishedYear", FieldDefinitionTypes.Integer) + ); + } + + public void Configure(LuceneDirectoryIndexOptions options) + => Configure(string.Empty, options); +} +``` + +And once again, a composer is required to register the necessary components: + +```csharp +using Examine; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +[ComposeAfter(typeof(AddExamineComposer))] +public class ExamineComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddExamineLuceneIndex("BookIndex"); + + builder.Services.ConfigureOptions(); + } +} +``` + +With this in place, the details of the index will be available under the **Settings** > **Examine Management** > **Indexes** screen. + +To verify indexing and querying, a controller can be used: + +```csharp +using Examine; +using Microsoft.AspNetCore.Mvc; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +[ApiController] +[Route("/umbraco/api/books")] +public class BooksController : ControllerBase +{ + private readonly IExamineManager _examineManager; + + public BooksController(IExamineManager examineManager) => _examineManager = examineManager; + + [HttpPost("populateIndex")] + public async Task PopulateIndex() + { + if (_examineManager.TryGetIndex("BookIndex", out IIndex? index) == false) + { + throw new InvalidOperationException("Book index not found"); + } + + List books = BookData.GetBooks(); + foreach (Book book in books) + { + index.IndexItems( + [ + ValueSet.FromObject( + book.Id.ToString(), + "Book", + book), + ]); + } + + return Ok("Done"); + } + + [HttpGet("query")] + public async Task Query(string q) + { + if (_examineManager.TryGetIndex("BookIndex", out IIndex? index) == false) + { + throw new InvalidOperationException("Book index not found"); + } + + var results = index.Searcher.Search(q) + .Select(x => new BookDto(x.Values["title"], x.Values["author"], int.Parse(x.Values["publishedYear"]))) + .ToList(); + return Ok(results); + } + + private class BookDto + { + public BookDto(string title, string author, int publishedYear) + { + Title = title; + Author = author; + PublishedYear = publishedYear; + } + + public string Title { get; } + + public string Author { get; } + + public int PublishedYear { get; } + } +} +```