Skip to content

Commit cfe158c

Browse files
author
John Luo
authored
Convert DatabaseErrorPage to exception filter (#24588)
* Convert DatabaseErrorPage middleware to exception filter
1 parent a6abd11 commit cfe158c

File tree

40 files changed

+1099
-405
lines changed

40 files changed

+1099
-405
lines changed

src/Identity/ApiAuthorization.IdentityServer/samples/ApiAuthSample/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public void ConfigureServices(IServiceCollection services)
4040

4141
services.AddMvc()
4242
.AddNewtonsoftJson();
43+
44+
services.AddDatabaseDeveloperPageExceptionFilter();
4345
}
4446

4547
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -48,7 +50,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4850
if (env.IsDevelopment())
4951
{
5052
app.UseDeveloperExceptionPage();
51-
app.UseDatabaseErrorPage();
5253
}
5354
else
5455
{

src/Identity/samples/IdentitySample.DefaultUI/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public void ConfigureServices(IServiceCollection services)
4949
services.AddDefaultIdentity<ApplicationUser>(o => o.SignIn.RequireConfirmedAccount = true)
5050
.AddRoles<IdentityRole>()
5151
.AddEntityFrameworkStores<ApplicationDbContext>();
52+
53+
services.AddDatabaseDeveloperPageExceptionFilter();
5254
}
5355

5456

@@ -58,7 +60,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF
5860
if (env.IsDevelopment())
5961
{
6062
app.UseDeveloperExceptionPage();
61-
app.UseDatabaseErrorPage();
6263
}
6364
else
6465
{

src/Identity/samples/IdentitySample.Mvc/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public void ConfigureServices(IServiceCollection services)
5353
// Add application services.
5454
services.AddTransient<IEmailSender, AuthMessageSender>();
5555
services.AddTransient<ISmsSender, AuthMessageSender>();
56+
57+
services.AddDatabaseDeveloperPageExceptionFilter();
5658
}
5759

5860
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -61,7 +63,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF
6163
if (env.IsDevelopment())
6264
{
6365
app.UseDeveloperExceptionPage();
64-
app.UseDatabaseErrorPage();
6566
}
6667
else
6768
{

src/Identity/testassets/Identity.DefaultUI.WebSite/NoIdentityStartup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public void ConfigureServices(IServiceCollection services)
3737
options.Conventions.AuthorizePage("/Areas/Identity/Pages/Account/Logout");
3838
})
3939
.AddNewtonsoftJson();
40+
41+
services.AddDatabaseDeveloperPageExceptionFilter();
4042
}
4143

4244
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -47,7 +49,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4749
if (env.IsDevelopment())
4850
{
4951
app.UseDeveloperExceptionPage();
50-
app.UseDatabaseErrorPage();
5152
}
5253
else
5354
{

src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,22 @@ public virtual void ConfigureServices(IServiceCollection services)
4949
services.AddDefaultIdentity<TUser>()
5050
.AddRoles<IdentityRole>()
5151
.AddEntityFrameworkStores<TContext>();
52-
52+
5353
services.AddMvc();
5454
services.AddSingleton<IFileVersionProvider, FileVersionProvider>();
55+
56+
services.AddDatabaseDeveloperPageExceptionFilter();
5557
}
5658

