From d19d39d7a69256afbc82febb7073535ca25ad3c0 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Wed, 10 Apr 2024 16:37:31 -0700 Subject: [PATCH] Fix AISearch index prefix and index creation (#15723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky --- .../OrchardCore.Search.AzureAI/Startup.cs | 10 +-- .../Extensions/ServiceCollectionExtensions.cs | 19 +----- ...ureAISearchDefaultOptionsConfigurations.cs | 10 +-- .../Services/AzureAISearchIndexManager.cs | 62 +++++++++++-------- 4 files changed, 46 insertions(+), 55 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Search.AzureAI/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Search.AzureAI/Startup.cs index e0ae4e6b0fa..737e5b4b72e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.AzureAI/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.AzureAI/Startup.cs @@ -1,10 +1,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using OrchardCore.ContentTypes.Editors; using OrchardCore.Deployment; using OrchardCore.DisplayManagement.Handlers; -using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Modules; using OrchardCore.Navigation; using OrchardCore.Search.Abstractions; @@ -16,15 +14,11 @@ namespace OrchardCore.Search.AzureAI; -public class Startup(ILogger logger, IShellConfiguration shellConfiguration) - : StartupBase +public class Startup : StartupBase { - private readonly ILogger _logger = logger; - private readonly IShellConfiguration _shellConfiguration = shellConfiguration; - public override void ConfigureServices(IServiceCollection services) { - services.TryAddAzureAISearchServices(_shellConfiguration, _logger); + services.AddAzureAISearchServices(); services.AddScoped(); services.AddScoped, AzureAISearchDefaultSettingsDisplayDriver>(); } diff --git a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Extensions/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Extensions/ServiceCollectionExtensions.cs index d503af12949..7fb1fac7258 100644 --- a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,10 +1,7 @@ using Microsoft.Extensions.Azure; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.ContentManagement.Handlers; -using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Recipes; using OrchardCore.Search.AzureAI.Handlers; using OrchardCore.Search.AzureAI.Models; @@ -16,22 +13,10 @@ namespace OrchardCore.Search.AzureAI; public static class ServiceCollectionExtensions { - public static bool TryAddAzureAISearchServices(this IServiceCollection services, IShellConfiguration configuration, ILogger logger) + public static IServiceCollection AddAzureAISearchServices(this IServiceCollection services) { - var section = configuration.GetSection("OrchardCore_AzureAISearch"); - - var options = section.Get(); - var configExists = true; - if (string.IsNullOrWhiteSpace(options?.Endpoint) || string.IsNullOrWhiteSpace(options?.Credential?.Key)) - { - configExists = false; - logger.LogError("Azure AI Search module is enabled. However, the connection settings are not provided in configuration file."); - } - services.AddTransient, AzureAISearchDefaultOptionsConfigurations>(); - services.AddAzureClientsCore(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -44,6 +29,6 @@ public static bool TryAddAzureAISearchServices(this IServiceCollection services, services.AddRecipeExecutionStep(); services.AddRecipeExecutionStep(); - return configExists; + return services; } } diff --git a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchDefaultOptionsConfigurations.cs b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchDefaultOptionsConfigurations.cs index 51b799e4e70..a54eae036a8 100644 --- a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchDefaultOptionsConfigurations.cs +++ b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchDefaultOptionsConfigurations.cs @@ -28,13 +28,14 @@ public AzureAISearchDefaultOptionsConfigurations( public async void Configure(AzureAISearchDefaultOptions options) { - var fileOptions = _shellConfiguration.GetSection("OrchardCore_AzureAISearch").Get() + var fileOptions = _shellConfiguration.GetSection("OrchardCore_AzureAISearch") + .Get() ?? new AzureAISearchDefaultOptions(); - // This should be called first to set whether or not the file configs are set or not. + // This should be called first determine whether the file configs are set or not. options.SetFileConfigurationExists(HasConnectionInfo(fileOptions)); - // The DisableUIConfiguration should always be set using the file options only. + // The 'DisableUIConfiguration' should always be set from the file-options. options.DisableUIConfiguration = fileOptions.DisableUIConfiguration; options.Analyzers = fileOptions.Analyzers == null || fileOptions.Analyzers.Length == 0 @@ -104,6 +105,7 @@ private static bool HasConnectionInfo(AzureAISearchDefaultOptions options) return false; } - return options.AuthenticationType != AzureAIAuthenticationType.ApiKey || !string.IsNullOrEmpty(options.Credential?.Key); + return options.AuthenticationType != AzureAIAuthenticationType.ApiKey || + !string.IsNullOrEmpty(options.Credential?.Key); } } diff --git a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchIndexManager.cs b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchIndexManager.cs index c55d01a31d5..278f291a305 100644 --- a/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchIndexManager.cs +++ b/src/OrchardCore/OrchardCore.Search.AzureAI.Core/Services/AzureAISearchIndexManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Azure; using Azure.Search.Documents.Indexes.Models; @@ -15,27 +14,37 @@ namespace OrchardCore.Search.AzureAI.Services; -public class AzureAISearchIndexManager( - AzureAIClientFactory clientFactory, - ILogger logger, - IOptions azureAIOptions, - IEnumerable indexEvents, - IMemoryCache memoryCache, - ShellSettings shellSettings) +public class AzureAISearchIndexManager { public const string OwnerKey = "Content__ContentItem__Owner"; public const string AuthorKey = "Content__ContentItem__Author"; public const string FullTextKey = "Content__ContentItem__FullText"; public const string DisplayTextAnalyzedKey = "Content__ContentItem__DisplayText__Analyzed"; - private const string _prefixCacheKey = "AzureAISearchIndexesPrefix"; - - private readonly AzureAIClientFactory _clientFactory = clientFactory; - private readonly ILogger _logger = logger; - private readonly IEnumerable _indexEvents = indexEvents; - private readonly IMemoryCache _memoryCache = memoryCache; - private readonly ShellSettings _shellSettings = shellSettings; - private readonly AzureAISearchDefaultOptions _azureAIOptions = azureAIOptions.Value; + private readonly AzureAIClientFactory _clientFactory; + private readonly ILogger _logger; + private readonly IEnumerable _indexEvents; + private readonly IMemoryCache _memoryCache; + private readonly ShellSettings _shellSettings; + private readonly AzureAISearchDefaultOptions _azureAIOptions; + private readonly string _prefixCacheKey; + + public AzureAISearchIndexManager( + AzureAIClientFactory clientFactory, + ILogger logger, + IEnumerable indexEvents, + IMemoryCache memoryCache, + ShellSettings shellSettings, + IOptions azureAIOptions) + { + _clientFactory = clientFactory; + _logger = logger; + _indexEvents = indexEvents; + _memoryCache = memoryCache; + _shellSettings = shellSettings; + _azureAIOptions = azureAIOptions.Value; + _prefixCacheKey = $"AzureAISearchIndexesPrefix_{shellSettings.Name}"; + } public async Task CreateAsync(AzureAISearchIndexSettings settings) { @@ -54,7 +63,7 @@ public async Task CreateAsync(AzureAISearchIndexSettings settings) var client = _clientFactory.CreateSearchIndexClient(); - var response = client.CreateIndexAsync(searchIndex); + await client.CreateIndexAsync(searchIndex); await _indexEvents.InvokeAsync((handler, ctx) => handler.CreatedAsync(ctx), context, _logger); @@ -62,7 +71,7 @@ public async Task CreateAsync(AzureAISearchIndexSettings settings) } catch (Exception ex) { - _logger.LogError(ex, "Unable to create index in Azure AI Search."); + _logger.LogError(ex, "Unable to create index in Azure AI Search. Message: {Message}", ex.Message); } return false; @@ -113,7 +122,7 @@ public async Task DeleteAsync(string indexName) var client = _clientFactory.CreateSearchIndexClient(); - var response = await client.DeleteIndexAsync(context.IndexFullName); + await client.DeleteIndexAsync(context.IndexFullName); await _indexEvents.InvokeAsync((handler, ctx) => handler.RemovedAsync(ctx), context, _logger); @@ -144,7 +153,7 @@ public async Task RebuildAsync(AzureAISearchIndexSettings settings) var searchIndex = GetSearchIndex(context.IndexFullName, settings); - var response = await client.CreateIndexAsync(searchIndex); + await client.CreateIndexAsync(searchIndex); await _indexEvents.InvokeAsync((handler, ctx) => handler.RebuiltAsync(ctx), context, _logger); } @@ -165,21 +174,22 @@ private string GetIndexPrefix() { if (!_memoryCache.TryGetValue(_prefixCacheKey, out var value)) { - var builder = new StringBuilder(); + var prefix = _shellSettings.Name.ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(_azureAIOptions.IndexesPrefix)) { - builder.Append(_azureAIOptions.IndexesPrefix.ToLowerInvariant()); - builder.Append('-'); + prefix = $"{_azureAIOptions.IndexesPrefix.ToLowerInvariant()}-{prefix}"; } - builder.Append(_shellSettings.Name.ToLowerInvariant()); - - if (AzureAISearchIndexNamingHelper.TryGetSafePrefix(builder.ToString(), out var safePrefix)) + if (AzureAISearchIndexNamingHelper.TryGetSafePrefix(prefix, out var safePrefix)) { value = safePrefix; _memoryCache.Set(_prefixCacheKey, safePrefix); } + else + { + throw new InvalidOperationException($"Unable to create a safe index prefix for AI Search. Attempted to created a safe name using '{safePrefix}'."); + } } return value ?? string.Empty;