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": []
}
}