5759
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
5860
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
5961
{
6062
// This prevents running out of file watchers on some linux machines
6163
DisableFilePolling(env);
62-
64+
6365
if (env.IsDevelopment())
6466
{
6567
app.UseDeveloperExceptionPage();
66-
app.UseDatabaseErrorPage();
6768
}
6869
else
6970
{

src/Identity/testassets/Identity.DefaultUI.WebSite/StartupWithoutEndpointRouting.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public override void ConfigureServices(IServiceCollection services)
2222
{
2323
base.ConfigureServices(services);
2424
services.AddMvc(options => options.EnableEndpointRouting = false);
25+
services.AddDatabaseDeveloperPageExceptionFilter();
2526
}
2627

2728
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -33,7 +34,6 @@ public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
3334
if (env.IsDevelopment())
3435
{
3536
app.UseDeveloperExceptionPage();
36-
app.UseDatabaseErrorPage();
3737
}
3838
else
3939
{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
8+
{
9+
internal class DatabaseContextDetails
10+
{
11+
public Type Type { get; }
12+
public bool DatabaseExists { get; }
13+
public bool PendingModelChanges { get; }
14+
public IEnumerable<string> PendingMigrations { get; }
15+
16+
public DatabaseContextDetails(Type type, bool databaseExists, bool pendingModelChanges, IEnumerable<string> pendingMigrations)
17+
{
18+
Type = type;
19+
DatabaseExists = databaseExists;
20+
PendingModelChanges = pendingModelChanges;
21+
PendingMigrations = pendingMigrations;
22+
}
23+
}
24+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Data.Common;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Builder;
10+
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
11+
using Microsoft.EntityFrameworkCore;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
16+
#nullable enable
17+
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
18+
{
19+
public sealed class DatabaseDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
20+
{
21+
private readonly ILogger _logger;
22+
private readonly DatabaseErrorPageOptions _options;
23+
24+
public DatabaseDeveloperPageExceptionFilter(ILogger<DatabaseDeveloperPageExceptionFilter> logger, IOptions<DatabaseErrorPageOptions> options)
25+
{
26+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
27+
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
28+
}
29+
30+
public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next)
31+
{
32+
if (!(errorContext.Exception is DbException))
33+
{
34+
await next(errorContext);
35+
}
36+
37+
try
38+
{
39+
// Look for DbContext classes registered in the service provider
40+
var registeredContexts = errorContext.HttpContext.RequestServices.GetServices<DbContextOptions>()
41+
.Select(o => o.ContextType);
42+
43+
if (registeredContexts.Any())
44+
{
45+
var contextDetails = new List<DatabaseContextDetails>();
46+
47+
foreach (var registeredContext in registeredContexts)
48+
{
49+
var details = await errorContext.HttpContext.GetContextDetailsAsync(registeredContext, _logger);
50+
51+
if (details != null)
52+
{
53+
contextDetails.Add(details);
54+
}
55+
}
56+
57+
if (contextDetails.Any(c => c.PendingModelChanges || c.PendingMigrations.Any()))
58+
{
59+
var page = new DatabaseErrorPage
60+
{
61+
Model = new DatabaseErrorPageModel(errorContext.Exception, contextDetails, _options, errorContext.HttpContext.Request.PathBase)
62+
};
63+
64+
await page.ExecuteAsync(errorContext.HttpContext);
65+
return;
66+
}
67+
}
68+
}
69+
catch (Exception e)
70+
{
71+
_logger.DatabaseErrorPageMiddlewareException(e);
72+
return;
73+
}
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Diagnostics;
6+
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
7+
using Microsoft.Extensions.DependencyInjection.Extensions;
8+
9+
#nullable enable
10+
namespace Microsoft.Extensions.DependencyInjection
11+
{
12+
/// <summary>
13+
/// Service extension methods for the <see cref="DatabaseDeveloperPageExceptionFilter"/>.
14+
/// </summary>
15+
public static class DatabaseDeveloperPageExceptionFilterServiceExtensions
16+
{
17+
/// <summary>
18+
/// Add response caching services.
19+
/// </summary>
20+
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
21+
/// <returns></returns>
22+
public static IServiceCollection AddDatabaseDeveloperPageExceptionFilter(this IServiceCollection services)
23+
{
24+
if (services == null)
25+
{
26+
throw new ArgumentNullException(nameof(services));
27+
}
28+
29+
services.TryAddEnumerable(new ServiceDescriptor(typeof(IDeveloperPageExceptionFilter), typeof(DatabaseDeveloperPageExceptionFilter), ServiceLifetime.Singleton));
30+
31+
return services;
32+
}
33+
}
34+
}

src/Middleware/Diagnostics.EntityFrameworkCore/src/DatabaseErrorPageExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Builder
1111
/// <summary>
1212
/// <see cref="IApplicationBuilder"/> extension methods for the <see cref="DatabaseErrorPageMiddleware"/>.
1313
/// </summary>
14+
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
1415
public static class DatabaseErrorPageExtensions
1516
{
1617
/// <summary>
@@ -19,6 +20,7 @@ public static class DatabaseErrorPageExtensions
1920
/// </summary>
2021
/// <param name="app">The <see cref="IApplicationBuilder"/> to register the middleware with.</param>
2122
/// <returns>The same <see cref="IApplicationBuilder"/> instance so that multiple calls can be chained.</returns>
23+
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
2224
public static IApplicationBuilder UseDatabaseErrorPage(this IApplicationBuilder app)
2325
{
2426
if (app == null)
@@ -36,6 +38,7 @@ public static IApplicationBuilder UseDatabaseErrorPage(this IApplicationBuilder
3638
/// <param name="app">The <see cref="IApplicationBuilder"/> to register the middleware with.</param>
3739
/// <param name="options">A <see cref="DatabaseErrorPageOptions"/> that specifies options for the middleware.</param>
3840
/// <returns>The same <see cref="IApplicationBuilder"/> instance so that multiple calls can be chained.</returns>
41+
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
3942
public static IApplicationBuilder UseDatabaseErrorPage(
4043
this IApplicationBuilder app, DatabaseErrorPageOptions options)
4144
{

0 commit comments

Comments
 (0)