Add EFCore DbContext check
This commit is contained in:
parent
6a8494f938
commit
014e7eb963
|
|
@ -1,4 +1,4 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26923.0
|
VisualStudioVersion = 15.0.26923.0
|
||||||
MinimumVisualStudioVersion = 15.0.26730.03
|
MinimumVisualStudioVersion = 15.0.26730.03
|
||||||
|
|
@ -56,6 +56,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Diagno
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagnostics.HealthChecks.Tests", "test\Microsoft.Extensions.Diagnostics.HealthChecks.Tests\Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj", "{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagnostics.HealthChecks.Tests", "test\Microsoft.Extensions.Diagnostics.HealthChecks.Tests\Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj", "{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore", "src\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj", "{61F8B71C-4BDA-431C-9485-743D619ACF9A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests", "test\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests.csproj", "{4416D921-0E43-496D-9156-D7EAA9CB8DD3}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -344,6 +348,30 @@ Global
|
||||||
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|x86.ActiveCfg = Release|Any CPU
|
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|x86.Build.0 = Release|Any CPU
|
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -373,6 +401,8 @@ Global
|
||||||
{3B4E60F6-E42D-496E-B96F-71A11DABAEE7} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
|
{3B4E60F6-E42D-496E-B96F-71A11DABAEE7} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
|
||||||
{E718CE19-23CC-427F-B06E-B866E9A35924} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
|
{E718CE19-23CC-427F-B06E-B866E9A35924} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
|
||||||
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
|
{3783E8E4-2E96-4987-A83A-0CCCAF4891C1} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
|
||||||
|
{61F8B71C-4BDA-431C-9485-743D619ACF9A} = {509A6F36-AD80-4A18-B5B1-717D38DFF29D}
|
||||||
|
{4416D921-0E43-496D-9156-D7EAA9CB8DD3} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {D915AA7B-4ADE-4BAC-AF65-1E800D3F3580}
|
SolutionGuid = {D915AA7B-4ADE-4BAC-AF65-1E800D3F3580}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
namespace HealthChecksSample
|
namespace HealthChecksSample
|
||||||
{
|
{
|
||||||
// Pass in `--scenario db` at the command line to run this sample.
|
// Pass in `--scenario db` at the command line to run this sample.
|
||||||
public class DBHealthStartup
|
public class DbHealthStartup
|
||||||
{
|
{
|
||||||
public DBHealthStartup(IConfiguration configuration)
|
public DbHealthStartup(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace HealthChecksSample
|
||||||
|
{
|
||||||
|
// Pass in `--scenario dbcontext` at the command line to run this sample.
|
||||||
|
public class DbContextHealthStartup
|
||||||
|
{
|
||||||
|
public DbContextHealthStartup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Registers required services for health checks
|
||||||
|
services.AddHealthChecks()
|
||||||
|
|
||||||
|
// Registers a health check for the MyContext type. By default the name of the health check will be the
|
||||||
|
// name of the DbContext type. There are other options available through AddDbContextCheck to configure
|
||||||
|
// failure status, tags, and custom test query.
|
||||||
|
.AddDbContextCheck<MyContext>();
|
||||||
|
|
||||||
|
// Registers the MyContext type and configures the database provider.
|
||||||
|
//
|
||||||
|
// The health check added by AddDbContextCheck will create instances of MyContext from the service provider,
|
||||||
|
// and so will reuse the configuration provided here.
|
||||||
|
services.AddDbContext<MyContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
// This will register the health checks middleware at the URL /health.
|
||||||
|
//
|
||||||
|
// Since this sample doesn't do anything to create the database by default, this will
|
||||||
|
// return unhealthy by default.
|
||||||
|
//
|
||||||
|
// You can to to /createdatabase and /deletedatabase to create and delete the database
|
||||||
|
// (respectively), and see how it immediately effects the health status.
|
||||||
|
//
|
||||||
|
app.UseHealthChecks("/health");
|
||||||
|
|
||||||
|
app.Map("/createdatabase", b => b.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Creating the database...\n");
|
||||||
|
await context.Response.Body.FlushAsync();
|
||||||
|
|
||||||
|
var myContext = context.RequestServices.GetRequiredService<MyContext>();
|
||||||
|
await myContext.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
await context.Response.WriteAsync("Done\n");
|
||||||
|
await context.Response.WriteAsync("Go to /health to see the health status\n");
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.Map("/deletedatabase", b => b.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Deleting the database...\n");
|
||||||
|
await context.Response.Body.FlushAsync();
|
||||||
|
|
||||||
|
var myContext = context.RequestServices.GetRequiredService<MyContext>();
|
||||||
|
await myContext.Database.EnsureDeletedAsync();
|
||||||
|
|
||||||
|
await context.Response.WriteAsync("Done\n");
|
||||||
|
await context.Response.WriteAsync("Go to /health to see the health status\n");
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Go to /health to see the health status\n");
|
||||||
|
await context.Response.WriteAsync("Go to /createdatabase to create the database\n");
|
||||||
|
await context.Response.WriteAsync("Go to /deletedatabase to create the database\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Used in our tests -->
|
<!-- Used in our tests -->
|
||||||
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">netcoreapp2.0;net461</TargetFrameworks>
|
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">netcoreapp2.2;net461</TargetFrameworks>
|
||||||
<TargetFrameworks Condition="'$(TargetFrameworks)'==''">netcoreapp2.0</TargetFrameworks>
|
<TargetFrameworks Condition="'$(TargetFrameworks)'==''">netcoreapp2.2</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -12,12 +12,14 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion)" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
||||||
<PackageReference Include="System.Data.SqlClient" Version="$(SystemDataSqlClientPackageVersion)" />
|
<PackageReference Include="System.Data.SqlClient" Version="$(SystemDataSqlClientPackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Diagnostics.HealthChecks\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Diagnostics.HealthChecks\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace HealthChecksSample
|
||||||
|
{
|
||||||
|
public class MyContext : DbContext
|
||||||
|
{
|
||||||
|
public MyContext(DbContextOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Blog> Blog { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Blog
|
||||||
|
{
|
||||||
|
public int BlogId { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,12 +15,13 @@ namespace HealthChecksSample
|
||||||
{
|
{
|
||||||
_scenarios = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
_scenarios = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ "", typeof(CustomWriterStartup) },
|
{ "", typeof(BasicStartup) },
|
||||||
{ "basic", typeof(BasicStartup) },
|
{ "basic", typeof(BasicStartup) },
|
||||||
{ "writer", typeof(CustomWriterStartup) },
|
{ "writer", typeof(CustomWriterStartup) },
|
||||||
{ "liveness", typeof(LivenessProbeStartup) },
|
{ "liveness", typeof(LivenessProbeStartup) },
|
||||||
{ "port", typeof(ManagementPortStartup) },
|
{ "port", typeof(ManagementPortStartup) },
|
||||||
{ "db", typeof(DBHealthStartup) },
|
{ "db", typeof(DbHealthStartup) },
|
||||||
|
{ "dbcontext", typeof(DbContextHealthStartup) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
|
{
|
||||||
|
internal sealed class DbContextHealthCheck<TContext> : IHealthCheck where TContext : DbContext
|
||||||
|
{
|
||||||
|
private static readonly Func<TContext, CancellationToken, Task<bool>> DefaultTestQuery = (dbContext, cancellationToken) =>
|
||||||
|
{
|
||||||
|
return dbContext.Database.CanConnectAsync(cancellationToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly TContext _dbContext;
|
||||||
|
private readonly IOptionsMonitor<DbContextHealthCheckOptions<TContext>> _options;
|
||||||
|
|
||||||
|
public DbContextHealthCheck(TContext dbContext, IOptionsMonitor<DbContextHealthCheckOptions<TContext>> options)
|
||||||
|
{
|
||||||
|
if (dbContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dbContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = _options.Get(context.Registration.Name);
|
||||||
|
var testQuery = options.CustomTestQuery ?? DefaultTestQuery;
|
||||||
|
|
||||||
|
if (await testQuery(_dbContext, cancellationToken))
|
||||||
|
{
|
||||||
|
return HealthCheckResult.Passed();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HealthCheckResult.Failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
|
{
|
||||||
|
internal sealed class DbContextHealthCheckOptions<TContext> where TContext : DbContext
|
||||||
|
{
|
||||||
|
public Func<TContext, CancellationToken, Task<bool>> CustomTestQuery { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public static class EntityFrameworkCoreHealthChecksBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a health check for the specified <see cref="DbContext"/> type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TContext">The <see cref="DbContext"/> type.</typeparam>
|
||||||
|
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||||
|
/// <param name="name">
|
||||||
|
/// The health check name. Optional. If <c>null</c> the type name of <typeparamref name="TContext"/> will be used for the name.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="failureStatus">
|
||||||
|
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
|
||||||
|
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
|
||||||
|
/// <param name="customTestQuery">
|
||||||
|
/// A custom test query that will be executed when the health check executes to test the health of the database
|
||||||
|
/// connection and configurations.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// The health check implementation added by this method will use the dependency injection container
|
||||||
|
/// to create an instance of <typeparamref name="TContext"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// By default the health check implementation will use the <see cref="DatabaseFacade.CanConnectAsync(CancellationToken)"/> method
|
||||||
|
/// to test connectivity to the database. This method requires that the database provider has correctly implemented the
|
||||||
|
/// <see cref="IDatabaseCreatorWithCanConnect" /> interface. If the database provide has not implemented this interface
|
||||||
|
/// then the health check will report a failure.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Providing a <paramref name="customTestQuery" /> will replace the use of <see cref="DatabaseFacade.CanConnectAsync(CancellationToken)"/>
|
||||||
|
/// to test database connectivity. An implementation of a test query should handle exceptions that can arise due to connectivity failure,
|
||||||
|
/// and should return a pass/fail result. The test query should be be designed to complete in a short and predicatable amount of time.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static IHealthChecksBuilder AddDbContextCheck<TContext>(
|
||||||
|
this IHealthChecksBuilder builder,
|
||||||
|
string name = null,
|
||||||
|
HealthStatus? failureStatus = default,
|
||||||
|
IEnumerable<string> tags = default,
|
||||||
|
Func<TContext, CancellationToken, Task<bool>> customTestQuery = default)
|
||||||
|
where TContext : DbContext
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
name = typeof(TContext).Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customTestQuery != null)
|
||||||
|
{
|
||||||
|
builder.Services.Configure<DbContextHealthCheckOptions<TContext>>(name, options => options.CustomTestQuery = customTestQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.AddCheck<DbContextHealthCheck<TContext>>(name, failureStatus, tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>
|
||||||
|
Components for performing health checks using EntityFrameworkCore.
|
||||||
|
</Description>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>diagnostics;healthchecks;entityframeworkcore</PackageTags>
|
||||||
|
<BaseNamespace>Microsoft.Extensions.Diagnostics.HealthChecks</BaseNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\Microsoft.Extensions.Diagnostics.HealthChecks\Microsoft.Extensions.Diagnostics.HealthChecks.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
|
{
|
||||||
|
public class DbContextHealthCheckTest
|
||||||
|
{
|
||||||
|
// Just testing pass here since it would be complicated to simulate a failure. All of that logic lives in EF anyway.
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckAsync_DefaultTest_Pass()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = CreateServices();
|
||||||
|
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
{
|
||||||
|
var registration = Assert.Single(services.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value.Registrations);
|
||||||
|
var check = ActivatorUtilities.CreateInstance<DbContextHealthCheck<TestDbContext>>(scope.ServiceProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.Result, "Health check passed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckAsync_CustomTest_Pass()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = CreateServices(async (c, ct) =>
|
||||||
|
{
|
||||||
|
return 0 < await c.Blogs.CountAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
{
|
||||||
|
var registration = Assert.Single(services.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value.Registrations);
|
||||||
|
var check = ActivatorUtilities.CreateInstance<DbContextHealthCheck<TestDbContext>>(scope.ServiceProvider);
|
||||||
|
|
||||||
|
// Add a blog so that the custom test passes
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<TestDbContext>();
|
||||||
|
context.Add(new Blog());
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.Result, "Health check passed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckAsync_CustomTest_Fail()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var services = CreateServices(async (c, ct) =>
|
||||||
|
{
|
||||||
|
return 0 < await c.Blogs.CountAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
{
|
||||||
|
var registration = Assert.Single(services.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value.Registrations);
|
||||||
|
var check = ActivatorUtilities.CreateInstance<DbContextHealthCheck<TestDbContext>>(scope.ServiceProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result.Result, "Health check failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceProvider CreateServices(Func<TestDbContext, CancellationToken, Task<bool>> testQuery = null)
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddDbContext<TestDbContext>(o => o.UseInMemoryDatabase("Test"));
|
||||||
|
|
||||||
|
var builder = serviceCollection.AddHealthChecks();
|
||||||
|
builder.AddDbContextCheck<TestDbContext>("test", HealthStatus.Degraded, new[] { "tag1", "tag2", }, testQuery);
|
||||||
|
return serviceCollection.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public class EntityFrameworkCoreHealthChecksBuilderExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AddDbContextCheck_RegistersDbContextHealthCheck()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddDbContext<TestDbContext>(o => o.UseInMemoryDatabase("Test"));
|
||||||
|
|
||||||
|
var builder = serviceCollection.AddHealthChecks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.AddDbContextCheck<TestDbContext>("test", HealthStatus.Degraded, new[] { "tag1", "tag2", }, (c, ct) => Task.FromResult(true));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var services = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
var registrations = services.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value.Registrations;
|
||||||
|
|
||||||
|
var registration = Assert.Single(registrations);
|
||||||
|
Assert.Equal("test", registration.Name);
|
||||||
|
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||||
|
Assert.Equal(new[] { "tag1", "tag2", }, registration.Tags.ToArray());
|
||||||
|
|
||||||
|
var options = services.GetRequiredService<IOptionsMonitor<DbContextHealthCheckOptions<TestDbContext>>>();
|
||||||
|
Assert.NotNull(options.Get("test").CustomTestQuery);
|
||||||
|
|
||||||
|
using (var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
|
||||||
|
{
|
||||||
|
var check = Assert.IsType<DbContextHealthCheck<TestDbContext>>(registration.Factory(scope.ServiceProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
|
<RootNamespace>Microsoft.AspNetCore.Diagnostics.HealthChecks</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||||
|
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
|
{
|
||||||
|
public class TestDbContext : DbContext
|
||||||
|
{
|
||||||
|
public TestDbContext(DbContextOptions<TestDbContext> options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Blog> Blogs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Blog
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue