From c3667a1dd93e8d01dfa2d7a9b5952114a317aa49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kurzyniec?= Date: Tue, 7 May 2024 17:52:29 +0200 Subject: [PATCH] added ping controller --- README.md | 2 +- .../PingWebsiteBackgroundService.cs | 106 +++++----- .../Controllers/PingsController.cs | 41 ++++ .../Startup.cs | 200 +++++++++--------- .../Infrastructure/AuthFilter.cs | 92 ++++---- 5 files changed, 247 insertions(+), 194 deletions(-) create mode 100644 src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs diff --git a/README.md b/README.md index 4a7d3f90d..cd1c08d99 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ At the end, You are in charge, so it's your decision to which path you would lik * Configurations * `Serilog` configuration place - [SerilogConfigurator.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs) * `Swagger` registration place - [SwaggerRegistration.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Registrations/SwaggerRegistration.cs) -* Simple exemplary API controllers - [EmployeesController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/EmployeesController.cs), [CarsController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/CarsController.cs) +* Simple exemplary API controllers - [EmployeesController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/EmployeesController.cs), [CarsController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/CarsController.cs), [PingsController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs) * Example of BackgroundService - [PingWebsiteBackgroundService.cs](src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs) ![HappyCode.NetCoreBoilerplate.Api](.assets/api.png "HappyCode.NetCoreBoilerplate.Api") diff --git a/src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs b/src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs index 9b3d46dc4..2c68216c3 100644 --- a/src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs +++ b/src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs @@ -1,48 +1,58 @@ -using System.Net.Http; -using HappyCode.NetCoreBoilerplate.Core.Settings; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace HappyCode.NetCoreBoilerplate.Api.BackgroundServices -{ - public class PingWebsiteBackgroundService : BackgroundService - { - private readonly PeriodicTimer _timer; - private readonly HttpClient _client; - private readonly ILogger _logger; - private readonly IOptions _configuration; - - public PingWebsiteBackgroundService( - IHttpClientFactory httpClientFactory, - ILogger logger, - IOptions configuration) - { - _client = httpClientFactory.CreateClient(nameof(PingWebsiteBackgroundService)); - _logger = logger; - _configuration = configuration; - _timer = new PeriodicTimer(TimeSpan.FromMinutes(_configuration.Value.TimeIntervalInMinutes)); - } - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - _logger.LogInformation("{BackgroundService} running at '{Date}', pinging '{URL}'", - nameof(PingWebsiteBackgroundService), DateTime.Now, _configuration.Value.Url); - try - { - using var response = await _client.GetAsync(_configuration.Value.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - _logger.LogInformation("Is '{Host}' responding: {Status}", - _configuration.Value.Url.Authority, response.IsSuccessStatusCode); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Error during ping"); - } - await _timer.WaitForNextTickAsync(cancellationToken); - } - _timer.Dispose(); - } - } -} +using System.Net; +using System.Net.Http; +using HappyCode.NetCoreBoilerplate.Core.Settings; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace HappyCode.NetCoreBoilerplate.Api.BackgroundServices +{ + public interface IPingService + { + public HttpStatusCode WebsiteStatusCode { get; } + } + + public class PingWebsiteBackgroundService : BackgroundService, IPingService + { + private readonly PeriodicTimer _timer; + private readonly HttpClient _client; + private readonly ILogger _logger; + private readonly IOptions _configuration; + + public HttpStatusCode WebsiteStatusCode { get; private set; } + + public PingWebsiteBackgroundService( + IHttpClientFactory httpClientFactory, + ILogger logger, + IOptions configuration) + { + _client = httpClientFactory.CreateClient(nameof(PingWebsiteBackgroundService)); + _logger = logger; + _configuration = configuration; + + _timer = new PeriodicTimer(TimeSpan.FromMinutes(_configuration.Value.TimeIntervalInMinutes)); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + _logger.LogInformation("{BackgroundService} running at '{Date}', pinging '{URL}'", + nameof(PingWebsiteBackgroundService), DateTime.Now, _configuration.Value.Url); + try + { + using var response = await _client.GetAsync(_configuration.Value.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + WebsiteStatusCode = response.StatusCode; + _logger.LogInformation("Is '{Host}' responding: {Status}", + _configuration.Value.Url.Authority, response.IsSuccessStatusCode); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error during ping"); + } + await _timer.WaitForNextTickAsync(cancellationToken); + } + _timer.Dispose(); + } + } +} diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs b/src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs new file mode 100644 index 000000000..66ec898c3 --- /dev/null +++ b/src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs @@ -0,0 +1,41 @@ +using System.Net; +using HappyCode.NetCoreBoilerplate.Api.BackgroundServices; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace HappyCode.NetCoreBoilerplate.Api.Controllers +{ + [AllowAnonymous] + [Route("api/pings")] + public class PingsController : ApiControllerBase + { + private readonly IPingService _pingService; + + public PingsController(IPingService pingService) + { + _pingService = pingService; + } + + [HttpGet("website")] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + public IActionResult GetWebsitePingStatusCode() + { + var result = _pingService.WebsiteStatusCode; + return Ok($"{(int)result} ({result})"); + } + + [HttpGet("random")] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + public IActionResult GetRandomStatusCode() + { + var random = new Random(Guid.NewGuid().GetHashCode()); + int pretender; + do + { + pretender = random.Next(100, 600); + } while (!Enum.IsDefined(typeof(HttpStatusCode), pretender)); + return Ok($"{pretender} ({(HttpStatusCode)pretender})"); + } + } +} diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs b/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs index 381806e44..964338b65 100644 --- a/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs +++ b/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs @@ -1,99 +1,101 @@ -using HappyCode.NetCoreBoilerplate.Api.BackgroundServices; -using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Configurations; -using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Filters; -using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Registrations; -using HappyCode.NetCoreBoilerplate.BooksModule; -using HappyCode.NetCoreBoilerplate.Core; -using HappyCode.NetCoreBoilerplate.Core.Registrations; -using HappyCode.NetCoreBoilerplate.Core.Settings; -using HealthChecks.UI.Client; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.FeatureManagement; -using Microsoft.FeatureManagement.FeatureFilters; -using Swashbuckle.AspNetCore.SwaggerUI; - -namespace HappyCode.NetCoreBoilerplate.Api -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddHttpContextAccessor() - .AddRouting(options => options.LowercaseUrls = true); - - services.AddMvcCore(options => - { - options.Filters.Add(); - options.Filters.Add(); - options.Filters.Add(); - }) - .AddApiExplorer() - .AddDataAnnotations(); - - //there is a difference between AddDbContext() and AddDbContextPool(), more info https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling and https://stackoverflow.com/questions/48443567/adddbcontext-or-adddbcontextpool - services.AddDbContext(options => options.UseMySql(_configuration.GetConnectionString("MySqlDb"), ServerVersion.Parse("8.0")), contextLifetime: ServiceLifetime.Transient, optionsLifetime: ServiceLifetime.Singleton); - services.AddDbContextPool(options => options.UseSqlServer(_configuration.GetConnectionString("MsSqlDb")), poolSize: 10); - - services.Configure(_configuration.GetSection("ApiKey")); - services.AddSwagger(_configuration); - - services.Configure(_configuration.GetSection("PingWebsite")); - services.AddHostedService(); - services.AddHttpClient(nameof(PingWebsiteBackgroundService)); - - services.AddCoreComponents(); - services.AddBooksModule(_configuration); - - services.AddFeatureManagement() - .AddFeatureFilter(); - - services.AddHealthChecks() - .AddMySql(_configuration.GetConnectionString("MySqlDb")) - .AddSqlServer(_configuration.GetConnectionString("MsSqlDb")) - .AddBooksModule(_configuration); - } - - public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapBooksModule(); - - endpoints.MapHealthChecks("/health", new HealthCheckOptions - { - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, - }); - }); - - app.UseSwagger(); - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Simple Api V1"); - c.DocExpansion(DocExpansion.None); - }); - - app.InitBooksModule(); - } - } -} +using System.Linq; +using HappyCode.NetCoreBoilerplate.Api.BackgroundServices; +using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Configurations; +using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Filters; +using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Registrations; +using HappyCode.NetCoreBoilerplate.BooksModule; +using HappyCode.NetCoreBoilerplate.Core; +using HappyCode.NetCoreBoilerplate.Core.Registrations; +using HappyCode.NetCoreBoilerplate.Core.Settings; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.FeatureFilters; +using Swashbuckle.AspNetCore.SwaggerUI; + +namespace HappyCode.NetCoreBoilerplate.Api +{ + public class Startup + { + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddHttpContextAccessor() + .AddRouting(options => options.LowercaseUrls = true); + + services.AddMvcCore(options => + { + options.Filters.Add(); + options.Filters.Add(); + options.Filters.Add(); + }) + .AddApiExplorer() + .AddDataAnnotations(); + + //there is a difference between AddDbContext() and AddDbContextPool(), more info https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling and https://stackoverflow.com/questions/48443567/adddbcontext-or-adddbcontextpool + services.AddDbContext(options => options.UseMySql(_configuration.GetConnectionString("MySqlDb"), ServerVersion.Parse("8.0")), contextLifetime: ServiceLifetime.Transient, optionsLifetime: ServiceLifetime.Singleton); + services.AddDbContextPool(options => options.UseSqlServer(_configuration.GetConnectionString("MsSqlDb")), poolSize: 10); + + services.Configure(_configuration.GetSection("ApiKey")); + services.AddSwagger(_configuration); + + services.Configure(_configuration.GetSection("PingWebsite")); + services.AddHttpClient(nameof(PingWebsiteBackgroundService)); + services.AddHostedService(); + services.AddSingleton(x => x.GetServices().OfType().Single()); + + services.AddCoreComponents(); + services.AddBooksModule(_configuration); + + services.AddFeatureManagement() + .AddFeatureFilter(); + + services.AddHealthChecks() + .AddMySql(_configuration.GetConnectionString("MySqlDb")) + .AddSqlServer(_configuration.GetConnectionString("MsSqlDb")) + .AddBooksModule(_configuration); + } + + public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapBooksModule(); + + endpoints.MapHealthChecks("/health", new HealthCheckOptions + { + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, + }); + }); + + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Simple Api V1"); + c.DocExpansion(DocExpansion.None); + }); + + app.InitBooksModule(); + } + } +} diff --git a/src/HappyCode.NetCoreBoilerplate.BooksModule/Infrastructure/AuthFilter.cs b/src/HappyCode.NetCoreBoilerplate.BooksModule/Infrastructure/AuthFilter.cs index 7145c9c2d..94ae2da7a 100644 --- a/src/HappyCode.NetCoreBoilerplate.BooksModule/Infrastructure/AuthFilter.cs +++ b/src/HappyCode.NetCoreBoilerplate.BooksModule/Infrastructure/AuthFilter.cs @@ -1,46 +1,46 @@ -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace HappyCode.NetCoreBoilerplate.BooksModule.Infrastructure -{ - internal class AuthFilter : IEndpointFilter - { - private readonly IConfiguration _configuration; - private readonly IWebHostEnvironment _env; - - public AuthFilter(IConfiguration configuration, IWebHostEnvironment env) - { - _configuration = configuration; - _env = env; - } - - public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) - { - if (_env.IsEnvironment("Test")) - { - return await next(context); - } - - context.HttpContext.Request.Headers.TryGetValue("Authorization", out var values); - var authorization = values.FirstOrDefault(); - - if (authorization is null) - { - return Results.Unauthorized(); - } - - var key = Regex.Replace(authorization, @"APIKEY\s+", string.Empty, RegexOptions.IgnoreCase); - - var secretKey = _configuration.GetValue("ApiKey:SecretKey"); - - if (secretKey == key) - { - return await next(context); - } - return Results.Unauthorized(); - } - } -} +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace HappyCode.NetCoreBoilerplate.BooksModule.Infrastructure +{ + internal class AuthFilter : IEndpointFilter + { + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _env; + + public AuthFilter(IConfiguration configuration, IWebHostEnvironment env) + { + _configuration = configuration; + _env = env; + } + + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + if (!_env.IsProduction()) + { + return await next(context); + } + + context.HttpContext.Request.Headers.TryGetValue("Authorization", out var values); + var authorization = values.FirstOrDefault(); + + if (authorization is null) + { + return Results.Unauthorized(); + } + + var key = Regex.Replace(authorization, @"APIKEY\s+", string.Empty, RegexOptions.IgnoreCase); + + var secretKey = _configuration.GetValue("ApiKey:SecretKey"); + + if (secretKey == key) + { + return await next(context); + } + return Results.Unauthorized(); + } + } +}