Skip to content

Instantly share code, notes, and snippets.

@AaronSadlerUK
Created August 7, 2021 18:52
Show Gist options
  • Save AaronSadlerUK/baa1aa299115cd5207c50c7230caf4ec to your computer and use it in GitHub Desktop.
Save AaronSadlerUK/baa1aa299115cd5207c50c7230caf4ec to your computer and use it in GitHub Desktop.
How to create a custom index in Umbraco V9
using Examine;
using Microsoft.Extensions.DependencyInjection;
using UmbHost.Core.Components;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.Examine;
namespace UmbHost.Core.Composers
{
public class IndexHelperComposer : IUserComposer
{
public void Compose(IUmbracoBuilder builder)
{
IServiceCollection services = builder.Services;
services
.AddExamineLuceneIndex<KnowledgebaseIndex, ConfigurationEnabledDirectoryFactory>("KnowledgebaseArticles");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Examine;
using Examine.Lucene;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Examine;
namespace UmbHost.Core.Components
{
public class KnowledgebaseIndex : UmbracoExamineIndex, IUmbracoContentIndex, IDisposable
{
public KnowledgebaseIndex(
ILoggerFactory loggerFactory,
string name,
IOptionsSnapshot<LuceneDirectoryIndexOptions> indexOptions,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState)
: base(loggerFactory, name, indexOptions, hostingEnvironment, runtimeState)
{
loggerFactory.CreateLogger<KnowledgebaseIndex>();
LuceneDirectoryIndexOptions namedOptions = indexOptions.Get(name);
if (namedOptions == null)
{
throw new InvalidOperationException($"No named {typeof(LuceneDirectoryIndexOptions)} options with name {name}");
}
if (namedOptions.Validator is IContentValueSetValidator contentValueSetValidator)
{
PublishedValuesOnly = contentValueSetValidator.PublishedValuesOnly;
}
}
void IIndex.IndexItems(IEnumerable<ValueSet> values) => PerformIndexItems(values.Where(v => v.ItemType == "knowledgebaseArticle"), OnIndexOperationComplete);
}
}
@mistyn8
Copy link

mistyn8 commented Jul 17, 2023

I know this is a couple of years old now, but as it was one of the only things I could find and it inspired my solution for an unprotected index. Here's my take in v12 to give back. I think inheriting from UmbracoContentIndex rather than implementing the interface means gaining the core implementations for updating indexes when un/protecting content events occur. And the options.Validator = new ContentValueSetValidator(true, true, _publicAccessService, _scopeProvider); second option is supportprotected was what I was actually after.

using Examine;
using Examine.Lucene;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Microsoft.Extensions.Options;
using TSD.Core.Models;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Examine;


namespace www.Extensions.Examine
{
    public class IndexHelperComposer : IComposer
    {
        public void Compose(IUmbracoBuilder builder)
        {
            IServiceCollection services = builder.Services;
            services.AddExamineLuceneIndex<TendersUmbracoContentIndex, ConfigurationEnabledDirectoryFactory>(IndexNames.TendersIndexName);
            services.ConfigureOptions<ConfigureTenderIndexOptions>();
        }
    }

    public class TendersUmbracoContentIndex : UmbracoContentIndex, IIndex
    {
        public TendersUmbracoContentIndex(ILoggerFactory loggerFactory, string name, IOptionsMonitor<LuceneDirectoryIndexOptions> indexOptions, Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, ILocalizationService? languageService = null) : base(loggerFactory, name, indexOptions, hostingEnvironment, runtimeState, languageService)
        {
        }

        void IIndex.IndexItems(IEnumerable<ValueSet> values)
        {
            PerformIndexItems(values.Where(x => x.ItemType == nameof(TenderPage).ToFirstLower()), OnIndexOperationComplete);
        }
    }

    public class ConfigureTenderIndexOptions : IConfigureNamedOptions<LuceneDirectoryIndexOptions>
    {
        private readonly IndexCreatorSettings _settings;
        private readonly IPublicAccessService? _publicAccessService;
        [Obsolete ("Update when Umbraco Core ContentValueSetValidator is updated")]
        private readonly Umbraco.Cms.Core.Scoping.IScopeProvider? _scopeProvider;

        public ConfigureTenderIndexOptions(IOptions<IndexCreatorSettings> settings, IPublicAccessService? publicAccessService, Umbraco.Cms.Core.Scoping.IScopeProvider? scopeProvider)
        {
            _settings = settings.Value;
            _scopeProvider = scopeProvider;
            _publicAccessService = publicAccessService;
            _publicAccessService = publicAccessService;
            _scopeProvider = scopeProvider;
        }

        public void Configure(string? name, LuceneDirectoryIndexOptions options)
        {
            if (name?.Equals(IndexNames.TendersIndexName) is false)
            {
                return;
            }

            options.Analyzer = new StandardAnalyzer(LuceneInfo.CurrentVersion);
            options.Validator = new ContentValueSetValidator(true, true, _publicAccessService, _scopeProvider);
            options.FieldDefinitions = new UmbracoFieldDefinitionCollection();

            // so we can sort on the fields
            options.FieldDefinitions.TryAdd(new FieldDefinition("expires", FieldDefinitionTypes.DateTime));
            options.FieldDefinitions.TryAdd(new FieldDefinition("summary", FieldDefinitionTypes.FullTextSortable));

            // ensure indexes are unlocked on startup
            options.UnlockIndex = true;

            if (_settings.LuceneDirectoryFactory == LuceneDirectoryFactory.SyncedTempFileSystemDirectoryFactory)
            {
                // if this directory factory is enabled then a snapshot deletion policy is required
                options.IndexDeletionPolicy = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
            }
        }

        // not used
        public void Configure(LuceneDirectoryIndexOptions options) => throw new NotImplementedException("This is never called and is just part of the interface");
    }

    public static class IndexNames
    {
        public const string TendersIndexName = "TendersIndex";
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment