Skip to content

Commit

Permalink
Register SQL/Redis Health Checks (#1422) (#1426)
Browse files Browse the repository at this point in the history
This will ensure that the healthcheck endpoint correctly reports the status of the service if a database connection faults

* Removed view and unused "using" statements

* Map healthchecks to /health

* Add Redis to Health Check

* Add DbContextChecks to Health Checks

* Moved package into Api project

* Remove unnecessary call to AddHealthChecks()

* This bit isn't necessary as we are already registering it on the Sql and Redis servers

* Register /health endpoint on App and API
  • Loading branch information
DrizzlyOwl authored Dec 9, 2024
1 parent f047e8c commit f524ff4
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 608 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.6.2" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.8" />
</ItemGroup>

<ItemGroup>
Expand Down
103 changes: 53 additions & 50 deletions ConcernsCaseWork/ConcernsCaseWork.API/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
using ConcernsCaseWork.API.Extensions;
using ConcernsCaseWork.API.Middleware;
using ConcernsCaseWork.API.StartupConfiguration;
using ConcernsCaseWork.Middleware;
using Microsoft.AspNetCore.Mvc.ApiExplorer;

namespace ConcernsCaseWork.API
{
/// <summary>
/// THIS STARTUP ISN'T USED WHEN API IS HOSTED THROUGH WEBSITE. It is used when running API tests
/// </summary>
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
services.AddConcernsApiProject(Configuration);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseConcernsCaseworkSwagger(provider);

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseMiddleware<ExceptionHandlerMiddleware>();
app.UseMiddleware<ApiKeyMiddleware>();
app.UseMiddleware<UrlDecoderMiddleware>();
app.UseMiddleware<CorrelationIdMiddleware>();
app.UseMiddleware<UserContextReceiverMiddleware>();

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseConcernsCaseworkEndpoints();
}
}
}
using ConcernsCaseWork.API.Extensions;
using ConcernsCaseWork.API.Middleware;
using ConcernsCaseWork.API.StartupConfiguration;
using ConcernsCaseWork.Middleware;
using Microsoft.AspNetCore.Mvc.ApiExplorer;

namespace ConcernsCaseWork.API
{
/// <summary>
/// THIS STARTUP ISN'T USED WHEN API IS HOSTED THROUGH WEBSITE. It is used when running API tests
/// </summary>
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
services.AddConcernsApiProject(Configuration);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseConcernsCaseworkSwagger(provider);

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseMiddleware<ExceptionHandlerMiddleware>();
app.UseMiddleware<ApiKeyMiddleware>();
app.UseMiddleware<UrlDecoderMiddleware>();
app.UseMiddleware<CorrelationIdMiddleware>();
app.UseMiddleware<UserContextReceiverMiddleware>();

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseConcernsCaseworkEndpoints();

// Add Health Checks
app.UseHealthChecks("/health");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using ConcernsCaseWork.Data;

namespace ConcernsCaseWork.API.StartupConfiguration;

public static class DatabaseConfigurationExtensions
{
public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ConcernsDbContext>(options =>
options.UseConcernsSqlServer(connectionString)
);

return services;
}
}
using ConcernsCaseWork.Data;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace ConcernsCaseWork.API.StartupConfiguration;

public static class DatabaseConfigurationExtensions
{
public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ConcernsDbContext>(options =>
options.UseConcernsSqlServer(connectionString)
);
services.AddHealthChecks()
.AddDbContextCheck<ConcernsDbContext>("Concerns Database");

return services;
}
}
Original file line number Diff line number Diff line change
@@ -1,77 +1,55 @@
using ConcernsCaseWork.Pages;
using ConcernsCaseWork.Pages.Base;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using NUnit.Framework;
using System;
using System.Linq;
using System.Reflection;

namespace ConcernsCaseWork.Tests.Security
{
public class AuthorizeAttributeTests
{
public AuthorizeAttributeTests()
{
this.UnauthorizedPages = new Type[]
{
typeof(HealthModel),
typeof(ErrorPageModel),
};
}

public Type[] UnauthorizedPages { get; }

[Test]
public void All_Pages_Include_Authorize_Attribute()
{
var pages = this.GetAllPagesExceptUnauthorizedPages();

pages.Length.Should().BeGreaterThan(0);

using (var scope = new AssertionScope())
{
foreach (Type page in pages)
{
var authAttributes = page.GetCustomAttributes<AuthorizeAttribute>();
if (authAttributes == null || !authAttributes.Any())
{
scope.AddPreFormattedFailure($"Could not find [Authorize] attribute and no exemption for the following page type: {page.Name} ({page.FullName})");
}
}
}
}

[Test]
public void Open_Pages_Do_Not_Require_Authorization()
{
Type[] UnauthorizedPages = new[]
{
typeof(HealthModel),
};

using (var scope = new AssertionScope())
{
foreach (Type page in this.UnauthorizedPages)
{
var authAttribute = page.GetCustomAttribute<AuthorizeAttribute>();
if (authAttribute != null)
{
scope.AddPreFormattedFailure($"Expected page to be open and not require authorisation. But found [Authorize] attribute on the following page type: {page.Name} ({page.FullName})");
}
}
}
}

private Type[] GetAllPagesExceptUnauthorizedPages()
{
return Assembly
.GetAssembly(typeof(AbstractPageModel))
.GetTypes()
.Where(x => x.IsAssignableTo(typeof(PageModel)) && !this.UnauthorizedPages.Contains(x))
.ToArray();
}
}
}
using ConcernsCaseWork.Pages;
using ConcernsCaseWork.Pages.Base;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using NUnit.Framework;
using System;
using System.Linq;
using System.Reflection;

namespace ConcernsCaseWork.Tests.Security
{
public class AuthorizeAttributeTests
{
public AuthorizeAttributeTests()
{
this.UnauthorizedPages = new Type[]
{
typeof(ErrorPageModel),
};
}

public Type[] UnauthorizedPages { get; }

[Test]
public void All_Pages_Include_Authorize_Attribute()
{
var pages = this.GetAllPagesExceptUnauthorizedPages();

pages.Length.Should().BeGreaterThan(0);

using (var scope = new AssertionScope())
{
foreach (Type page in pages)
{
var authAttributes = page.GetCustomAttributes<AuthorizeAttribute>();
if (authAttributes == null || !authAttributes.Any())
{
scope.AddPreFormattedFailure($"Could not find [Authorize] attribute and no exemption for the following page type: {page.Name} ({page.FullName})");
}
}
}
}

private Type[] GetAllPagesExceptUnauthorizedPages()
{
return Assembly
.GetAssembly(typeof(AbstractPageModel))
.GetTypes()
.Where(x => x.IsAssignableTo(typeof(PageModel)) && !this.UnauthorizedPages.Contains(x))
.ToArray();
}
}
}
1 change: 1 addition & 0 deletions ConcernsCaseWork/ConcernsCaseWork/ConcernsCaseWork.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

<ItemGroup>
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="8.0.1" />
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
Expand Down
Loading

0 comments on commit f524ff4

Please sign in to comment.