diff --git a/CoreWiki.Application/Articles/Search/ArticleRepositorySearchIndexingProxy.cs b/CoreWiki.Application/Articles/Search/ArticleRepositorySearchIndexingProxy.cs new file mode 100644 index 00000000..b18b66f5 --- /dev/null +++ b/CoreWiki.Application/Articles/Search/ArticleRepositorySearchIndexingProxy.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CoreWiki.Core.Domain; +using CoreWiki.Data.Abstractions.Interfaces; + +namespace CoreWiki.Application.Articles.Search +{ + /// + /// Proxy pattern: Using a middleman to extend functionality of original class but not changing it. + /// Ex caching, when you dont want/need to muddle the original class with logic for another resposibility, + /// Or here when we want to extend our app with a searchengine, wich needs indexing. The repository dont need to know we do indexing somewere else + /// + public class ArticleRepositorySearchIndexingProxy : IArticleRepository + { + private readonly ISearchProvider
_searchProvider; + private readonly IArticleRepository _repository; + + public ArticleRepositorySearchIndexingProxy(ISearchProvider
searchProvider, Func repository) + { + _searchProvider = searchProvider; + _repository = repository(1); + } + + public Task
CreateArticleAndHistory(Article article) + { + _searchProvider.IndexElementsAsync(article); + return _repository.CreateArticleAndHistory(article); + } + + public Task
Delete(string slug) + { + return _repository.Delete(slug); + } + + public void Dispose() + { + _repository.Dispose(); + } + + // GetFrom Search + public Task Exists(int id) + { + return _repository.Exists(id); + } + + public Task
GetArticleById(int articleId) + { + return _repository.GetArticleById(articleId); + } + + public Task
GetArticleBySlug(string articleSlug) + { + return _repository.GetArticleBySlug(articleSlug); + } + + // TODO: Search should not be a part of repository + public (IEnumerable
articles, int totalFound) GetArticlesForSearchQuery(string filteredQuery, int offset, int resultsPerPage) + { + return _repository.GetArticlesForSearchQuery(filteredQuery, offset, resultsPerPage); + } + + public Task
GetArticleWithHistoriesBySlug(string articleSlug) + { + return _repository.GetArticleWithHistoriesBySlug(articleSlug); + } + + // TODO: get from search + public Task> GetLatestArticles(int numOfArticlesToGet) + { + return _repository.GetLatestArticles(numOfArticlesToGet); + } + + //TODO: update search? + public Task IncrementViewCount(string slug) + { + return _repository.IncrementViewCount(slug); + } + + //TODO: Topic from Search + public Task IsTopicAvailable(string articleSlug, int articleId) + { + return _repository.IsTopicAvailable(articleSlug, articleId); + } + + public Task Update(Article article) + { + _searchProvider.IndexElementsAsync(article); + return _repository.Update(article); + } + } +} diff --git a/CoreWiki.Application/Articles/Search/IArticlesSearchEngine.cs b/CoreWiki.Application/Articles/Search/IArticlesSearchEngine.cs index ee0091b3..593ed051 100644 --- a/CoreWiki.Application/Articles/Search/IArticlesSearchEngine.cs +++ b/CoreWiki.Application/Articles/Search/IArticlesSearchEngine.cs @@ -1,6 +1,5 @@ -using System.Threading.Tasks; -using CoreWiki.Application.Articles.Search.Dto; -using CoreWiki.Core.Domain; +using CoreWiki.Application.Articles.Search.Dto; +using System.Threading.Tasks; namespace CoreWiki.Application.Articles.Search { diff --git a/CoreWiki.Application/Articles/Search/ISearchProvider.cs b/CoreWiki.Application/Articles/Search/ISearchProvider.cs new file mode 100644 index 00000000..8562445e --- /dev/null +++ b/CoreWiki.Application/Articles/Search/ISearchProvider.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CoreWiki.Application.Articles.Search +{ + public interface ISearchProvider where T : class + { + Task<(IEnumerable results, long total)> SearchAsync(string Query, int pageNumber, int resultsPerPage); + + Task IndexElementsAsync(params T[] items); + } +} diff --git a/CoreWiki.Application/Articles/Search/Impl/ArticlesDbSearchEngine.cs b/CoreWiki.Application/Articles/Search/Impl/ArticlesDbSearchEngine.cs index a3f0d397..4b37f776 100644 --- a/CoreWiki.Application/Articles/Search/Impl/ArticlesDbSearchEngine.cs +++ b/CoreWiki.Application/Articles/Search/Impl/ArticlesDbSearchEngine.cs @@ -1,7 +1,7 @@ using AutoMapper; using CoreWiki.Application.Articles.Search.Dto; using CoreWiki.Core.Domain; -using CoreWiki.Data.Abstractions.Interfaces; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -9,33 +9,50 @@ namespace CoreWiki.Application.Articles.Search.Impl { public class ArticlesDbSearchEngine : IArticlesSearchEngine { - - private readonly IArticleRepository _articleRepo; + private readonly ISearchProvider
_searchProvider; private readonly IMapper _mapper; - public ArticlesDbSearchEngine(IArticleRepository articleRepo, IMapper mapper) + public ArticlesDbSearchEngine(ISearchProvider
searchProvider, IMapper mapper) { - _articleRepo = articleRepo; + _searchProvider = searchProvider; _mapper = mapper; } public async Task> SearchAsync(string query, int pageNumber, int resultsPerPage) { var filteredQuery = query.Trim(); - var offset = (pageNumber - 1) * resultsPerPage; + var (articles, totalFound) = await _searchProvider.SearchAsync(filteredQuery, pageNumber, resultsPerPage).ConfigureAwait(false); + + // TODO maybe make this searchproviders problem + var total = int.TryParse(totalFound.ToString(), out var inttotal); + if (!total) + { + inttotal = int.MaxValue; + } - var (articles, totalFound) = _articleRepo.GetArticlesForSearchQuery(filteredQuery, offset, resultsPerPage); + return _mapper.CreateArticleResultDTO(filteredQuery, articles, pageNumber, resultsPerPage, inttotal); + } + } + internal static class SearchResultFactory + { + internal static SearchResultDto CreateArticleResultDTO(this IMapper mapper, string query, IEnumerable
articles, int currenPage, int resultsPerPage, int totalResults) + { + var results = new List
(); + if (articles?.Any() == true) + { + results = articles.ToList(); + } var result = new SearchResult
{ - Query = filteredQuery, - Results = articles.ToList(), - CurrentPage = pageNumber, + Query = query, + Results = results, + CurrentPage = currenPage, ResultsPerPage = resultsPerPage, - TotalResults = totalFound + TotalResults = totalResults }; - return _mapper.Map>(result); + return mapper.Map>(result); } } } diff --git a/CoreWiki.Application/Articles/Search/Impl/LocalDb/LocalDbArticleSearchProviderAdapter.cs b/CoreWiki.Application/Articles/Search/Impl/LocalDb/LocalDbArticleSearchProviderAdapter.cs new file mode 100644 index 00000000..31e67fcb --- /dev/null +++ b/CoreWiki.Application/Articles/Search/Impl/LocalDb/LocalDbArticleSearchProviderAdapter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CoreWiki.Core.Domain; +using CoreWiki.Data.Abstractions.Interfaces; +using Microsoft.Extensions.Logging; + +namespace CoreWiki.Application.Articles.Search.Impl +{ + /// + /// Adapter pattern: When using local DB, convert Concrete Articlesearch to Generic ISearchProvider + /// + /// + public class LocalDbArticleSearchProviderAdapter : ISearchProvider where T : Article + { + private readonly ILogger _logger; + private readonly IArticleRepository _articleRepo; + + public LocalDbArticleSearchProviderAdapter(ILogger> logger, Func articleRepo) + { + _logger = logger; + _articleRepo = articleRepo(1); + } + + public Task IndexElementsAsync(params T[] items) + { + // For LocalDB DB itself is responsible for "Indexing" + return Task.Run(() => items.Length); + } + + public async Task<(IEnumerable results, long total)> SearchAsync(string Query, int pageNumber, int resultsPerPage) + { + var offset = (pageNumber - 1) * resultsPerPage; + var (articles, totalFound) = _articleRepo.GetArticlesForSearchQuery(Query, offset, resultsPerPage); + + var supportedType = articles.GetType().GetGenericArguments()[0]; + if (typeof(T) == supportedType) + { + var tlist = articles.Cast(); + return (results: tlist, total: totalFound); + } + + _logger.LogWarning($"{nameof(SearchAsync)}: Only supports search for {nameof(supportedType)} but asked for {typeof(T).FullName}"); + return (Enumerable.Empty(), 0); + } + } +} diff --git a/CoreWiki.Application/Articles/Search/Queries/SearchArticlesHandler.cs b/CoreWiki.Application/Articles/Search/Queries/SearchArticlesHandler.cs index 6b9ea376..0e294eb8 100644 --- a/CoreWiki.Application/Articles/Search/Queries/SearchArticlesHandler.cs +++ b/CoreWiki.Application/Articles/Search/Queries/SearchArticlesHandler.cs @@ -1,11 +1,11 @@ -using System.Threading; -using System.Threading.Tasks; -using CoreWiki.Application.Articles.Search.Dto; +using CoreWiki.Application.Articles.Search.Dto; using MediatR; +using System.Threading; +using System.Threading.Tasks; namespace CoreWiki.Application.Articles.Search.Queries { - class SearchArticlesHandler: IRequestHandler> + internal class SearchArticlesHandler : IRequestHandler> { private readonly IArticlesSearchEngine _articlesSearchEngine; @@ -13,6 +13,7 @@ public SearchArticlesHandler(IArticlesSearchEngine articlesSearchEngine) { _articlesSearchEngine = articlesSearchEngine; } + public Task> Handle(SearchArticlesQuery request, CancellationToken cancellationToken) { return _articlesSearchEngine.SearchAsync(request.Query, request.PageNumber, request.ResultsPerPage); diff --git a/CoreWiki.Application/Articles/Search/SearchProviderSettings.cs b/CoreWiki.Application/Articles/Search/SearchProviderSettings.cs new file mode 100644 index 00000000..4c53b7f1 --- /dev/null +++ b/CoreWiki.Application/Articles/Search/SearchProviderSettings.cs @@ -0,0 +1,9 @@ +namespace CoreWiki.Application.Articles.Search +{ + public class SearchProviderSettings + { + public string Az_ApiGateway { get; set; } + public string Az_ReadApiKey { get; set; } + public string Az_WriteApiKey { get; set; } + } +} diff --git a/CoreWiki.Application/CoreWiki.Application.csproj b/CoreWiki.Application/CoreWiki.Application.csproj index 0d588674..01f270a0 100644 --- a/CoreWiki.Application/CoreWiki.Application.csproj +++ b/CoreWiki.Application/CoreWiki.Application.csproj @@ -11,11 +11,13 @@ + + diff --git a/CoreWiki.Azure/Areas/AzureSearch/ArticlesAzureSearcSearchEngine.cs b/CoreWiki.Azure/Areas/AzureSearch/ArticlesAzureSearcSearchEngine.cs new file mode 100644 index 00000000..307fce66 --- /dev/null +++ b/CoreWiki.Azure/Areas/AzureSearch/ArticlesAzureSearcSearchEngine.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CoreWiki.Application.Articles.Search; +using Microsoft.Azure.Search; +using Microsoft.Azure.Search.Models; +using Microsoft.Extensions.Logging; + +namespace CoreWiki.Azure.Areas.AzureSearch +{ + /// + /// Tutorial here: https://github.com/Azure-Samples/search-dotnet-getting-started/blob/master/DotNetHowTo/DotNetHowTo/Program.cs + /// + /// + public class AzureSearchProvider : ISearchProvider where T : class + { + private readonly ILogger _logger; + private readonly IAzureSearchClient _searchClient; + private readonly ISearchIndexClient _myclient; + + public AzureSearchProvider(ILogger> logger, IAzureSearchClient searchClient) + { + _logger = logger; + _searchClient = searchClient; + _myclient = _searchClient.GetSearchClient(); + } + + public async Task IndexElementsAsync(params T[] items) + { + var action = items.Select(IndexAction.MergeOrUpload); + var job = new IndexBatch(action); + + try + { + var myclient = _searchClient.CreateServiceClient(); + var res = await _myclient.Documents.IndexAsync(job).ConfigureAwait(false); + return res.Results.Count; + } + catch (IndexBatchException e) + { + // Sometimes when your Search service is under load, indexing will fail for some of the documents in + // the batch. Depending on your application, you can take compensating actions like delaying and + // retrying. For this simple demo, we just log the failed document keys and continue. + + var failedElements = e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key); + _logger.LogError(e, "Failed to index some of the documents", failedElements); + return items.Length - failedElements.Count(); + } + } + + public async Task<(IEnumerable results, long total)> SearchAsync(string Query, int pageNumber, int resultsPerPage) + { + var offset = (pageNumber - 1) * resultsPerPage; + var parameters = new SearchParameters() + { + IncludeTotalResultCount = true, + Top = resultsPerPage, + Skip = offset, + }; + try + { + var res = await _myclient.Documents.SearchAsync(Query, parameters).ConfigureAwait(false); + + var total = res.Count.GetValueOrDefault(); + var list = res.Results; + //TODO: map results + + return (results: null, total: total); + } + catch (System.Exception e) + { + _logger.LogCritical(e, $"{nameof(SearchAsync)} Search failed horribly, you should check it out"); + return (results: null, total: 0); + } + } + } +} diff --git a/CoreWiki.Azure/Areas/AzureSearch/AzureSearchClient.cs b/CoreWiki.Azure/Areas/AzureSearch/AzureSearchClient.cs new file mode 100644 index 00000000..33f99bb1 --- /dev/null +++ b/CoreWiki.Azure/Areas/AzureSearch/AzureSearchClient.cs @@ -0,0 +1,69 @@ +using CoreWiki.Application.Articles.Search; +using Microsoft.Azure.Search; +using Microsoft.Azure.Search.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace CoreWiki.Azure.Areas.AzureSearch +{ + public class AzureSearchClient : IAzureSearchClient + { + private readonly IOptionsSnapshot _config; + private readonly IConfiguration _configuration; + + private string SearchServiceName => _config.Value.Az_ApiGateway; + private string AdminApiKey => _config.Value.Az_WriteApiKey; + private string QueryApiKey => _config.Value.Az_ReadApiKey; + + private string GetIndexName() + { + return typeof(T).Name.ToLowerInvariant(); + } + + public AzureSearchClient(IOptionsSnapshot config, IConfiguration configuration) + { + _config = config; + _configuration = configuration; + } + + public ISearchIndexClient CreateServiceClient() + { + var index = typeof(T).FullName; + var serviceClient = new SearchServiceClient(SearchServiceName, new SearchCredentials(AdminApiKey)); + return GetOrCreateIndex(serviceClient); + } + + private ISearchIndexClient GetOrCreateIndex(SearchServiceClient serviceClient) + { + //indexname must be lowercase + var index = GetIndexName(); + if (serviceClient.Indexes.Exists(index)) + { + return serviceClient.Indexes.GetClient(index); + } + + try + { + var definition = new Index() + { + Name = index, + // NodaTimecrash + Fields = FieldBuilder.BuildForType() + }; + var createdindex = serviceClient.Indexes.Create(definition); + } + catch (System.Exception e) + { + throw; + } + + return serviceClient.Indexes.GetClient(index); + } + + public ISearchIndexClient GetSearchClient() + { + var indexClient = new SearchIndexClient(SearchServiceName, GetIndexName(), new SearchCredentials(QueryApiKey)); + return indexClient; + } + } +} diff --git a/CoreWiki.Azure/Areas/AzureSearch/IAzureSearchClient.cs b/CoreWiki.Azure/Areas/AzureSearch/IAzureSearchClient.cs new file mode 100644 index 00000000..c96fb2b3 --- /dev/null +++ b/CoreWiki.Azure/Areas/AzureSearch/IAzureSearchClient.cs @@ -0,0 +1,21 @@ +using Microsoft.Azure.Search; + +namespace CoreWiki.Azure.Areas.AzureSearch +{ + public interface IAzureSearchClient + { + /// + /// This client can be used for search + /// + /// + /// + ISearchIndexClient GetSearchClient(); + + /// + /// This client can be used to Index elements + /// + /// + /// + ISearchIndexClient CreateServiceClient(); + } +} diff --git a/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml b/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml new file mode 100644 index 00000000..d86d51fb --- /dev/null +++ b/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml @@ -0,0 +1,16 @@ +@page +@model CoreWiki.Azure.MyFeature.Pages.Page1Model +@{ + Layout = null; +} + + + + + + + Page1 + + + + diff --git a/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml.cs b/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml.cs new file mode 100644 index 00000000..918e4398 --- /dev/null +++ b/CoreWiki.Azure/Areas/MyFeature/Pages/Page1.cshtml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace CoreWiki.Azure.MyFeature.Pages +{ + public class Page1Model : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/CoreWiki.Azure/CoreWiki.Azure.csproj b/CoreWiki.Azure/CoreWiki.Azure.csproj new file mode 100644 index 00000000..60226c50 --- /dev/null +++ b/CoreWiki.Azure/CoreWiki.Azure.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp2.1 + + + + + + + + + + diff --git a/CoreWiki.Data/StartupExtensions.cs b/CoreWiki.Data/StartupExtensions.cs index faf5b4dc..c440aa78 100644 --- a/CoreWiki.Data/StartupExtensions.cs +++ b/CoreWiki.Data/StartupExtensions.cs @@ -1,9 +1,9 @@ -using CoreWiki.Data.EntityFramework.Repositories; +using System; +using CoreWiki.Data.Abstractions.Interfaces; +using CoreWiki.Data.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using CoreWiki.Data.Abstractions.Interfaces; -using System; namespace CoreWiki.Data.EntityFramework { @@ -17,12 +17,14 @@ public static class StartupExtensions /// /// /// - public static IServiceCollection AddRepositories(this IServiceCollection services, IConfiguration config) { + public static IServiceCollection AddRepositories(this IServiceCollection services, IConfiguration config) + { Action optionsBuilder; var connectionString = config.GetConnectionString("CoreWikiData"); - switch (config["DataProvider"].ToLowerInvariant()) { + switch (config["DataProvider"].ToLowerInvariant()) + { case "postgres": services.AddEntityFrameworkNpgsql(); optionsBuilder = options => options.UseNpgsql(connectionString); @@ -34,20 +36,22 @@ public static IServiceCollection AddRepositories(this IServiceCollection service break; } - services.AddDbContextPool(options => { + services.AddDbContextPool(options => + { optionsBuilder(options); options.EnableSensitiveDataLogging(); }); // db repos - services.AddTransient(); + //services.AddTransient(); services.AddTransient(); services.AddTransient(); return services; } - public static IServiceScope SeedData(this IServiceScope serviceScope) { + public static IServiceScope SeedData(this IServiceScope serviceScope) + { var context = serviceScope.ServiceProvider.GetService(); diff --git a/CoreWiki.sln b/CoreWiki.sln index 6828309d..5d0512a0 100644 --- a/CoreWiki.sln +++ b/CoreWiki.sln @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{ pipelines\azure-pipelines-cake.yml = pipelines\azure-pipelines-cake.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreWiki.Azure", "CoreWiki.Azure\CoreWiki.Azure.csproj", "{6DF86E8A-DC29-4621-ACFD-849A26E0F003}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +87,10 @@ Global {192CBE23-68A7-4437-A2E1-B88FC3DE152B}.Debug|Any CPU.Build.0 = Debug|Any CPU {192CBE23-68A7-4437-A2E1-B88FC3DE152B}.Release|Any CPU.ActiveCfg = Release|Any CPU {192CBE23-68A7-4437-A2E1-B88FC3DE152B}.Release|Any CPU.Build.0 = Release|Any CPU + {6DF86E8A-DC29-4621-ACFD-849A26E0F003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DF86E8A-DC29-4621-ACFD-849A26E0F003}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DF86E8A-DC29-4621-ACFD-849A26E0F003}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DF86E8A-DC29-4621-ACFD-849A26E0F003}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CoreWiki/Configuration/Settings/AppSettings.cs b/CoreWiki/Configuration/Settings/AppSettings.cs index a300a54b..90aaaf63 100644 --- a/CoreWiki/Configuration/Settings/AppSettings.cs +++ b/CoreWiki/Configuration/Settings/AppSettings.cs @@ -5,12 +5,10 @@ namespace CoreWiki.Configuration.Settings { public class AppSettings { - public Uri Url { get; set; } public Connectionstrings ConnectionStrings { get; set; } public Comments Comments { get; set; } public EmailNotifications EmailNotifications { get; set; } public CspSettings CspSettings { get; set; } - } } diff --git a/CoreWiki/Configuration/Startup/ConfigureScopedServices.cs b/CoreWiki/Configuration/Startup/ConfigureScopedServices.cs index d363fc5c..eda46693 100644 --- a/CoreWiki/Configuration/Startup/ConfigureScopedServices.cs +++ b/CoreWiki/Configuration/Startup/ConfigureScopedServices.cs @@ -1,6 +1,4 @@ -using CoreWiki.Application.Articles.Search; -using CoreWiki.Application.Articles.Search.Impl; -using CoreWiki.Notifications; +using CoreWiki.Notifications; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -19,10 +17,8 @@ public static IServiceCollection ConfigureScopedServices(this IServiceCollection services.AddSingleton(); services.AddEmailNotifications(configuration); - services.AddScoped(); services.AddProgressiveWebApp(new PwaOptions { EnableCspNonce = true }); - return services; } } diff --git a/CoreWiki/Configuration/Startup/ConfigureSearchProvider.cs b/CoreWiki/Configuration/Startup/ConfigureSearchProvider.cs new file mode 100644 index 00000000..017821a8 --- /dev/null +++ b/CoreWiki/Configuration/Startup/ConfigureSearchProvider.cs @@ -0,0 +1,47 @@ +using System; +using CoreWiki.Application.Articles.Search; +using CoreWiki.Application.Articles.Search.Impl; +using CoreWiki.Azure.Areas.AzureSearch; +using CoreWiki.Data.Abstractions.Interfaces; +using CoreWiki.Data.EntityFramework.Repositories; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace CoreWiki.Configuration.Startup +{ + public static partial class ConfigurationExtensions + { + public static IServiceCollection ConfigureSearchProvider(this IServiceCollection services, IConfiguration configuration) + { + services.AddScoped(); + + //Testing scrutor (https://github.com/khellang/Scrutor): getting overflowexception + //services.AddScoped(); + //services.Decorate(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped>(serviceProvider => key => + { + //TODO: enum or other DI that supports decorator + switch (key) + { + default: + return serviceProvider.GetService(); + } + }); + + switch (configuration["SearchProvider"]) + { + case "Az": + services.AddTransient(typeof(ISearchProvider<>), typeof(AzureSearchProvider<>)); + services.AddTransient(); + break; + default: + services.AddTransient(typeof(ISearchProvider<>), typeof(LocalDbArticleSearchProviderAdapter<>)); + break; + } + return services; + } + } +} diff --git a/CoreWiki/CoreWiki.csproj b/CoreWiki/CoreWiki.csproj index caa14869..ea4f8255 100644 --- a/CoreWiki/CoreWiki.csproj +++ b/CoreWiki/CoreWiki.csproj @@ -21,6 +21,7 @@ + @@ -33,6 +34,7 @@ + diff --git a/CoreWiki/Startup.cs b/CoreWiki/Startup.cs index ea9bd4cc..1fcc33fd 100644 --- a/CoreWiki/Startup.cs +++ b/CoreWiki/Startup.cs @@ -1,3 +1,4 @@ +using CoreWiki.Application.Articles.Search; using CoreWiki.Configuration.Settings; using CoreWiki.Configuration.Startup; using CoreWiki.Data.EntityFramework.Security; @@ -7,13 +8,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System.Threading.Tasks; namespace CoreWiki { public class Startup { - public Startup(IConfiguration configuration) { Configuration = configuration; @@ -24,7 +23,6 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.ConfigureAutomapper(); services.ConfigureRSSFeed(); @@ -32,6 +30,8 @@ public void ConfigureServices(IServiceCollection services) services.ConfigureSecurityAndAuthentication(); services.ConfigureDatabase(Configuration); services.ConfigureScopedServices(Configuration); + services.Configure(Configuration.GetSection(nameof(SearchProviderSettings))); + services.ConfigureSearchProvider(Configuration); services.ConfigureRouting(); services.ConfigureLocalisation(); services.ConfigureApplicationServices(); @@ -54,6 +54,5 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions app.UseStatusCodePagesWithReExecute("/HttpErrors/{0}"); app.UseMvc(); } - } } diff --git a/CoreWiki/appsettings.json b/CoreWiki/appsettings.json index 38ccfedf..2e513df1 100644 --- a/CoreWiki/appsettings.json +++ b/CoreWiki/appsettings.json @@ -6,13 +6,20 @@ "Default": "Warning" } }, - "DataProvider": "", + "DataProvider": "", "ConnectionStrings": { //"CoreWikiIdentityContextConnection": "host=localhost;port=5432;user id=postgres;password=password", //"CoreWikiData": "host=localhost;port=5432;user id=postgres;password=password" "CoreWikiIdentityContextConnection": "DataSource=./App_Data/wikiIdentity.db", "CoreWikiData": "DataSource=./App_Data/wikiContent.db" }, + "SearchProvider": "Azd", + "SearchProviderSettings": { + "Az_ApiGateway": "mycorewikisearchtest", // free plan, 50Mb of storage + "Az_ReadApiKey": "D91D336DFABB0A9C4393B2371D0E288C", + "Az_WriteApiKey": "NotCommited" + }, + "Authentication": { "Microsoft": { "ApplicationId": "", @@ -32,12 +39,12 @@ "FromName": "No Reply Team" }, "CspSettings": { - "ImageSources": [ ], - "StyleSources": [ ], - "ScriptSources": [ ], - "FontSources": [ ], - "FormActions": [ ], - "FrameAncestors": [ ], - "ReportUris": [ ] + "ImageSources": [], + "StyleSources": [], + "ScriptSources": [], + "FontSources": [], + "FormActions": [], + "FrameAncestors": [], + "ReportUris": [] } }