├── src ├── Sample.Elasticsearch.Infrastructure │ ├── Indices │ │ ├── ElasticBaseIndex.cs │ │ └── IndexActors.cs │ ├── Interfaces │ │ └── IResult.cs │ ├── Abstractions │ │ ├── ActorsAggregationModel.cs │ │ ├── Result.cs │ │ └── NestExtensions.cs │ ├── Sample.Elasticsearch.Infrastructure.csproj │ └── Elastic │ │ ├── IIElasticBaseRepository.cs │ │ └── ElasticBaseRepository.cs ├── Sample.Elasticsearch.Domain │ ├── Interfaces │ │ ├── IActorsRepository.cs │ │ └── IActorsApplication.cs │ ├── Sample.Elasticsearch.Domain.csproj │ ├── Repositories │ │ └── ActorsRepository.cs │ └── Applications │ │ └── ActorsApplication.cs ├── Sample.Elasticsearch.WebApi │ ├── appsettings.json │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Dockerfile │ ├── Sample.Elasticsearch.WebApi.csproj │ └── Controllers │ │ └── ActorsController.cs └── Sample.Elasticsearch.WebApi.Core │ ├── Extensions │ ├── SwaggerExtensions.cs │ ├── ElasticsearchExtensions.cs │ ├── ApiConfigurationExtensions.cs │ └── SerilogExtensions.cs │ ├── Middleware │ ├── RequestSerilLogMiddleware.cs │ └── ErrorHandlingMiddleware.cs │ └── Sample.Elasticsearch.WebApi.Core.csproj ├── README.md ├── docker ├── .dockerignore ├── docker-compose.dcproj └── docker-compose.yml ├── sample-elasticsearch.sln └── .gitignore /src/Sample.Elasticsearch.Infrastructure/Indices/ElasticBaseIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sample.Elasticsearch.Infrastructure.Indices; 4 | 5 | public abstract class ElasticBaseIndex 6 | { 7 | public string Id { get; set; } 8 | public DateTime? UpdateTime { get; set; } 9 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Domain/Interfaces/IActorsRepository.cs: -------------------------------------------------------------------------------- 1 | using Sample.Elasticsearch.Infrastructure.Elastic; 2 | using Sample.Elasticsearch.Infrastructure.Indices; 3 | 4 | namespace Sample.Elasticsearch.Domain.Interfaces; 5 | 6 | public interface IActorsRepository : IElasticBaseRepository 7 | { 8 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Interfaces/IResult.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Sample.Elasticsearch.Infrastructure.Interfaces; 3 | 4 | public interface IResult where T : class 5 | { 6 | public T Data { get; set; } 7 | public string Message { get; set; } 8 | public bool Success { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Abstractions/ActorsAggregationModel.cs: -------------------------------------------------------------------------------- 1 | namespace Sample.Elasticsearch.Infrastructure.Abstractions; 2 | 3 | public class ActorsAggregationModel 4 | { 5 | public double TotalAge { get; set; } 6 | public double TotalMovies { get; set; } 7 | public double AverageAge { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elasticsearch with NEST in .NET 9.0 2 | 3 | - Docker 4 | - .NET 9.0 5 | - Swashbuckle Swagger 6 | - NEST 7 | - Serilog 8 | - Elasticsearch Repository 9 | - Middlewares 10 | 11 | https://henriquemauri.net/escrevendo-queries-no-elasticsearch-utilizando-net-e-nest/ 12 | 13 | https://henriquemauri.net/centralizando-logs-com-elastic-stack-e-net-parte-1-docker/ 14 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Domain/Sample.Elasticsearch.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Sample.Elasticsearch.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "ElasticsearchSettings": { 10 | "uri": "http://localhost:9200", 11 | "defaultIndex": "indexactors", 12 | "username": "elastic", 13 | "password": "MagicWord" 14 | }, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Indices/IndexActors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sample.Elasticsearch.Infrastructure.Indices; 4 | 5 | public class IndexActors : ElasticBaseIndex 6 | { 7 | public DateTime RegistrationDate { get; set; } 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public DateTime BirthDate { get; set; } 11 | public int Age { get; set; } 12 | public int TotalMovies { get; set; } 13 | public string Movies { get; set; } 14 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Domain/Repositories/ActorsRepository.cs: -------------------------------------------------------------------------------- 1 | using Nest; 2 | using Sample.Elasticsearch.Domain.Interfaces; 3 | using Sample.Elasticsearch.Infrastructure.Elastic; 4 | using Sample.Elasticsearch.Infrastructure.Indices; 5 | 6 | namespace Sample.Elasticsearch.Domain.Repositories; 7 | 8 | public class ActorsRepository : ElasticBaseRepository, IActorsRepository 9 | { 10 | public ActorsRepository(IElasticClient elasticClient) 11 | : base(elasticClient) 12 | { 13 | } 14 | 15 | public override string IndexName { get; } = "indexactors"; 16 | } -------------------------------------------------------------------------------- /docker/docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | 18f6d3a5-091f-4a10-b5bc-57e2239c80d6 7 | None 8 | elasticsearch-with-nest 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | elasticsearch: 5 | container_name: elasticsearch 6 | image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2 7 | ports: 8 | - 9200:9200 9 | volumes: 10 | - elasticsearch-data:/usr/share/elasticsearch/data 11 | environment: 12 | - xpack.monitoring.enabled=true 13 | - xpack.watcher.enabled=false 14 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 15 | - discovery.type=single-node 16 | - ELASTICSEARCH_USERNAME=elastic 17 | - ELASTICSEARCH_PASSWORD=MagicWord 18 | networks: 19 | - elastic 20 | 21 | kibana: 22 | container_name: kibana 23 | image: docker.elastic.co/kibana/kibana:8.10.2 24 | ports: 25 | - 5601:5601 26 | depends_on: 27 | - elasticsearch 28 | environment: 29 | - ELASTICSEARCH_URL=http://localhost:9200 30 | - ELASTICSEARCH_USERNAME=elastic 31 | - ELASTICSEARCH_PASSWORD=MagicWord 32 | networks: 33 | - elastic 34 | 35 | networks: 36 | elastic: 37 | driver: bridge 38 | 39 | volumes: 40 | elasticsearch-data: 41 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Abstractions/Result.cs: -------------------------------------------------------------------------------- 1 | using Sample.Elasticsearch.Infrastructure.Interfaces; 2 | 3 | namespace Sample.Elasticsearch.Infrastructure.Abstractions; 4 | 5 | public class Result : IResult where T : class, new() 6 | { 7 | public T Data { get; set; } 8 | public string Message { get; set; } 9 | public bool Success { get; set; } 10 | 11 | public Result(bool success, string message, T data) 12 | { 13 | Data = data; 14 | Message = message; 15 | Success = success; 16 | } 17 | 18 | public Result(bool success, string message) 19 | { 20 | Message = message; 21 | Success = success; 22 | } 23 | 24 | public Result(bool success, T data) 25 | { 26 | Data = data; 27 | Success = success; 28 | } 29 | public Result(bool success) 30 | { 31 | Success = success; 32 | } 33 | 34 | public static Result FailureResult(string message) => new(false, message); 35 | public static Result SuccessResult(T data) => new(true, data); 36 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Extensions/SwaggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.OpenApi.Models; 6 | 7 | namespace Sample.Elasticsearch.WebApi.Core.Extensions; 8 | 9 | public static class SwaggerExtensions 10 | { 11 | public static void AddSwagger(this IServiceCollection services, IConfiguration configuration) 12 | { 13 | services.AddSwaggerGen(c => 14 | { 15 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "Sample Elasticsearch", Version = "v1" }); 16 | c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); 17 | }); 18 | } 19 | 20 | public static void UseSwaggerDoc(this IApplicationBuilder app) 21 | { 22 | app.UseSwagger(); 23 | app.UseSwaggerUI(c => 24 | { 25 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Elasticsearch"); 26 | c.RoutePrefix = "swagger"; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Sample.Elasticsearch.WebApi.Core.Extensions; 5 | using Serilog; 6 | 7 | try 8 | { 9 | var builder = WebApplication.CreateBuilder(args); 10 | builder.AddSerilog(builder.Configuration, "API Elasticsearch"); 11 | Log.Information("Starting API"); 12 | 13 | builder.Services.AddApiConfiguration(); 14 | 15 | builder.Services.AddElasticsearch(builder.Configuration); 16 | builder.Services.AddSwagger(builder.Configuration); 17 | 18 | builder.Services.AddEndpointsApiExplorer(); 19 | builder.Services.AddSwaggerGen(); 20 | 21 | var app = builder.Build(); 22 | 23 | app.UseApiConfiguration(app.Environment); 24 | 25 | app.UseSwaggerDoc(); 26 | 27 | app.MapControllers(); 28 | 29 | app.Run(); 30 | } 31 | catch (Exception ex) 32 | { 33 | Log.Fatal(ex, "Host terminated unexpectedly"); 34 | } 35 | finally 36 | { 37 | Log.Information("Server Shutting down..."); 38 | Log.CloseAndFlush(); 39 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Middleware/RequestSerilLogMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Primitives; 3 | using Serilog.Context; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Sample.Elasticsearch.WebApi.Core.Middleware; 8 | 9 | public class RequestSerilLogMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | 13 | public RequestSerilLogMiddleware(RequestDelegate next) 14 | { 15 | _next = next; 16 | } 17 | 18 | public Task Invoke(HttpContext context) 19 | { 20 | using (LogContext.PushProperty("UserName", context?.User?.Identity?.Name ?? "anônimo")) 21 | using (LogContext.PushProperty("CorrelationId", GetCorrelationId(context))) 22 | { 23 | return _next.Invoke(context); 24 | } 25 | } 26 | 27 | private static string GetCorrelationId(HttpContext httpContext) 28 | { 29 | httpContext.Request.Headers.TryGetValue("Cko-Correlation-Id", out StringValues correlationId); 30 | return correlationId.FirstOrDefault() ?? httpContext.TraceIdentifier; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:65025", 7 | "sslPort": 44378 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Sample.Elasticsearch.WebApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "https://localhost:5001" 28 | }, 29 | "Docker": { 30 | "commandName": "Docker", 31 | "launchBrowser": true, 32 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 33 | "publishAllPorts": true, 34 | "useSSL": true 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | EXPOSE 443 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 7 | WORKDIR /src 8 | COPY ["src/Sample.Elasticsearch.WebApi/Sample.Elasticsearch.WebApi.csproj", "src/Sample.Elasticsearch.WebApi/"] 9 | COPY ["src/Sample.Elasticsearch.WebApi.Core/Sample.Elasticsearch.WebApi.Core.csproj", "src/Sample.Elasticsearch.WebApi.Core/"] 10 | COPY ["src/Sample.Elasticsearch.Domain/Sample.Elasticsearch.Domain.csproj", "src/Sample.Elasticsearch.Domain/"] 11 | RUN dotnet restore "src/Sample.Elasticsearch.WebApi/Sample.Elasticsearch.WebApi.csproj" 12 | COPY . . 13 | WORKDIR "/src/src/Sample.Elasticsearch.WebApi" 14 | RUN dotnet build "Sample.Elasticsearch.WebApi.csproj" -c Release -o /app/build 15 | 16 | FROM build AS publish 17 | RUN dotnet publish "Sample.Elasticsearch.WebApi.csproj" -c Release -o /app/publish 18 | 19 | ENV TZ=America/Sao_Paulo 20 | ENV LANG pt-BR 21 | ENV LANGUAGE pt-BR 22 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 23 | 24 | FROM base AS final 25 | WORKDIR /app 26 | COPY --from=publish /app/publish . 27 | ENTRYPOINT ["dotnet", "Sample.Elasticsearch.WebApi.dll"] -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Extensions/ElasticsearchExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Nest; 5 | 6 | namespace Sample.Elasticsearch.WebApi.Core.Extensions; 7 | 8 | public static class ElasticsearchExtensions 9 | { 10 | public static void AddElasticsearch(this IServiceCollection services, IConfiguration configuration) 11 | { 12 | var defaultIndex = configuration["ElasticsearchSettings:defaultIndex"]; 13 | var basicAuthUser = configuration["ElasticsearchSettings:username"]; 14 | var basicAuthPassword = configuration["ElasticsearchSettings:password"]; 15 | 16 | var settings = new ConnectionSettings(new Uri(configuration["ElasticsearchSettings:uri"])); 17 | 18 | if (!string.IsNullOrEmpty(defaultIndex)) 19 | settings = settings.DefaultIndex(defaultIndex); 20 | 21 | if (!string.IsNullOrEmpty(basicAuthUser) && !string.IsNullOrEmpty(basicAuthPassword)) 22 | settings = settings.BasicAuthentication(basicAuthUser, basicAuthPassword); 23 | 24 | settings.EnableApiVersioningHeader(); 25 | 26 | var client = new ElasticClient(settings); 27 | 28 | services.AddSingleton(client); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/Sample.Elasticsearch.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | Sample.Elasticsearch.WebApi 6 | ff15173f-95e1-4a96-978d-e65678b218b9 7 | Linux 8 | ..\docker-compose.dcproj 9 | ..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Domain/Interfaces/IActorsApplication.cs: -------------------------------------------------------------------------------- 1 | using Sample.Elasticsearch.Infrastructure.Abstractions; 2 | using Sample.Elasticsearch.Infrastructure.Indices; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace Sample.Elasticsearch.Domain.Interfaces; 8 | 9 | public interface IActorsApplication 10 | { 11 | Task InsertManyAsync(); 12 | Task> GetAllAsync(); 13 | Task> GetByNameWithTerm(string name); 14 | Task> GetByNameWithMatch(string name); 15 | Task> GetByNameAndDescriptionMultiMatch(string term); 16 | Task> GetByNameWithMatchPhrase(string name); 17 | Task> GetByNameWithMatchPhrasePrefix(string name); 18 | Task> GetByNameWithWildcard(string name); 19 | Task> GetByNameWithFuzzy(string name); 20 | Task> SearchInAllFiels(string term); 21 | Task> GetByDescriptionMatch(string description); 22 | Task> GetActorsCondition(string name, string description, DateTime? birthdate); 23 | Task> GetActorsAllCondition(string term); 24 | Task GetActorsAggregation(); 25 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Middleware/ErrorHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Serilog; 3 | using System; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | 7 | namespace Sample.Elasticsearch.WebApi.Core.Middleware; 8 | 9 | public class ErrorHandlingMiddleware 10 | { 11 | private readonly RequestDelegate next; 12 | 13 | public ErrorHandlingMiddleware(RequestDelegate next) 14 | { 15 | this.next = next; 16 | } 17 | 18 | public async Task Invoke(HttpContext context) 19 | { 20 | try 21 | { 22 | await next(context); 23 | } 24 | catch (Exception ex) 25 | { 26 | await HandleExceptionAsync(context, ex); 27 | } 28 | } 29 | 30 | private static async Task HandleExceptionAsync(HttpContext context, Exception exception) 31 | { 32 | Log.Error(exception, "Erro não tratado"); 33 | 34 | context.Response.ContentType = "application/json"; 35 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 36 | 37 | var result = JsonSerializer.Serialize(new { error = exception?.Message }, new JsonSerializerOptions() 38 | { 39 | WriteIndented = true, 40 | DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, 41 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 42 | }); 43 | 44 | await context.Response.WriteAsync(result); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Elastic/IIElasticBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Nest; 5 | 6 | namespace Sample.Elasticsearch.Infrastructure.Elastic; 7 | 8 | public interface IElasticBaseRepository where T : class 9 | { 10 | Task GetAsync(string id); 11 | Task GetAsync(IGetRequest request); 12 | Task FindAsync(string id); 13 | Task FindAsync(IGetRequest request); 14 | Task> GetAllAsync(); 15 | Task> GetManyAsync(IEnumerable ids); 16 | Task> SearchAsync(Func, ISearchRequest> selector); 17 | Task> SearchAsync(Func, QueryContainer> request); 18 | Task> SearchAsync(Func, QueryContainer> request, Func, IAggregationContainer> aggregationsSelector); 19 | Task> SearchInAllFields(string term); 20 | Task CreateIndexAsync(); 21 | Task InsertAsync(T t); 22 | Task InsertManyAsync(IList tList); 23 | Task UpdateAsync(T t); 24 | Task UpdatePartAsync(T t, object partialEntity); 25 | Task GetTotalCountAsync(); 26 | Task DeleteByIdAsync(string id); 27 | Task DeleteByQueryAsync(Func, QueryContainer> selector); 28 | Task ExistAsync(string id); 29 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Sample.Elasticsearch.WebApi.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Extensions/ApiConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using Sample.Elasticsearch.Domain.Applications; 6 | using Sample.Elasticsearch.Domain.Interfaces; 7 | using Sample.Elasticsearch.Domain.Repositories; 8 | using Sample.Elasticsearch.WebApi.Core.Middleware; 9 | using Serilog; 10 | 11 | namespace Sample.Elasticsearch.WebApi.Core.Extensions; 12 | 13 | public static class ApiConfigurationExtensions 14 | { 15 | public static void AddApiConfiguration(this IServiceCollection services) 16 | { 17 | services.AddRouting(options => options.LowercaseUrls = true); 18 | 19 | services.AddTransient(); 20 | services.AddTransient(); 21 | 22 | services.AddControllers(); 23 | } 24 | 25 | public static void UseApiConfiguration(this IApplicationBuilder app, IWebHostEnvironment env) 26 | { 27 | if (env.IsDevelopment()) 28 | { 29 | app.UseDeveloperExceptionPage(); 30 | } 31 | 32 | app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = SerilogExtensions.EnrichFromRequest); 33 | 34 | app.UseMiddleware(); 35 | app.UseMiddleware(); 36 | 37 | app.UseHttpsRedirection(); 38 | 39 | app.UseRouting(); 40 | 41 | app.UseAuthorization(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi.Core/Extensions/SerilogExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Elastic.Apm.SerilogEnricher; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Http.Features; 7 | using Microsoft.AspNetCore.Routing; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | using Serilog; 11 | using Serilog.Filters; 12 | using Serilog.Sinks.Elasticsearch; 13 | 14 | namespace Sample.Elasticsearch.WebApi.Core.Extensions; 15 | 16 | public static class SerilogExtensions 17 | { 18 | public static WebApplicationBuilder AddSerilog(this WebApplicationBuilder builder, IConfiguration configuration, string applicationName) 19 | { 20 | Log.Logger = new LoggerConfiguration() 21 | .ReadFrom.Configuration(configuration) 22 | .Enrich.WithProperty("ApplicationName", $"{applicationName} - {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}") 23 | .Enrich.FromLogContext() 24 | .Enrich.WithMachineName() 25 | .Enrich.WithEnvironmentUserName() 26 | .Enrich.WithElasticApmCorrelationInfo() 27 | .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.StaticFiles")) 28 | .Filter.ByExcluding(z => z.MessageTemplate.Text.Contains("specific error")) 29 | .WriteTo.Async(writeTo => writeTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(configuration["ElasticsearchSettings:uri"])) 30 | { 31 | TypeName = null, 32 | AutoRegisterTemplate = true, 33 | IndexFormat = "indexlogs", 34 | BatchAction = ElasticOpType.Create, 35 | ModifyConnectionSettings = x => x.BasicAuthentication(configuration["ElasticsearchSettings:username"], configuration["ElasticsearchSettings:password"]) 36 | })) 37 | .WriteTo.Async(writeTo => writeTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")) 38 | .WriteTo.Debug() 39 | .CreateLogger(); 40 | 41 | builder.Logging.ClearProviders(); 42 | builder.Host.UseSerilog(Log.Logger, true); 43 | 44 | return builder; 45 | } 46 | 47 | public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) 48 | { 49 | diagnosticContext.Set("UserName", httpContext?.User?.Identity?.Name); 50 | diagnosticContext.Set("ClientIP", httpContext?.Connection?.RemoteIpAddress?.ToString()); 51 | diagnosticContext.Set("UserAgent", httpContext?.Request?.Headers["User-Agent"].FirstOrDefault()); 52 | diagnosticContext.Set("Resource", httpContext.GetMetricsCurrentResourceName()); 53 | } 54 | 55 | public static string GetMetricsCurrentResourceName(this HttpContext httpContext) 56 | { 57 | if (httpContext == null) 58 | throw new ArgumentNullException(nameof(httpContext)); 59 | 60 | var endpoint = httpContext.Features.Get()?.Endpoint; 61 | 62 | return endpoint?.Metadata.GetMetadata()?.EndpointName; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /sample-elasticsearch.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31717.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Elasticsearch.WebApi", "src\Sample.Elasticsearch.WebApi\Sample.Elasticsearch.WebApi.csproj", "{2CAF22E2-6510-4B11-8F5F-89E0A3C05010}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Elasticsearch.WebApi.Core", "src\Sample.Elasticsearch.WebApi.Core\Sample.Elasticsearch.WebApi.Core.csproj", "{EAE3FF16-13E9-42CA-93B7-43660C14A7A1}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Elasticsearch.Domain", "src\Sample.Elasticsearch.Domain\Sample.Elasticsearch.Domain.csproj", "{8038EF31-9DC6-4AF4-AC1A-A36C4C918B53}" 11 | EndProject 12 | Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker\docker-compose.dcproj", "{18F6D3A5-091F-4A10-B5BC-57E2239C80D6}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Elasticsearch.Infrastructure", "src\Sample.Elasticsearch.Infrastructure\Sample.Elasticsearch.Infrastructure.csproj", "{C518D16A-D9EE-43C5-A7CF-986F65F8FB20}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {2CAF22E2-6510-4B11-8F5F-89E0A3C05010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {2CAF22E2-6510-4B11-8F5F-89E0A3C05010}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {2CAF22E2-6510-4B11-8F5F-89E0A3C05010}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {2CAF22E2-6510-4B11-8F5F-89E0A3C05010}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {EAE3FF16-13E9-42CA-93B7-43660C14A7A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {EAE3FF16-13E9-42CA-93B7-43660C14A7A1}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {EAE3FF16-13E9-42CA-93B7-43660C14A7A1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {EAE3FF16-13E9-42CA-93B7-43660C14A7A1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {8038EF31-9DC6-4AF4-AC1A-A36C4C918B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8038EF31-9DC6-4AF4-AC1A-A36C4C918B53}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8038EF31-9DC6-4AF4-AC1A-A36C4C918B53}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {8038EF31-9DC6-4AF4-AC1A-A36C4C918B53}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {18F6D3A5-091F-4A10-B5BC-57E2239C80D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {18F6D3A5-091F-4A10-B5BC-57E2239C80D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {18F6D3A5-091F-4A10-B5BC-57E2239C80D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {18F6D3A5-091F-4A10-B5BC-57E2239C80D6}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {C518D16A-D9EE-43C5-A7CF-986F65F8FB20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C518D16A-D9EE-43C5-A7CF-986F65F8FB20}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {C518D16A-D9EE-43C5-A7CF-986F65F8FB20}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {C518D16A-D9EE-43C5-A7CF-986F65F8FB20}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {EED24293-FCEB-4257-A890-7B61D64A99FE} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.WebApi/Controllers/ActorsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Threading.Tasks; 4 | using Sample.Elasticsearch.Domain.Interfaces; 5 | 6 | namespace Sample.Elasticsearch.WebApi.Controllers; 7 | 8 | [Route("api/[controller]")] 9 | public class ActorsController : Controller 10 | { 11 | private readonly IActorsApplication _actorsApplication; 12 | 13 | public ActorsController(IActorsApplication actorsApplication) 14 | { 15 | _actorsApplication = actorsApplication; 16 | } 17 | 18 | [HttpPost("sample")] 19 | public async Task PostSampleData() 20 | { 21 | await _actorsApplication.InsertManyAsync(); 22 | 23 | return Ok(new { Result = "Data successfully registered with Elasticsearch" }); 24 | } 25 | 26 | [HttpPost("exception")] 27 | public IActionResult PostException() 28 | { 29 | throw new Exception("Generate sample exception"); 30 | } 31 | 32 | [HttpGet("")] 33 | public async Task GetAll() 34 | { 35 | var result = await _actorsApplication.GetAllAsync(); 36 | 37 | return Json(result); 38 | } 39 | 40 | [HttpGet("name-match")] 41 | public async Task GetByNameWithMatch([FromQuery] string name) 42 | { 43 | var result = await _actorsApplication.GetByNameWithMatch(name); 44 | 45 | return Json(result); 46 | } 47 | 48 | [HttpGet("name-multimatch")] 49 | public async Task GetByNameAndDescriptionMultiMatch([FromQuery] string term) 50 | { 51 | var result = await _actorsApplication.GetByNameAndDescriptionMultiMatch(term); 52 | 53 | return Json(result); 54 | } 55 | 56 | [HttpGet("name-matchphrase")] 57 | public async Task GetByNameWithMatchPhrase([FromQuery] string name) 58 | { 59 | var result = await _actorsApplication.GetByNameWithMatchPhrase(name); 60 | 61 | return Json(result); 62 | } 63 | 64 | [HttpGet("name-matchphraseprefix")] 65 | public async Task GetByNameWithMatchPhrasePrefix([FromQuery] string name) 66 | { 67 | var result = await _actorsApplication.GetByNameWithMatchPhrasePrefix(name); 68 | 69 | return Json(result); 70 | } 71 | 72 | [HttpGet("name-term")] 73 | public async Task GetByNameWithTerm([FromQuery] string name) 74 | { 75 | var result = await _actorsApplication.GetByNameWithTerm(name); 76 | 77 | return Json(result); 78 | } 79 | 80 | [HttpGet("name-wildcard")] 81 | public async Task GetByNameWithWildcard([FromQuery] string name) 82 | { 83 | var result = await _actorsApplication.GetByNameWithWildcard(name); 84 | 85 | return Json(result); 86 | } 87 | 88 | [HttpGet("name-fuzzy")] 89 | public async Task GetByNameWithFuzzy([FromQuery] string name) 90 | { 91 | var result = await _actorsApplication.GetByNameWithFuzzy(name); 92 | 93 | return Json(result); 94 | } 95 | 96 | [HttpGet("description-match")] 97 | public async Task GetByDescriptionMatch([FromQuery] string description) 98 | { 99 | var result = await _actorsApplication.GetByDescriptionMatch(description); 100 | 101 | return Json(result); 102 | } 103 | 104 | [HttpGet("all-fields")] 105 | public async Task SearchAllProperties([FromQuery] string term) 106 | { 107 | var result = await _actorsApplication.SearchInAllFiels(term); 108 | 109 | return Json(result); 110 | } 111 | 112 | [HttpGet("condiction")] 113 | public async Task GetByCondictions([FromQuery] string name, [FromQuery] string description, [FromQuery] DateTime? birthdate) 114 | { 115 | var result = await _actorsApplication.GetActorsCondition(name, description, birthdate); 116 | 117 | return Json(result); 118 | } 119 | 120 | [HttpGet("term")] 121 | public async Task GetByAllCondictions([FromQuery] string term) 122 | { 123 | var result = await _actorsApplication.GetActorsAllCondition(term); 124 | 125 | return Json(result); 126 | } 127 | 128 | [HttpGet("aggregation")] 129 | public async Task GetActorsAggregation() 130 | { 131 | var result = await _actorsApplication.GetActorsAggregation(); 132 | 133 | return Json(result); 134 | } 135 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Abstractions/NestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Nest; 5 | using Sample.Elasticsearch.Infrastructure.Indices; 6 | 7 | namespace Sample.Elasticsearch.Infrastructure.Abstractions; 8 | 9 | public static class NestExtensions 10 | { 11 | public static QueryContainer BuildMultiMatchQuery(string queryValue) where T : class 12 | { 13 | var fields = typeof(T).GetProperties().Select(p => p.Name.ToLower()).ToArray(); 14 | 15 | return new QueryContainerDescriptor() 16 | .MultiMatch(c => c 17 | .Type(TextQueryType.Phrase) 18 | .Fields(f => f.Fields(fields)).Lenient().Query(queryValue)); 19 | } 20 | 21 | public static List GetSampleData() 22 | { 23 | var list = new List 24 | { 25 | new() {Id = "38d61273-8f4e-431c-a0bb-3de2fbbf8757", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1969, 9, 25), Age = 50, TotalMovies = 25, Name = "Catherine Zeta-Jones", Description = "Sua carreira artística começou ainda cedo. Antes dos dez anos de idade, Catherine já cantava e dançava na companhia teatral de uma congregação católica. Aos 15 anos, deixou Swansea e foi para Londres, tentar fazer sucesso nos palcos. Estreou no cinema em 1990, no filme Les mille et une nuits do diretor francês Philippe de Broca. Em 1991, participou da série britânica The Darling Buds of May.[2][3] Em 1992 e 1993 participou do seriado The Young Indiana Jones Chronicles. Em 1995, estrelou no papel-título da minissérie Catherine the Great, sobre a imperatriz russa Catarina II."}, 26 | new() {Id = "6d288857-34e4-417f-8a22-e0c7575972b5", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1948, 12, 27), Age = 71, TotalMovies = 44, Name = "Gérard Depardieu", Description = "Com origens humildes, filho de um operário metalúrgico.[1] Antes de ser considerado o presente francês para o cinema, ou ainda, o Robert De Niro francês, o terceiro dos seis filhos de um pobre operário abandonou a escola aos 12 anos, fugiu de casa e viveu com prostitutas. A vida delinquente e vândala de Depardieu se estendeu até os seus 16 anos de idade, quando foi incentivado por uma assistente social a sair das ruas e investir numa carreira artística. Desde então, começou a marcar presença nos palcos de teatros parisienses, e daí a começar a participar de filmes foi mera questão de tempo. Sua carreira de ator, que já conta com um total de 140 filmes. Ao optar pelos palcos, Depardieu transformou-se no ator carismático que, hoje, é famoso em todo o mundo."}, 27 | new() {Id = "9dc521a4-3486-4c6c-b769-8bdafcc4563d", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1976, 1, 13), Age = 44, TotalMovies = 34, Name = "Michael Peña", Description = "Peña nasceu em Chicago, Illinois, onde seu pai trabalhava em uma fábrica de botões e sua mãe era uma assistente social.[1][2] Os pais de Peña, imigrantes provenientes do México, foram originalmente agricultores.[3][4] Peña frequentou a Hubbard High School em Chicago.[5] Peña e sua esposa, Brie Shaffer, tiveram seu primeiro filho em setembro de 2008, um menino chamado Roman.[6]"}, 28 | new() {Id = "b9044bf7-6917-4521-9b1b-f737a05965f9", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1944, 9, 25),Age = 75, TotalMovies = 52, Name = "Michael Douglas", Description = "Michael Douglas graduou-se em 1968 na Choate School University de Santa Barbara, em American Place Theatre. Ficou conhecido do público ao estrelar para a televisão o seriado The Streets of San Francisco (ao lado do veterano ator Karl Malden). Mas ganharia renome nos meios cinematográficos quando produziu One Flew Over the Cuckoo's Nest."}, 29 | new() {Id = "abc6caf0-6c60-4e9e-93d2-2f53fbe941e2", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1956, 7, 9),Age = 63, TotalMovies = 41, Name = "Tom Hanks", Description = "Thomas Jeffrey Hanks, mais conhecido como Tom Hanks[1] (Concord, 9 de julho de 1956),[2] é um premiado ator, produtor, dublador, roteirista e diretor norte-americano. Destacou-se em diversos filmes de sucesso, como: Forrest Gump, Apollo 13, That Thing You Do!, The Green Mile, The Terminal, Inferno, Saving Private Ryan, You've Got Mail, Sleepless In Seattle. Charlie Wilson's War, Catch Me If You Can, Cast Away, A League Of Their Own, The Da Vinci Code, Captain Phillips, Angels & Demons, Splash, Big, Road To Perdition, Philadelphia e como a voz do personagem Woody na série de filmes de animação Toy Story e também pelas vozes em The Polar Express." }, 30 | new() {Id = "7b0636fd-42ca-4435-a9ff-bebef6a40fda", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1933, 3, 14),Age = 87, TotalMovies = 69, Name = "Michael Caine", Description = "Michael Caine serviu o Exército Britânico na Guerra da Coreia, nos anos cinquenta. Estreou no cinema com o filme A Hill in Korea, de 1956, no qual interpreta um cabo do exército britânico. Seu primeiro papel de destaque no cinema foi no filme Zulu, de 1964, dirigido por Cy Endfield. Caine ficou popular em uma série de filmes realizados nos anos sessenta, no auge da Guerra Fria, nos quais ele interpretou um espião inteligente e racional chamado Harry Palmer." }, 31 | new() {Id = "b7666135-d20b-4a52-98f2-87023d4c2bdb", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1974, 1, 30),Age = 46, TotalMovies = 42, Name = "Christian Bale", Description = "Christian Bale, para cada dez atores mirins que, quando adultos, não conseguem continuar sua carreira, existe um Christian Bale. O menino escolhido pessoalmente por Steven Spielberg para estrelar Império do Sol cresceu e se tornou um dos talentos mais respeitados e um dos homens mais desejados de Hollywood.[carece de fontes]" }, 32 | new() {Id = "6b0b2f52-ee2d-4b90-9dcc-c303d1b9830c", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1943, 8, 17), Age = 76,TotalMovies = 56, Name = "Robert De Niro", Description = "O primeiro filme em que Robert De Niro participou foi a produção francesa de 1965 Trois Chambres à Manhattan, de Marcel Carné. Niro fazia um papel secundário, como cliente de um restaurante.[4] Depois participou em muitos outros filmes, com papéis maiores e menores, mas nenhum deles com grande sucesso, até que atingiu a popularidade com o seu papel em Bang the Drum Slowly (br: A Última Batalha de um Jogador), em 1973. "}, 33 | new() {Id = "2eda34e3-3cdb-47b7-ac56-c5946d7d695a", RegistrationDate = DateTime.Now, BirthDate = new DateTime(1940, 4, 25),Age = 80, TotalMovies = 78, Name = "Al Pacino", Description = "Al Pacino nasceu em Nova York (East Harlem), filho dos Ítalo-americanos Salvatore Pacino e Rose, que se divorciaram quando tinha dois anos.[1] Sua mãe mudou-se para próximo ao Zoológico do Bronx para morar com seus pais, Kate e James Gerardi, que, por coincidência, tinham vindo de uma cidade na Sicília chamada Corleone.[2] Seu pai que era de San Fratello na Província de Messina, mudou-se para Covina, Califórnia, onde trabalhou como vendedor de seguros e gerente/proprietário de restaurante.[1]"} 34 | }; 35 | return list; 36 | } 37 | 38 | public static double ObterBucketAggregationDouble(AggregateDictionary agg, string bucket) 39 | { 40 | return agg.BucketScript(bucket).Value.HasValue ? agg.BucketScript(bucket).Value.Value : 0; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Domain/Applications/ActorsApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Nest; 6 | using Sample.Elasticsearch.Domain.Interfaces; 7 | using Sample.Elasticsearch.Infrastructure.Abstractions; 8 | using Sample.Elasticsearch.Infrastructure.Indices; 9 | 10 | namespace Sample.Elasticsearch.Domain.Applications; 11 | 12 | public class ActorsApplication : IActorsApplication 13 | { 14 | private readonly IActorsRepository _actorsRepository; 15 | 16 | public ActorsApplication(IActorsRepository actorsRepository) 17 | { 18 | _actorsRepository = actorsRepository; 19 | } 20 | 21 | public async Task InsertManyAsync() 22 | { 23 | await _actorsRepository.InsertManyAsync(NestExtensions.GetSampleData()); 24 | } 25 | 26 | public async Task> GetAllAsync() 27 | { 28 | var result = await _actorsRepository.GetAllAsync(); 29 | 30 | return result.ToList(); 31 | } 32 | 33 | //lowcase 34 | public async Task> GetByNameWithTerm(string name) 35 | { 36 | var query = new QueryContainerDescriptor().Term(p => p.Field(p => p.Name).Value(name).CaseInsensitive().Boost(6.0)); 37 | var result = await _actorsRepository.SearchAsync(_ => query); 38 | 39 | return result?.ToList(); 40 | } 41 | 42 | //using operator OR in case insensitive 43 | public async Task> GetByNameWithMatch(string name) 44 | { 45 | var query = new QueryContainerDescriptor().Match(p => p.Field(f => f.Name).Query(name).Operator(Operator.And)); 46 | var result = await _actorsRepository.SearchAsync(_ => query); 47 | 48 | return result?.ToList(); 49 | } 50 | 51 | public async Task> GetByNameWithMatchPhrase(string name) 52 | { 53 | var query = new QueryContainerDescriptor().MatchPhrase(p => p.Field(f => f.Name).Query(name)); 54 | var result = await _actorsRepository.SearchAsync(_ => query); 55 | 56 | return result?.ToList(); 57 | } 58 | 59 | public async Task> GetByNameWithMatchPhrasePrefix(string name) 60 | { 61 | var query = new QueryContainerDescriptor().MatchPhrasePrefix(p => p.Field(f => f.Name).Query(name)); 62 | var result = await _actorsRepository.SearchAsync(_ => query); 63 | 64 | return result?.ToList(); 65 | } 66 | 67 | //contains 68 | public async Task> GetByNameWithWildcard(string name) 69 | { 70 | var query = new QueryContainerDescriptor().Wildcard(w => w.Field(f => f.Name).Value($"*{name}*").CaseInsensitive()); 71 | var result = await _actorsRepository.SearchAsync(_ => query); 72 | 73 | return result?.ToList(); 74 | } 75 | 76 | public async Task> GetByNameWithFuzzy(string name) 77 | { 78 | var query = new QueryContainerDescriptor().Fuzzy(descriptor => descriptor.Field(p => p.Name).Value(name)); 79 | var result = await _actorsRepository.SearchAsync(_ => query); 80 | 81 | return result?.ToList(); 82 | } 83 | 84 | public async Task> SearchInAllFiels(string term) 85 | { 86 | var query = NestExtensions.BuildMultiMatchQuery(term); 87 | var result = await _actorsRepository.SearchAsync(_ => query); 88 | 89 | return result.ToList(); 90 | } 91 | 92 | public async Task> GetByDescriptionMatch(string description) 93 | { 94 | //case insensitive 95 | var query = new QueryContainerDescriptor().Match(p => p.Field(f => f.Description).Query(description)); 96 | var result = await _actorsRepository.SearchAsync(_ => query); 97 | 98 | return result?.ToList(); 99 | } 100 | 101 | public async Task> GetByNameAndDescriptionMultiMatch(string term) 102 | { 103 | var query = new QueryContainerDescriptor() 104 | .MultiMatch(p => p. 105 | Fields(p => p. 106 | Field(f => f.Name). 107 | Field(d => d.Description)). 108 | Query(term).Operator(Operator.And)); 109 | 110 | var result = await _actorsRepository.SearchAsync(_ => query); 111 | 112 | return result?.ToList(); 113 | } 114 | 115 | public async Task> GetActorsCondition(string name, string description, DateTime? birthdate) 116 | { 117 | QueryContainer query = new QueryContainerDescriptor(); 118 | 119 | if (!string.IsNullOrEmpty(name)) 120 | { 121 | query = query && new QueryContainerDescriptor().MatchPhrasePrefix(qs => qs.Field(fs => fs.Name).Query(name)); 122 | } 123 | if (!string.IsNullOrEmpty(description)) 124 | { 125 | query = query && new QueryContainerDescriptor().MatchPhrasePrefix(qs => qs.Field(fs => fs.Description).Query(description)); 126 | } 127 | if (birthdate.HasValue) 128 | { 129 | query = query && new QueryContainerDescriptor() 130 | .Bool(b => b.Filter(f => f.DateRange(dt => dt 131 | .Field(field => field.BirthDate) 132 | .GreaterThanOrEquals(birthdate) 133 | .LessThanOrEquals(birthdate) 134 | .TimeZone("+00:00")))); 135 | } 136 | 137 | var result = await _actorsRepository.SearchAsync(_ => query); 138 | 139 | return result?.ToList(); 140 | } 141 | 142 | public async Task> GetActorsAllCondition(string term) 143 | { 144 | var query = new QueryContainerDescriptor().Bool(b => b.Must(m => m.Exists(e => e.Field(f => f.Description)))); 145 | int.TryParse(term, out var numero); 146 | 147 | query = query && new QueryContainerDescriptor().Wildcard(w => w.Field(f => f.Name).Value($"*{term}*")) //bad performance, use MatchPhrasePrefix 148 | || new QueryContainerDescriptor().Wildcard(w => w.Field(f => f.Description).Value($"*{term}*")) //bad performance, use MatchPhrasePrefix 149 | || new QueryContainerDescriptor().Term(w => w.Age, numero) 150 | || new QueryContainerDescriptor().Term(w => w.TotalMovies, numero); 151 | 152 | var result = await _actorsRepository.SearchAsync(_ => query); 153 | 154 | return result?.ToList(); 155 | } 156 | 157 | public async Task GetActorsAggregation() 158 | { 159 | var query = new QueryContainerDescriptor().Bool(b => b.Must(m => m.Exists(e => e.Field(f => f.Description)))); 160 | 161 | var result = await _actorsRepository.SearchAsync(_ => query, a => 162 | a.Sum("TotalAge", sa => sa.Field(o => o.Age)) 163 | .Sum("TotalMovies", sa => sa.Field(p => p.TotalMovies)) 164 | .Average("AvAge", sa => sa.Field(p => p.Age))); 165 | 166 | var totalAge = NestExtensions.ObterBucketAggregationDouble(result.Aggregations, "TotalAge"); 167 | var totalMovies = NestExtensions.ObterBucketAggregationDouble(result.Aggregations, "TotalMovies"); 168 | var avAge = NestExtensions.ObterBucketAggregationDouble(result.Aggregations, "AvAge"); 169 | 170 | return new ActorsAggregationModel { TotalAge = totalAge, TotalMovies = totalMovies, AverageAge = avAge }; 171 | } 172 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /src/Sample.Elasticsearch.Infrastructure/Elastic/ElasticBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Nest; 6 | using Sample.Elasticsearch.Infrastructure.Abstractions; 7 | using Sample.Elasticsearch.Infrastructure.Indices; 8 | using Serilog; 9 | 10 | namespace Sample.Elasticsearch.Infrastructure.Elastic; 11 | 12 | public abstract class ElasticBaseRepository : IElasticBaseRepository where T : ElasticBaseIndex 13 | { 14 | private readonly IElasticClient _elasticClient; 15 | 16 | public abstract string IndexName { get; } 17 | 18 | protected ElasticBaseRepository(IElasticClient elasticClient) 19 | { 20 | _elasticClient = elasticClient; 21 | } 22 | 23 | public async Task GetAsync(string id) 24 | { 25 | var response = await _elasticClient.GetAsync(DocumentPath.Id(id).Index(IndexName)); 26 | 27 | if (response.IsValid) 28 | return response.Source; 29 | 30 | Log.Error(response.OriginalException, response.ServerError?.ToString()); 31 | return null; 32 | } 33 | 34 | public async Task GetAsync(IGetRequest request) 35 | { 36 | var response = await _elasticClient.GetAsync(request); 37 | 38 | if (response.IsValid) 39 | return response.Source; 40 | 41 | Log.Error(response.OriginalException, response.ServerError?.ToString()); 42 | return null; 43 | } 44 | 45 | public async Task FindAsync(string id) 46 | { 47 | var response = await _elasticClient.GetAsync(DocumentPath.Id(id).Index(IndexName)); 48 | 49 | if (!response.IsValid) 50 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 51 | 52 | return response.Source; 53 | } 54 | 55 | public async Task FindAsync(IGetRequest request) 56 | { 57 | var response = await _elasticClient.GetAsync(request); 58 | 59 | if (!response.IsValid) 60 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 61 | 62 | return response.Source; 63 | } 64 | 65 | public async Task> GetAllAsync() 66 | { 67 | var search = new SearchDescriptor(IndexName).MatchAll(); 68 | var response = await _elasticClient.SearchAsync(search); 69 | 70 | if (!response.IsValid) 71 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 72 | 73 | return response.Hits.Select(hit => hit.Source).ToList(); 74 | } 75 | 76 | public async Task> GetManyAsync(IEnumerable ids) 77 | { 78 | var response = await _elasticClient.GetManyAsync(ids, IndexName); 79 | 80 | return response.Select(item => item.Source).ToList(); 81 | } 82 | 83 | public async Task> SearchAsync(Func, QueryContainer> request) 84 | { 85 | var response = await _elasticClient.SearchAsync(s => 86 | s.Index(IndexName) 87 | .Query(request)); 88 | 89 | if (!response.IsValid) 90 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 91 | 92 | return response.Hits.Select(hit => hit.Source).ToList(); 93 | } 94 | 95 | public async Task> SearchAsync(Func, QueryContainer> request, 96 | Func, IAggregationContainer> aggregationsSelector) 97 | { 98 | var response = await _elasticClient.SearchAsync(s => 99 | s.Index(IndexName) 100 | .Query(request) 101 | .Aggregations(aggregationsSelector)); 102 | 103 | if (!response.IsValid) 104 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 105 | 106 | return response; 107 | } 108 | 109 | public async Task> SearchAsync(Func, ISearchRequest> selector) 110 | { 111 | var list = new List(); 112 | var response = await _elasticClient.SearchAsync(selector); 113 | 114 | if (!response.IsValid) 115 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 116 | 117 | return response.Hits.Select(hit => hit.Source).ToList(); 118 | } 119 | 120 | public async Task> SearchInAllFields(string term) 121 | { 122 | var result = await _elasticClient.SearchAsync(s => s.Query(p => NestExtensions.BuildMultiMatchQuery(term))); 123 | 124 | return result.Documents.ToList(); 125 | } 126 | 127 | public async Task CreateIndexAsync() 128 | { 129 | if (!(await _elasticClient.Indices.ExistsAsync(IndexName)).Exists) 130 | { 131 | await _elasticClient.Indices.CreateAsync(IndexName, c => 132 | { 133 | c.Map(p => p.AutoMap()); 134 | return c; 135 | }); 136 | } 137 | return true; 138 | } 139 | 140 | public async Task InsertAsync(T model) 141 | { 142 | var response = await _elasticClient.IndexAsync(model, descriptor => descriptor.Index(IndexName)); 143 | 144 | if (!response.IsValid) 145 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 146 | 147 | return true; 148 | } 149 | 150 | public async Task InsertManyAsync(IList tList) 151 | { 152 | await CreateIndexAsync(); 153 | var response = await _elasticClient.IndexManyAsync(tList, IndexName); 154 | 155 | if (!response.IsValid) 156 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 157 | 158 | 159 | return true; 160 | } 161 | 162 | public async Task UpdateAsync(T model) 163 | { 164 | var response = await _elasticClient.UpdateAsync(DocumentPath.Id(model.Id).Index(IndexName), p => p.Doc(model)); 165 | 166 | if (!response.IsValid) 167 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 168 | 169 | return true; 170 | } 171 | 172 | public async Task UpdatePartAsync(T model, object partialEntity) 173 | { 174 | var request = new UpdateRequest(IndexName, model.Id) 175 | { 176 | Doc = partialEntity 177 | }; 178 | var response = await _elasticClient.UpdateAsync(request); 179 | 180 | if (!response.IsValid) 181 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 182 | 183 | return true; 184 | } 185 | 186 | public async Task DeleteByIdAsync(string id) 187 | { 188 | var response = await _elasticClient.DeleteAsync(DocumentPath.Id(id).Index(IndexName)); 189 | 190 | if (!response.IsValid) 191 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 192 | 193 | return true; 194 | } 195 | 196 | public async Task DeleteByQueryAsync(Func, QueryContainer> selector) 197 | { 198 | var response = await _elasticClient.DeleteByQueryAsync(q => q 199 | .Query(selector) 200 | .Index(IndexName) 201 | ); 202 | 203 | if (!response.IsValid) 204 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 205 | 206 | return true; 207 | } 208 | 209 | public async Task GetTotalCountAsync() 210 | { 211 | var search = new SearchDescriptor(IndexName).MatchAll(); 212 | var response = await _elasticClient.SearchAsync(search); 213 | 214 | if (!response.IsValid) 215 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 216 | 217 | return response.Total; 218 | } 219 | 220 | public async Task ExistAsync(string id) 221 | { 222 | var response = await _elasticClient.DocumentExistsAsync(DocumentPath.Id(id).Index(IndexName)); 223 | 224 | if (!response.IsValid) 225 | throw new Exception(response.ServerError?.ToString(), response.OriginalException); 226 | 227 | return response.Exists; 228 | } 229 | } --------------------------------------------------------------------------------