Convert DatabaseErrorPage to exception filter (#24588)

* Convert DatabaseErrorPage middleware to exception filter
This commit is contained in:
John Luo 2020-08-17 11:13:17 -07:00 committed by GitHub
parent a6abd1101d
commit cfe158cbed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1142 additions and 448 deletions

View File

@ -40,6 +40,8 @@ namespace ApiAuthSample
services.AddMvc()
.AddNewtonsoftJson();
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -48,7 +50,6 @@ namespace ApiAuthSample
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -49,6 +49,8 @@ namespace IdentitySample.DefaultUI
services.AddDefaultIdentity<ApplicationUser>(o => o.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddDatabaseDeveloperPageExceptionFilter();
}
@ -58,7 +60,6 @@ namespace IdentitySample.DefaultUI
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -53,6 +53,8 @@ namespace IdentitySample
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -61,7 +63,6 @@ namespace IdentitySample
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -37,6 +37,8 @@ namespace Identity.DefaultUI.WebSite
options.Conventions.AuthorizePage("/Areas/Identity/Pages/Account/Logout");
})
.AddNewtonsoftJson();
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -47,7 +49,6 @@ namespace Identity.DefaultUI.WebSite
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -49,9 +49,11 @@ namespace Identity.DefaultUI.WebSite
services.AddDefaultIdentity<TUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<TContext>();
services.AddMvc();
services.AddSingleton<IFileVersionProvider, FileVersionProvider>();
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -59,11 +61,10 @@ namespace Identity.DefaultUI.WebSite
{
// This prevents running out of file watchers on some linux machines
DisableFilePolling(env);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -22,6 +22,7 @@ namespace Identity.DefaultUI.WebSite
{
base.ConfigureServices(services);
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -33,7 +34,6 @@ namespace Identity.DefaultUI.WebSite
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{

View File

@ -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 System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
internal class DatabaseContextDetails
{
public Type Type { get; }
public bool DatabaseExists { get; }
public bool PendingModelChanges { get; }
public IEnumerable<string> PendingMigrations { get; }
public DatabaseContextDetails(Type type, bool databaseExists, bool pendingModelChanges, IEnumerable<string> pendingMigrations)
{
Type = type;
DatabaseExists = databaseExists;
PendingModelChanges = pendingModelChanges;
PendingMigrations = pendingMigrations;
}
}
}

View File

@ -0,0 +1,76 @@
// 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.Data.Common;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
#nullable enable
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
public sealed class DatabaseDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
private readonly ILogger _logger;
private readonly DatabaseErrorPageOptions _options;
public DatabaseDeveloperPageExceptionFilter(ILogger<DatabaseDeveloperPageExceptionFilter> logger, IOptions<DatabaseErrorPageOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next)
{
if (!(errorContext.Exception is DbException))
{
await next(errorContext);
}
try
{
// Look for DbContext classes registered in the service provider
var registeredContexts = errorContext.HttpContext.RequestServices.GetServices<DbContextOptions>()
.Select(o => o.ContextType);
if (registeredContexts.Any())
{
var contextDetails = new List<DatabaseContextDetails>();
foreach (var registeredContext in registeredContexts)
{
var details = await errorContext.HttpContext.GetContextDetailsAsync(registeredContext, _logger);
if (details != null)
{
contextDetails.Add(details);
}
}
if (contextDetails.Any(c => c.PendingModelChanges || c.PendingMigrations.Any()))
{
var page = new DatabaseErrorPage
{
Model = new DatabaseErrorPageModel(errorContext.Exception, contextDetails, _options, errorContext.HttpContext.Request.PathBase)
};
await page.ExecuteAsync(errorContext.HttpContext);
return;
}
}
}
catch (Exception e)
{
_logger.DatabaseErrorPageMiddlewareException(e);
return;
}
}
}
}

View File

@ -0,0 +1,34 @@
// 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 Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;
#nullable enable
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Service extension methods for the <see cref="DatabaseDeveloperPageExceptionFilter"/>.
/// </summary>
public static class DatabaseDeveloperPageExceptionFilterServiceExtensions
{
/// <summary>
/// Add response caching services.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
/// <returns></returns>
public static IServiceCollection AddDatabaseDeveloperPageExceptionFilter(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAddEnumerable(new ServiceDescriptor(typeof(IDeveloperPageExceptionFilter), typeof(DatabaseDeveloperPageExceptionFilter), ServiceLifetime.Singleton));
return services;
}
}
}

View File

@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Builder
/// <summary>
/// <see cref="IApplicationBuilder"/> extension methods for the <see cref="DatabaseErrorPageMiddleware"/>.
/// </summary>
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
public static class DatabaseErrorPageExtensions
{
/// <summary>
@ -19,6 +20,7 @@ namespace Microsoft.AspNetCore.Builder
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> to register the middleware with.</param>
/// <returns>The same <see cref="IApplicationBuilder"/> instance so that multiple calls can be chained.</returns>
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
public static IApplicationBuilder UseDatabaseErrorPage(this IApplicationBuilder app)
{
if (app == null)
@ -36,6 +38,7 @@ namespace Microsoft.AspNetCore.Builder
/// <param name="app">The <see cref="IApplicationBuilder"/> to register the middleware with.</param>
/// <param name="options">A <see cref="DatabaseErrorPageOptions"/> that specifies options for the middleware.</param>
/// <returns>The same <see cref="IApplicationBuilder"/> instance so that multiple calls can be chained.</returns>
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
public static IApplicationBuilder UseDatabaseErrorPage(
this IApplicationBuilder app, DatabaseErrorPageOptions options)
{

View File

@ -12,12 +12,6 @@ using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -56,6 +50,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
/// consumes them to detect database related exception.
/// </param>
/// <param name="options">The options to control what information is displayed on the error page.</param>
[Obsolete("This is obsolete and will be removed in a future version. Use DatabaseDeveloperPageExceptionFilter instead, see documentation at https://aka.ms/DatabaseDeveloperPageExceptionFilter.")]
public DatabaseErrorPageMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory,
@ -101,7 +96,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
// Because CallContext is cloned at each async operation we cannot
// lazily create the error object when an error is encountered, otherwise
// it will not be available to code outside of the current async context.
// it will not be available to code outside of the current async context.
// We create it ahead of time so that any cloning just clones the reference
// to the object that will hold any errors.
@ -116,81 +111,18 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
if (ShouldDisplayErrorPage(exception))
{
var contextType = _localDiagnostic.Value.ContextType;
var context = (DbContext)httpContext.RequestServices.GetService(contextType);
var details = await httpContext.GetContextDetailsAsync(contextType, _logger);
if (context == null)
if (details != null && (details.PendingModelChanges || details.PendingMigrations.Count() > 0))
{
_logger.ContextNotRegisteredDatabaseErrorPageMiddleware(contextType.FullName);
}
else
{
var relationalDatabaseCreator = context.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
if (relationalDatabaseCreator == null)
var page = new DatabaseErrorPage
{
_logger.NotRelationalDatabase();
}
else
{
var databaseExists = await relationalDatabaseCreator.ExistsAsync();
Model = new DatabaseErrorPageModel(exception, new DatabaseContextDetails[] { details }, _options, httpContext.Request.PathBase)
};
if (databaseExists)
{
databaseExists = await relationalDatabaseCreator.HasTablesAsync();
}
await page.ExecuteAsync(httpContext);
var migrationsAssembly = context.GetService<IMigrationsAssembly>();
var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IConventionModel conventionModel)
{
var conventionSet = context.GetService<IConventionSetBuilder>().CreateConventionSet();
var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType<TypeMappingConvention>().FirstOrDefault();
if (typeMappingConvention != null)
{
typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null);
}
var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType<RelationalModelConvention>().FirstOrDefault();
if (relationalModelConvention != null)
{
snapshotModel = relationalModelConvention.ProcessModelFinalized(conventionModel);
}
}
if (snapshotModel is IMutableModel mutableModel)
{
snapshotModel = mutableModel.FinalizeModel();
}
// HasDifferences will return true if there is no model snapshot, but if there is an existing database
// and no model snapshot then we don't want to show the error page since they are most likely targeting
// and existing database and have just misconfigured their model
var pendingModelChanges
= (!databaseExists || migrationsAssembly.ModelSnapshot != null)
&& modelDiffer.HasDifferences(snapshotModel?.GetRelationalModel(), context.Model.GetRelationalModel());
var pendingMigrations
= (databaseExists
? await context.Database.GetPendingMigrationsAsync()
: context.Database.GetMigrations())
.ToArray();
if (pendingModelChanges || pendingMigrations.Length > 0)
{
var page = new DatabaseErrorPage
{
Model = new DatabaseErrorPageModel(
contextType, exception, databaseExists, pendingModelChanges, pendingMigrations, _options)
};
await page.ExecuteAsync(httpContext);
return;
}
}
return;
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -14,11 +14,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
new EventId(1, "NoContextType"),
"No context type was specified. Ensure the form data from the request includes a 'context' value, specifying the context type name to apply migrations for.");
private static readonly Action<ILogger, string, Exception> _invalidContextType = LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, "InvalidContextType"),
"The context type '{ContextTypeName}' could not be loaded. Ensure this is the correct type name for the context you are trying to apply migrations for.");
private static readonly Action<ILogger, string, Exception> _contextNotRegistered = LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(3, "ContextNotRegistered"),
@ -85,11 +80,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
_noContextType(logger, null);
}
public static void InvalidContextType(this ILogger logger, string contextTypeName)
{
_invalidContextType(logger, contextTypeName, null);
}
public static void ContextNotRegistered(this ILogger logger, string contextTypeName)
{
_contextNotRegistered(logger, contextTypeName, null);

View File

@ -0,0 +1,87 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
#nullable enable
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
internal static class HttpContextDatabaseContextDetailsExtensions
{
public static async ValueTask<DatabaseContextDetails?> GetContextDetailsAsync(this HttpContext httpContext, Type dbcontextType, ILogger logger)
{
var context = (DbContext?)httpContext.RequestServices.GetService(dbcontextType);
if (context == null)
{
logger.ContextNotRegisteredDatabaseErrorPageMiddleware(dbcontextType.FullName);
return null;
}
var relationalDatabaseCreator = context.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
if (relationalDatabaseCreator == null)
{
logger.NotRelationalDatabase();
return null;
}
var databaseExists = await relationalDatabaseCreator.ExistsAsync();
if (databaseExists)
{
databaseExists = await relationalDatabaseCreator.HasTablesAsync();
}
var migrationsAssembly = context.GetService<IMigrationsAssembly>();
var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IConventionModel conventionModel)
{
var conventionSet = context.GetService<IConventionSetBuilder>().CreateConventionSet();
var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType<TypeMappingConvention>().FirstOrDefault();
if (typeMappingConvention != null)
{
typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null);
}
var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType<RelationalModelConvention>().FirstOrDefault();
if (relationalModelConvention != null)
{
snapshotModel = relationalModelConvention.ProcessModelFinalized(conventionModel);
}
}
if (snapshotModel is IMutableModel mutableModel)
{
snapshotModel = mutableModel.FinalizeModel();
}
// HasDifferences will return true if there is no model snapshot, but if there is an existing database
// and no model snapshot then we don't want to show the error page since they are most likely targeting
// and existing database and have just misconfigured their model
return new DatabaseContextDetails(
type: dbcontextType,
databaseExists: databaseExists,
pendingModelChanges: (!databaseExists || migrationsAssembly.ModelSnapshot != null)
&& modelDiffer.HasDifferences(snapshotModel?.GetRelationalModel(), context.Model.GetRelationalModel()),
pendingMigrations: databaseExists
? await context.Database.GetPendingMigrationsAsync()
: context.Database.GetMigrations());
}
}
}

View File

@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
<Reference Include="Microsoft.EntityFrameworkCore.Relational" />
</ItemGroup>

View File

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -72,9 +74,10 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
if (db != null)
{
var dbName = db.GetType().FullName;
try
{
_logger.ApplyingMigrations(db.GetType().FullName);
_logger.ApplyingMigrations(dbName);
await db.Database.MigrateAsync();
@ -82,13 +85,13 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
context.Response.Headers.Add("Pragma", new[] { "no-cache" });
context.Response.Headers.Add("Cache-Control", new[] { "no-cache,no-store" });
_logger.MigrationsApplied(db.GetType().FullName);
_logger.MigrationsApplied(dbName);
}
catch (Exception ex)
{
var message = Strings.FormatMigrationsEndPointMiddleware_Exception(db.GetType().FullName) + ex;
var message = Strings.FormatMigrationsEndPointMiddleware_Exception(dbName) + ex;
_logger.MigrationsEndPointMiddlewareException(db.GetType().FullName, ex);
_logger.MigrationsEndPointMiddlewareException(dbName, ex);
throw new InvalidOperationException(message, ex);
}
@ -114,32 +117,25 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
return null;
}
// Look for DbContext classes registered in the service provider
var registeredContexts = context.RequestServices.GetServices<DbContextOptions>()
.Select(o => o.ContextType);
if (!registeredContexts.Any(c => string.Equals(contextTypeName, c.AssemblyQualifiedName)))
{
var message = Strings.FormatMigrationsEndPointMiddleware_ContextNotRegistered(contextTypeName);
logger.ContextNotRegistered(contextTypeName);
await WriteErrorToResponse(context.Response, message);
return null;
}
var contextType = Type.GetType(contextTypeName);
if (contextType == null)
{
var message = Strings.FormatMigrationsEndPointMiddleware_InvalidContextType(contextTypeName);
logger.InvalidContextType(contextTypeName);
await WriteErrorToResponse(context.Response, message);
return null;
}
var db = (DbContext)context.RequestServices.GetService(contextType);
if (db == null)
{
var message = Strings.FormatMigrationsEndPointMiddleware_ContextNotRegistered(contextType.FullName);
logger.ContextNotRegistered(contextType.FullName);
await WriteErrorToResponse(context.Response, message);
return null;
}
return db;
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -148,32 +148,29 @@
<value>In Visual Studio, use the Package Manager Console to scaffold a new migration and apply it to the database:</value>
</data>
<data name="DatabaseErrorPage_NoDbOrMigrationsTitle" xml:space="preserve">
<value>Use migrations to create the database for {0}</value>
<value>Use migrations to create the database</value>
</data>
<data name="DatabaseErrorPage_PendingChangesInfoPMC" xml:space="preserve">
<value>In Visual Studio, use the Package Manager Console to scaffold a new migration for these changes and apply them to the database:</value>
</data>
<data name="DatabaseErrorPage_PendingChangesTitle" xml:space="preserve">
<value>There are pending model changes for {0}</value>
<value>There are pending model changes</value>
</data>
<data name="DatabaseErrorPage_PendingMigrationsInfo" xml:space="preserve">
<value>There are migrations for {0} that have not been applied to the database</value>
<value>There are migrations that have not been applied to the following database(s):</value>
</data>
<data name="DatabaseErrorPage_PendingMigrationsTitle" xml:space="preserve">
<value>Applying existing migrations for {0} may resolve this issue</value>
<value>Applying existing migrations may resolve this issue</value>
</data>
<data name="DatabaseErrorPage_ApplyMigrationsCommandCLI" xml:space="preserve">
<value>&gt; dotnet ef database update</value>
</data>
<data name="MigrationsEndPointMiddleware_ContextNotRegistered" xml:space="preserve">
<value>The context type '{0}' was not found in services. This usually means the context was not registered in services during startup. You probably want to call AddScoped&lt;{0}&gt;() inside the UseServices(...) call in your application startup code.</value>
<value>The context type '{0}' was not found in services. This usually means either the context is invalid or it was not registered in services during startup. You probably want to call AddDBContext&lt;&gt;() inside the ConfigureServices(...) call in your application startup code.</value>
</data>
<data name="MigrationsEndPointMiddleware_Exception" xml:space="preserve">
<value>An error occurred while applying the migrations for '{0}'. See InnerException for details.</value>
</data>
<data name="MigrationsEndPointMiddleware_InvalidContextType" xml:space="preserve">
<value>The context type '{0}' could not be loaded. Ensure this is the correct type name for the context you are trying to apply migrations for.</value>
</data>
<data name="MigrationsEndPointMiddleware_NoContextType" xml:space="preserve">
<value>No context type was specified. Ensure the form data from the request includes a 'context' value, specifying the context type name to apply migrations for.</value>
</data>
@ -198,4 +195,10 @@
<data name="DatabaseErrorPage_HowToApplyFromCLI" xml:space="preserve">
<value>Alternatively, you can apply pending migrations from a command prompt at your project directory:</value>
</data>
<data name="DatabaseErrorPage_NoDbOrMigrationsInfo" xml:space="preserve">
<value>A database needs to be created for the following:</value>
</data>
<data name="DatabaseErrorPage_PendingChangesInfo" xml:space="preserve">
<value>Pending model changes are detected in the following:</value>
</data>
</root>

View File

@ -4,31 +4,40 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
{
#line hidden
using System.Threading.Tasks;
#nullable restore
#line 1 "DatabaseErrorPage.cshtml"
using System;
#line default
#line hidden
#nullable disable
#nullable restore
#line 2 "DatabaseErrorPage.cshtml"
using System.Linq;
#line default
#line hidden
#nullable disable
#nullable restore
#line 3 "DatabaseErrorPage.cshtml"
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
#line default
#line hidden
#nullable disable
#nullable restore
#line 4 "DatabaseErrorPage.cshtml"
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
#line default
#line hidden
#nullable disable
internal class DatabaseErrorPage : Microsoft.Extensions.RazorViews.BaseView
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#nullable restore
#line 5 "DatabaseErrorPage.cshtml"
Response.StatusCode = 500;
@ -37,6 +46,7 @@ using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
#line default
#line hidden
#nullable disable
WriteLiteral(@"<!DOCTYPE html>
<html lang=""en"" xmlns=""http://www.w3.org/1999/xhtml"">
@ -128,156 +138,265 @@ body .titleerror {
</head>
<body>
<h1>");
#nullable restore
#line 113 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_Title);
#line default
#line hidden
#nullable disable
WriteLiteral("</h1>\r\n <p>\r\n");
#nullable restore
#line 115 "DatabaseErrorPage.cshtml"
for (Exception ex = Model.Exception; ex != null; ex = ex.InnerException)
{
{
#line default
#line hidden
#nullable disable
WriteLiteral(" <span>");
#nullable restore
#line 117 "DatabaseErrorPage.cshtml"
Write(ex.GetType().Name);
#line default
#line hidden
#nullable disable
WriteLiteral(": ");
#nullable restore
#line 117 "DatabaseErrorPage.cshtml"
Write(ex.Message);
#line default
#line hidden
#nullable disable
WriteLiteral("</span>\r\n <br />\r\n");
#nullable restore
#line 119 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
#nullable disable
WriteLiteral(" </p>\r\n <hr />\r\n\r\n");
#nullable restore
#line 123 "DatabaseErrorPage.cshtml"
if (!Model.DatabaseExists && !Model.PendingMigrations.Any())
{
var contextWithNoDBOrMigrations = Model.ContextDetails.Where(c => !c.DatabaseExists && !c.PendingMigrations.Any());
if (contextWithNoDBOrMigrations.Any())
{
#line default
#line hidden
WriteLiteral(" <h2>");
#line 125 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_NoDbOrMigrationsTitle(Model.ContextType.Name));
#nullable disable
WriteLiteral(" <div>\r\n <h2>");
#nullable restore
#line 128 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsTitle);
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 126 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfoPMC);
#line default
#line hidden
WriteLiteral("</p>\r\n <code> ");
#line 127 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandPMC);
#line default
#line hidden
WriteLiteral("</code>\r\n <br />\r\n <code> ");
#nullable disable
WriteLiteral("</h2>\r\n <p>");
#nullable restore
#line 129 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfo);
#line default
#line hidden
WriteLiteral("</code>\r\n <p>");
#line 130 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfoCLI);
#nullable disable
WriteLiteral("</p>\r\n\r\n <ul>\r\n");
#nullable restore
#line 132 "DatabaseErrorPage.cshtml"
foreach (var context in contextWithNoDBOrMigrations)
{
#line default
#line hidden
WriteLiteral("</p>\r\n <code> ");
#line 131 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandCLI);
#nullable disable
WriteLiteral(" <li>");
#nullable restore
#line 134 "DatabaseErrorPage.cshtml"
Write(context.Type.Name);
#line default
#line hidden
WriteLiteral("</code>\r\n <br />\r\n <code> ");
#line 133 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
WriteLiteral("</code>\r\n <hr />\r\n");
#nullable disable
WriteLiteral("</li>\r\n");
#nullable restore
#line 135 "DatabaseErrorPage.cshtml"
}
else if (Model.PendingMigrations.Any())
{
}
#line default
#line hidden
WriteLiteral(" <div>\r\n <h2>");
#nullable disable
WriteLiteral(" </ul>\r\n\r\n <p>");
#nullable restore
#line 138 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfoPMC);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <code> ");
#nullable restore
#line 139 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingMigrationsTitle(Model.ContextType.Name));
Write(Strings.DatabaseErrorPage_AddMigrationCommandPMC);
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 140 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingMigrationsInfo(Model.ContextType.Name));
#nullable disable
WriteLiteral("</code>\r\n <br />\r\n <code> ");
#nullable restore
#line 141 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
#line default
#line hidden
WriteLiteral("</p>\r\n\r\n <ul>\r\n");
#nullable disable
WriteLiteral("</code>\r\n <p>");
#nullable restore
#line 142 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfoCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <code> ");
#nullable restore
#line 143 "DatabaseErrorPage.cshtml"
foreach (var migration in Model.PendingMigrations)
Write(Strings.DatabaseErrorPage_AddMigrationCommandCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <br />\r\n <code> ");
#nullable restore
#line 145 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#nullable restore
#line 148 "DatabaseErrorPage.cshtml"
}
var contextWithPendingMigrations = Model.ContextDetails.Where(c => c.PendingMigrations.Any()).Except(contextWithNoDBOrMigrations);
if (contextWithPendingMigrations.Any())
{
#line default
#line hidden
#nullable disable
WriteLiteral(" <div>\r\n <h2>");
#nullable restore
#line 154 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingMigrationsTitle);
#line default
#line hidden
#nullable disable
WriteLiteral("</h2>\r\n <p>");
#nullable restore
#line 155 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingMigrationsInfo);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n\r\n");
#nullable restore
#line 157 "DatabaseErrorPage.cshtml"
foreach (var context in contextWithPendingMigrations)
{
#line default
#line hidden
WriteLiteral(" <li>");
#line 145 "DatabaseErrorPage.cshtml"
Write(migration);
#nullable disable
WriteLiteral(" <h3>");
#nullable restore
#line 159 "DatabaseErrorPage.cshtml"
Write(context.Type.Name);
#line default
#line hidden
#nullable disable
WriteLiteral("</h3>\r\n <ul>\r\n");
#nullable restore
#line 161 "DatabaseErrorPage.cshtml"
foreach (var migration in context.PendingMigrations)
{
#line default
#line hidden
#nullable disable
WriteLiteral(" <li>");
#nullable restore
#line 163 "DatabaseErrorPage.cshtml"
Write(migration);
#line default
#line hidden
#nullable disable
WriteLiteral("</li>\r\n");
#line 146 "DatabaseErrorPage.cshtml"
#nullable restore
#line 164 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
#nullable disable
WriteLiteral(" </ul>\r\n");
WriteLiteral(" <p>\r\n <button id=\"applyMigrations\" onclick=\"ApplyMigrations()\" data-assemblyname=\"");
#nullable restore
#line 168 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(context.Type.AssemblyQualifiedName));
#line default
#line hidden
#nullable disable
WriteLiteral("\">");
#nullable restore
#line 168 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsButton);
#line default
#line hidden
#nullable disable
WriteLiteral("</button>\r\n <span id=\"applyMigrationsError\" class=\"error\"></span>\r\n <span id=\"applyMigrationsSuccess\"></span>\r\n </p>\r\n");
#nullable restore
#line 172 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral(" </ul>\r\n\r\n <p>\r\n <button id=\"applyMigrations\" onclick=\"ApplyMigrations()\">");
#line 150 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsButton);
#line default
#line hidden
WriteLiteral(@"</button>
<span id=""applyMigrationsError"" class=""error""></span>
<span id=""applyMigrationsSuccess""></span>
</p>
<script>
function ApplyMigrations() {
applyMigrations.disabled = true;
applyMigrationsError.innerHTML = """";
applyMigrations.innerHTML = """);
#line 158 "DatabaseErrorPage.cshtml"
#nullable disable
WriteLiteral("\r\n <script>\r\n function ApplyMigrations() {\r\n applyMigrations.disabled = true;\r\n applyMigrationsError.innerHTML = \"\";\r\n applyMigrations.innerHTML = \"");
#nullable restore
#line 178 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonRunning));
#line default
#line hidden
#nullable disable
WriteLiteral("\";\r\n\r\n var req = new XMLHttpRequest();\r\n\r\n req.onload = function (e) {\r\n if (req.status === 204) {\r\n applyMigrations.innerHTML = \"");
#line 164 "DatabaseErrorPage.cshtml"
#nullable restore
#line 184 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonDone));
#line default
#line hidden
#nullable disable
WriteLiteral("\";\r\n applyMigrationsSuccess.innerHTML = \"");
#line 165 "DatabaseErrorPage.cshtml"
#nullable restore
#line 185 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_MigrationsAppliedRefresh));
#line default
#line hidden
#nullable disable
WriteLiteral(@""";
} else {
ErrorApplyingMigrations();
@ -288,18 +407,15 @@ body .titleerror {
ErrorApplyingMigrations();
};
var formBody = ""context=");
#line 175 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(UrlEncode(Model.ContextType.AssemblyQualifiedName)));
#line default
#line hidden
WriteLiteral("\";\r\n req.open(\"POST\", \"");
#line 176 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Model.Options.MigrationsEndPointPath.Value));
var formBody = ""context="" + encodeURIComponent(document.getElementById('applyMigrations').getAttribute('data-assemblyname'));
req.open(""POST"", """);
#nullable restore
#line 196 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Model.PathBase.Add(Model.Options.MigrationsEndPointPath).Value));
#line default
#line hidden
#nullable disable
WriteLiteral(@""", true);
req.setRequestHeader(""Content-type"", ""application/x-www-form-urlencoded"");
req.send(formBody);
@ -307,100 +423,167 @@ body .titleerror {
function ErrorApplyingMigrations() {
applyMigrations.innerHTML = """);
#line 182 "DatabaseErrorPage.cshtml"
#nullable restore
#line 202 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButton));
#line default
#line hidden
#nullable disable
WriteLiteral("\";\r\n applyMigrationsError.innerHTML = \"");
#line 183 "DatabaseErrorPage.cshtml"
#nullable restore
#line 203 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsFailed));
#line default
#line hidden
WriteLiteral("\";\r\n applyMigrations.disabled = false;\r\n }\r\n </script>\r\n\r\n <p>");
#line 188 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_HowToApplyFromPMC);
#nullable disable
WriteLiteral("\";\r\n applyMigrations.disabled = false;\r\n }\r\n </script>\r\n\r\n <p>");
#nullable restore
#line 208 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_HowToApplyFromPMC);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 189 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
#line default
#line hidden
WriteLiteral("</code>\r\n <p>");
#line 190 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_HowToApplyFromCLI);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 191 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#line 194 "DatabaseErrorPage.cshtml"
}
else if (Model.PendingModelChanges)
{
#line default
#line hidden
WriteLiteral(" <div>\r\n <h2>");
#line 198 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingChangesTitle(Model.ContextType.Name));
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 199 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfoPMC);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 200 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandPMC);
#line default
#line hidden
WriteLiteral("</code>\r\n <br />\r\n <code>");
#line 202 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
#line default
#line hidden
WriteLiteral("</code>\r\n <p>");
#line 203 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfoCLI);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 204 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandCLI);
#line default
#line hidden
WriteLiteral("</code>\r\n <br />\r\n <code>");
#line 206 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#nullable disable
WriteLiteral("</p>\r\n <code>");
#nullable restore
#line 209 "DatabaseErrorPage.cshtml"
}
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
#line default
#line hidden
WriteLiteral("</body>\r\n</html>");
#nullable disable
WriteLiteral("</code>\r\n <p>");
#nullable restore
#line 210 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_HowToApplyFromCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <code>");
#nullable restore
#line 211 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#nullable restore
#line 214 "DatabaseErrorPage.cshtml"
}
var contextWithPendingModelChanges = Model.ContextDetails.Where(c => c.PendingModelChanges).Except(contextWithNoDBOrMigrations).Except(contextWithPendingMigrations);
if (contextWithPendingModelChanges.Any())
{
#line default
#line hidden
#nullable disable
WriteLiteral(" <div>\r\n <h2>");
#nullable restore
#line 220 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesTitle);
#line default
#line hidden
#nullable disable
WriteLiteral("</h2>\r\n <p>");
#nullable restore
#line 221 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfo);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <ul>\r\n");
#nullable restore
#line 223 "DatabaseErrorPage.cshtml"
foreach (var context in contextWithPendingModelChanges)
{
#line default
#line hidden
#nullable disable
WriteLiteral(" <li>");
#nullable restore
#line 225 "DatabaseErrorPage.cshtml"
Write(context.Type.Name);
#line default
#line hidden
#nullable disable
WriteLiteral("</li>\r\n");
#nullable restore
#line 226 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
#nullable disable
WriteLiteral(" </ul>\r\n <p>");
#nullable restore
#line 228 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfoPMC);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <code>");
#nullable restore
#line 229 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandPMC);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <br />\r\n <code>");
#nullable restore
#line 231 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <p>");
#nullable restore
#line 232 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfoCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</p>\r\n <code>");
#nullable restore
#line 233 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommandCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <br />\r\n <code>");
#nullable restore
#line 235 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI);
#line default
#line hidden
#nullable disable
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#nullable restore
#line 238 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
#nullable disable
WriteLiteral("</body>\r\n</html>\r\n");
}
#pragma warning restore 1998
#nullable restore
#line 11 "DatabaseErrorPage.cshtml"
public DatabaseErrorPageModel Model { get; set; }
@ -417,6 +600,7 @@ body .titleerror {
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591

View File

@ -35,45 +35,65 @@
<h1>@Strings.DatabaseErrorPage_Title</h1>
<p>
@for (Exception ex = Model.Exception; ex != null; ex = ex.InnerException)
{
{
<span>@ex.GetType().Name: @ex.Message</span>
<br />
}
</p>
<hr />
@if (!Model.DatabaseExists && !Model.PendingMigrations.Any())
{
<h2>@Strings.FormatDatabaseErrorPage_NoDbOrMigrationsTitle(Model.ContextType.Name)</h2>
<p>@Strings.DatabaseErrorPage_NoDbOrMigrationsInfoPMC</p>
<code> @Strings.DatabaseErrorPage_AddMigrationCommandPMC</code>
<br />
<code> @Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_NoDbOrMigrationsInfoCLI</p>
<code> @Strings.DatabaseErrorPage_AddMigrationCommandCLI</code>
<br />
<code> @Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
}
else if (Model.PendingMigrations.Any())
{
<div>
<h2>@Strings.FormatDatabaseErrorPage_PendingMigrationsTitle(Model.ContextType.Name)</h2>
<p>@Strings.FormatDatabaseErrorPage_PendingMigrationsInfo(Model.ContextType.Name)</p>
@{
var contextWithNoDBOrMigrations = Model.ContextDetails.Where(c => !c.DatabaseExists && !c.PendingMigrations.Any());
if (contextWithNoDBOrMigrations.Any())
{
<div>
<h2>@Strings.DatabaseErrorPage_NoDbOrMigrationsTitle</h2>
<p>@Strings.DatabaseErrorPage_NoDbOrMigrationsInfo</p>
<ul>
@foreach (var migration in Model.PendingMigrations)
<ul>
@foreach (var context in contextWithNoDBOrMigrations)
{
<li>@context.Type.Name</li>
}
</ul>
<p>@Strings.DatabaseErrorPage_NoDbOrMigrationsInfoPMC</p>
<code> @Strings.DatabaseErrorPage_AddMigrationCommandPMC</code>
<br />
<code> @Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_NoDbOrMigrationsInfoCLI</p>
<code> @Strings.DatabaseErrorPage_AddMigrationCommandCLI</code>
<br />
<code> @Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
</div>
}
var contextWithPendingMigrations = Model.ContextDetails.Where(c => c.PendingMigrations.Any()).Except(contextWithNoDBOrMigrations);
if (contextWithPendingMigrations.Any())
{
<div>
<h2>@Strings.DatabaseErrorPage_PendingMigrationsTitle</h2>
<p>@Strings.DatabaseErrorPage_PendingMigrationsInfo</p>
@foreach (var context in contextWithPendingMigrations)
{
<li>@migration</li>
}
</ul>
<h3>@context.Type.Name</h3>
<ul>
@foreach (var migration in context.PendingMigrations)
{
<li>@migration</li>
}
</ul>
<p>
<button id="applyMigrations" onclick="ApplyMigrations()">@Strings.DatabaseErrorPage_ApplyMigrationsButton</button>
<span id="applyMigrationsError" class="error"></span>
<span id="applyMigrationsSuccess"></span>
</p>
<script>
<p>
<button id="applyMigrations" onclick="ApplyMigrations()" data-assemblyname="@JavaScriptEncode(context.Type.AssemblyQualifiedName)">@Strings.DatabaseErrorPage_ApplyMigrationsButton</button>
<span id="applyMigrationsError" class="error"></span>
<span id="applyMigrationsSuccess"></span>
</p>
}
<script>
function ApplyMigrations() {
applyMigrations.disabled = true;
applyMigrationsError.innerHTML = "";
@ -94,8 +114,8 @@
ErrorApplyingMigrations();
};
var formBody = "context=@JavaScriptEncode(UrlEncode(Model.ContextType.AssemblyQualifiedName))";
req.open("POST", "@JavaScriptEncode(Model.Options.MigrationsEndPointPath.Value)", true);
var formBody = "context=" + encodeURIComponent(document.getElementById('applyMigrations').getAttribute('data-assemblyname'));
req.open("POST", "@JavaScriptEncode(Model.PathBase.Add(Model.Options.MigrationsEndPointPath).Value)", true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.send(formBody);
}
@ -105,29 +125,39 @@
applyMigrationsError.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsFailed)";
applyMigrations.disabled = false;
}
</script>
</script>
<p>@Strings.DatabaseErrorPage_HowToApplyFromPMC</p>
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_HowToApplyFromCLI</p>
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
</div>
}
else if (Model.PendingModelChanges)
{
<div>
<h2>@Strings.FormatDatabaseErrorPage_PendingChangesTitle(Model.ContextType.Name)</h2>
<p>@Strings.DatabaseErrorPage_PendingChangesInfoPMC</p>
<code>@Strings.DatabaseErrorPage_AddMigrationCommandPMC</code>
<br />
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_PendingChangesInfoCLI</p>
<code>@Strings.DatabaseErrorPage_AddMigrationCommandCLI</code>
<br />
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
</div>
<p>@Strings.DatabaseErrorPage_HowToApplyFromPMC</p>
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_HowToApplyFromCLI</p>
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
</div>
}
var contextWithPendingModelChanges = Model.ContextDetails.Where(c => c.PendingModelChanges).Except(contextWithNoDBOrMigrations).Except(contextWithPendingMigrations);
if (contextWithPendingModelChanges.Any())
{
<div>
<h2>@Strings.DatabaseErrorPage_PendingChangesTitle</h2>
<p>@Strings.DatabaseErrorPage_PendingChangesInfo</p>
<ul>
@foreach (var context in contextWithPendingModelChanges)
{
<li>@context.Type.Name</li>
}
</ul>
<p>@Strings.DatabaseErrorPage_PendingChangesInfoPMC</p>
<code>@Strings.DatabaseErrorPage_AddMigrationCommandPMC</code>
<br />
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandPMC</code>
<p>@Strings.DatabaseErrorPage_PendingChangesInfoCLI</p>
<code>@Strings.DatabaseErrorPage_AddMigrationCommandCLI</code>
<br />
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommandCLI</code>
<hr />
</div>
}
}
</body>
</html>
</html>

View File

@ -4,32 +4,27 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
{
internal class DatabaseErrorPageModel
{
public DatabaseErrorPageModel(
Type contextType,
Exception exception,
bool databaseExists,
bool pendingModelChanges,
IEnumerable<string> pendingMigrations,
DatabaseErrorPageOptions options)
IEnumerable<DatabaseContextDetails> contextDetails,
DatabaseErrorPageOptions options,
PathString pathBase)
{
ContextType = contextType;
Exception = exception;
DatabaseExists = databaseExists;
PendingModelChanges = pendingModelChanges;
PendingMigrations = pendingMigrations;
ContextDetails = contextDetails;
Options = options;
PathBase = pathBase;
}
public virtual Type ContextType { get; }
public virtual Exception Exception { get; }
public virtual bool DatabaseExists { get; }
public virtual bool PendingModelChanges { get; }
public virtual IEnumerable<string> PendingMigrations { get; }
public virtual IEnumerable<DatabaseContextDetails> ContextDetails { get; }
public virtual DatabaseErrorPageOptions Options { get; }
public virtual PathString PathBase { get; }
}
}
}

View File

@ -33,8 +33,10 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
{
webHostBuilder
.UseTestServer()
#pragma warning disable CS0618 // Type or member is obsolete
.Configure(app => app
.UseDatabaseErrorPage()
#pragma warning restore CS0618 // Type or member is obsolete
.UseMiddleware<SuccessMiddleware>());
}).Build();
@ -68,8 +70,10 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
{
webHostBuilder
.UseTestServer()
#pragma warning disable CS0618 // Type or member is obsolete
.Configure(app => app
.UseDatabaseErrorPage()
#pragma warning restore CS0618 // Type or member is obsolete
.UseMiddleware<ExceptionMiddleware>());
}).Build();
@ -140,7 +144,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", typeof(BloggingContext).Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsInfo"), content);
Assert.Contains(typeof(BloggingContext).Name, content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_AddMigrationCommandPMC").Replace(">", "&gt;"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_ApplyMigrationsCommandPMC").Replace(">", "&gt;"), content);
}
@ -200,7 +206,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingMigrationsTitle", typeof(BloggingContextWithMigrations).Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsInfo"), content);
Assert.Contains(typeof(BloggingContextWithMigrations).Name, content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_ApplyMigrationsCommandPMC").Replace(">", "&gt;"), content);
Assert.Contains("<li>111111111111111_MigrationOne</li>", content);
Assert.Contains("<li>222222222222222_MigrationTwo</li>", content);
@ -237,7 +245,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingChangesTitle", typeof(BloggingContextWithPendingModelChanges).Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesInfo"), content);
Assert.Contains(typeof(BloggingContextWithPendingModelChanges).Name, content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_AddMigrationCommandCLI").Replace(">", "&gt;"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_AddMigrationCommandPMC").Replace(">", "&gt;"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_ApplyMigrationsCommandCLI").Replace(">", "&gt;"), content);
@ -283,7 +293,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
// Ensure the url we're going to test is what the page is using in it's JavaScript
var javaScriptEncoder = JavaScriptEncoder.Default;
Assert.Contains("req.open(\"POST\", \"" + JavaScriptEncode(expectedMigrationsEndpoint) + "\", true);", content);
Assert.Contains("var formBody = \"context=" + JavaScriptEncode(UrlEncode(expectedContextType)) + "\";", content);
Assert.Contains("data-assemblyname=\"" + JavaScriptEncode(expectedContextType) + "\"", content);
// Step Two: Request to migrations endpoint
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
@ -333,10 +343,12 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
.UseTestServer()
.Configure(app =>
{
#pragma warning disable CS0618 // Type or member is obsolete
app.UseDatabaseErrorPage(new DatabaseErrorPageOptions
{
MigrationsEndPointPath = new PathString(migrationsEndpoint)
});
#pragma warning restore CS0618 // Type or member is obsolete
app.UseMiddleware<PendingMigrationsMiddleware>();
})
@ -374,7 +386,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
.UseTestServer()
.Configure(app =>
{
#pragma warning disable CS0618 // Type or member is obsolete
app.UseDatabaseErrorPage();
#pragma warning restore CS0618 // Type or member is obsolete
app.UseMiddleware<ContextNotRegisteredInServicesMiddleware>();
#pragma warning disable CS0618 // Type or member is obsolete
app.ApplicationServices.GetService<ILoggerFactory>().AddProvider(logProvider);
@ -478,7 +492,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("I wrapped your exception", content);
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", typeof(BloggingContext).Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsInfo"), content);
Assert.Contains(typeof(BloggingContext).Name, content);
}
}
@ -513,7 +529,9 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
.UseTestServer()
.Configure(app =>
{
#pragma warning disable CS0618 // Type or member is obsolete
app.UseDatabaseErrorPage();
#pragma warning restore CS0618 // Type or member is obsolete
app.UseMiddleware<TMiddleware>();

View File

@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_InvalidContextType", typeName), content);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_ContextNotRegistered", typeName), content);
Assert.True(content.Length > 512);
}
@ -228,7 +228,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_ContextNotRegistered", typeof(BloggingContext)), content);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_ContextNotRegistered", typeof(BloggingContext).AssemblyQualifiedName), content);
Assert.True(content.Length > 512);
}

View File

@ -1,18 +1,19 @@
// 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.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.Helpers;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Moq;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;
#nullable enable
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
{
public class DatabaseErrorPageTest
@ -23,12 +24,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -43,12 +49,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -63,12 +74,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -83,12 +99,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: true,
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: true,
pendingModelChanges: true,
pendingMigrations: new string[] { "111_MigrationOne" })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -103,12 +124,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: true,
pendingMigrations: new string[] { },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: true,
pendingModelChanges: true,
pendingMigrations: new string[] { })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -123,12 +149,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception("Something bad happened"),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { },
options: options);
new Exception("Something bad happened"),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -141,12 +172,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
var options = new DatabaseErrorPageOptions();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception("Something bad happened", new Exception("Because something more badder happened")),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { },
options: options);
new Exception("Something bad happened", new Exception("Because something more badder happened")),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
@ -161,18 +197,46 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
options.MigrationsEndPointPath = "/HitThisEndPoint";
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" })
},
options: options,
pathBase: PathString.Empty);
var content = await ExecutePage(options, model);
Assert.Contains(options.MigrationsEndPointPath.Value, content);
}
[Fact]
public async Task PathBase_is_respected()
{
var options = new DatabaseErrorPageOptions();
options.MigrationsEndPointPath = "/HitThisEndPoint";
var model = new DatabaseErrorPageModel(
new Exception(),
new DatabaseContextDetails[]
{
new DatabaseContextDetails(
type: typeof(BloggingContext),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" })
},
options: options,
pathBase: "/PathBase");
var content = await ExecutePage(options, model);
Assert.Contains("/PathBase/HitThisEndPoint", content);
}
private static async Task<string> ExecutePage(DatabaseErrorPageOptions options, DatabaseErrorPageModel model)
{
@ -196,4 +260,4 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
}
}
}
}

View File

@ -10,32 +10,38 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.Helpers
{
public static void DisplaysScaffoldFirstMigration(Type contextType, string content)
{
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", contextType.Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsInfo"), content);
}
public static void NotDisplaysScaffoldFirstMigration(Type contextType, string content)
{
Assert.DoesNotContain(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", contextType.Name), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsTitle"), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_NoDbOrMigrationsInfo"), content);
}
public static void DisplaysApplyMigrations(Type contextType, string content)
{
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingMigrationsTitle", contextType.Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsInfo"), content);
}
public static void NotDisplaysApplyMigrations(Type contextType, string content)
{
Assert.DoesNotContain(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingMigrationsTitle", contextType.Name), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsTitle"), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingMigrationsInfo"), content);
}
public static void DisplaysScaffoldNextMigraion(Type contextType, string content)
{
Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingChangesTitle", contextType.Name), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesTitle"), content);
Assert.Contains(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesInfo"), content);
}
public static void NotDisplaysScaffoldNextMigraion(Type contextType, string content)
{
Assert.DoesNotContain(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingChangesTitle", contextType.Name), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesTitle"), content);
Assert.DoesNotContain(StringsHelpers.GetResourceString("DatabaseErrorPage_PendingChangesInfo"), content);
}
}
}
}

View File

@ -12,8 +12,13 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.Helpers
public static string GetResourceString(string stringName, params object[] parameters)
{
var strings = typeof(DatabaseErrorPageMiddleware).GetTypeInfo().Assembly.GetType("Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Strings").GetTypeInfo();
var method = strings.GetDeclaredMethods(stringName).Single();
return (string)method.Invoke(null, parameters);
var method = strings.GetDeclaredMethods(stringName).SingleOrDefault();
if (method != null)
{
return (string)method.Invoke(null, parameters);
}
var property = strings.GetDeclaredProperty(stringName);
return (string)property.GetValue(null);
}
}
}
}

View File

@ -19,7 +19,9 @@ namespace DatabaseErrorPageSample
public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
#pragma warning disable CS0618 // Type or member is obsolete
app.UseDatabaseErrorPage();
#pragma warning restore CS0618 // Type or member is obsolete
app.Run(context =>
{
context.RequestServices.GetService<MyContext>().Blog.FirstOrDefault();

View File

@ -0,0 +1,216 @@
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace RazorPageGenerator
{
public class Program
{
public static int Main(string[] args)
{
if (args == null || args.Length < 1)
{
Console.WriteLine("Invalid argument(s).");
Console.WriteLine(@"Usage:
dotnet razorpagegenerator <root-namespace-of-views> [path]
Examples:
dotnet razorpagegenerator Microsoft.AspNetCore.Diagnostics.RazorViews
- processes all views in ""Views"" subfolders of the current directory
dotnet razorpagegenerator Microsoft.AspNetCore.Diagnostics.RazorViews c:\project
- processes all views in ""Views"" subfolders of c:\project directory
");
return 1;
}
var rootNamespace = args[0];
var targetProjectDirectory = args.Length > 1 ? args[1] : Directory.GetCurrentDirectory();
var projectEngine = CreateProjectEngine(rootNamespace, targetProjectDirectory);
var results = MainCore(projectEngine, targetProjectDirectory);
foreach (var result in results)
{
File.WriteAllText(result.FilePath, result.GeneratedCode);
}
Console.WriteLine();
Console.WriteLine($"{results.Count} files successfully generated.");
Console.WriteLine();
return 0;
}
public static RazorProjectEngine CreateProjectEngine(string rootNamespace, string targetProjectDirectory, Action<RazorProjectEngineBuilder> configure = null)
{
var fileSystem = RazorProjectFileSystem.Create(targetProjectDirectory);
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
{
builder
.SetNamespace(rootNamespace)
.SetBaseType("Microsoft.Extensions.RazorViews.BaseView")
.ConfigureClass((document, @class) =>
{
@class.ClassName = Path.GetFileNameWithoutExtension(document.Source.FilePath);
@class.Modifiers.Clear();
@class.Modifiers.Add("internal");
});
SectionDirective.Register(builder);
builder.Features.Add(new SuppressChecksumOptionsFeature());
builder.Features.Add(new SuppressMetadataAttributesFeature());
if (configure != null)
{
configure(builder);
}
builder.AddDefaultImports(@"
@using System
@using System.Threading.Tasks
");
});
return projectEngine;
}
public static IList<RazorPageGeneratorResult> MainCore(RazorProjectEngine projectEngine, string targetProjectDirectory)
{
var viewDirectories = Directory.EnumerateDirectories(targetProjectDirectory, "Views", SearchOption.AllDirectories);
var fileCount = 0;
var results = new List<RazorPageGeneratorResult>();
foreach (var viewDir in viewDirectories)
{
Console.WriteLine();
Console.WriteLine(" Generating code files for views in {0}", viewDir);
var viewDirPath = viewDir.Substring(targetProjectDirectory.Length).Replace('\\', '/');
var cshtmlFiles = projectEngine.FileSystem.EnumerateItems(viewDirPath);
if (!cshtmlFiles.Any())
{
Console.WriteLine(" No .cshtml files were found.");
continue;
}
foreach (var item in cshtmlFiles)
{
Console.WriteLine(" Generating code file for view {0}...", item.FileName);
results.Add(GenerateCodeFile(projectEngine, item));
Console.WriteLine(" Done!");
fileCount++;
}
}
return results;
}
private static RazorPageGeneratorResult GenerateCodeFile(RazorProjectEngine projectEngine, RazorProjectItem projectItem)
{
var projectItemWrapper = new FileSystemRazorProjectItemWrapper(projectItem);
var codeDocument = projectEngine.Process(projectItemWrapper);
var cSharpDocument = codeDocument.GetCSharpDocument();
if (cSharpDocument.Diagnostics.Any())
{
var diagnostics = string.Join(Environment.NewLine, cSharpDocument.Diagnostics);
Console.WriteLine($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}.");
}
var generatedCodeFilePath = Path.ChangeExtension(projectItem.PhysicalPath, ".Designer.cs");
return new RazorPageGeneratorResult
{
FilePath = generatedCodeFilePath,
GeneratedCode = cSharpDocument.GeneratedCode,
};
}
private class SuppressChecksumOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
options.SuppressChecksum = true;
}
}
private class SuppressMetadataAttributesFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; set; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
options.SuppressMetadataAttributes = true;
}
}
private class FileSystemRazorProjectItemWrapper : RazorProjectItem
{
private readonly RazorProjectItem _source;
public FileSystemRazorProjectItemWrapper(RazorProjectItem item)
{
_source = item;
}
public override string BasePath => _source.BasePath;
public override string FilePath => _source.FilePath;
// Mask the full name since we don't want a developer's local file paths to be commited.
public override string PhysicalPath => _source.FileName;
public override bool Exists => _source.Exists;
public override Stream Read()
{
var processedContent = ProcessFileIncludes();
return new MemoryStream(Encoding.UTF8.GetBytes(processedContent));
}
private string ProcessFileIncludes()
{
var basePath = System.IO.Path.GetDirectoryName(_source.PhysicalPath);
var cshtmlContent = File.ReadAllText(_source.PhysicalPath);
var startMatch = "<%$ include: ";
var endMatch = " %>";
var startIndex = 0;
while (startIndex < cshtmlContent.Length)
{
startIndex = cshtmlContent.IndexOf(startMatch, startIndex);
if (startIndex == -1)
{
break;
}
var endIndex = cshtmlContent.IndexOf(endMatch, startIndex);
if (endIndex == -1)
{
throw new InvalidOperationException($"Invalid include file format in {_source.PhysicalPath}. Usage example: <%$ include: ErrorPage.js %>");
}
var includeFileName = cshtmlContent.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length));
Console.WriteLine(" Inlining file {0}", includeFileName);
var includeFileContent = File.ReadAllText(System.IO.Path.Combine(basePath, includeFileName));
cshtmlContent = cshtmlContent.Substring(0, startIndex) + includeFileContent + cshtmlContent.Substring(endIndex + endMatch.Length);
startIndex = startIndex + includeFileContent.Length;
}
return cshtmlContent;
}
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Builds Razor pages for views in a project. For internal use only.</Description>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<AssemblyName>dotnet-razorpagegenerator</AssemblyName>
<PackageId>RazorPageGenerator</PackageId>
<OutputType>Exe</OutputType>
<EnableApiCheck>false</EnableApiCheck>
<IsShipping>false</IsShipping>
<ExcludeFromSourceBuild>true</ExcludeFromSourceBuild>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Razor.Language" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
// 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.
namespace RazorPageGenerator
{
public class RazorPageGeneratorResult
{
public string FilePath { get; set; }
public string GeneratedCode { get; set; }
}
}

View File

@ -103,6 +103,8 @@ namespace MusicStore
{
options.AddPolicy("ManageStore", new AuthorizationPolicyBuilder().RequireClaim("ManageStore", "Allowed").Build());
});
services.AddDatabaseDeveloperPageExceptionFilter();
}
public void Configure(IApplicationBuilder app)
@ -123,8 +125,6 @@ namespace MusicStore
// During development use the ErrorPage middleware to display error information in the browser
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
// Configure Session.
app.UseSession();

View File

@ -140,6 +140,7 @@ namespace MusicStore
options.Scope.Add("wl.signin");
});
services.AddDatabaseDeveloperPageExceptionFilter();
}
public void Configure(IApplicationBuilder app)
@ -160,8 +161,6 @@ namespace MusicStore
// Note: Not recommended for production.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
// Configure Session.
app.UseSession();

View File

@ -119,6 +119,8 @@ namespace MusicStore
options.ClientId = "000000004012C08A";
options.ClientSecret = "GaMQ2hCnqAC6EcDLnXsAeBVIJOLmeutL";
});
services.AddDatabaseDeveloperPageExceptionFilter();
}
//This method is invoked when ASPNETCORE_ENVIRONMENT is 'Development' or is not defined
@ -132,8 +134,6 @@ namespace MusicStore
// During development use the ErrorPage middleware to display error information in the browser
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
Configure(app);
}

View File

@ -94,6 +94,8 @@ namespace MusicStore
authBuilder.RequireClaim("ManageStore", "Allowed");
});
});
services.AddDatabaseDeveloperPageExceptionFilter();
}
public void Configure(IApplicationBuilder app)
@ -114,7 +116,6 @@ namespace MusicStore
// request pipeline.
// Note: Not recommended for production.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.Use((context, next) =>
{

View File

@ -102,6 +102,8 @@ namespace MusicStore
options.ClientId = "[ClientId]";
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
});
services.AddDatabaseDeveloperPageExceptionFilter();
}
public void Configure(IApplicationBuilder app)
@ -122,8 +124,6 @@ namespace MusicStore
// During development use the ErrorPage middleware to display error information in the browser
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
// Configure Session.
app.UseSession();

View File

@ -122,6 +122,7 @@ namespace BlazorServerWeb_CSharp
#endif
#if (IndividualLocalAuth)
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
#endif
services.AddSingleton<WeatherForecastService>();
}
@ -132,9 +133,6 @@ namespace BlazorServerWeb_CSharp
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{

View File

@ -49,6 +49,8 @@ namespace ComponentsWebAssembly_CSharp.Server
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
#endif
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
@ -99,9 +101,6 @@ namespace ComponentsWebAssembly_CSharp.Server
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
app.UseWebAssemblyDebugging();
}
else

View File

@ -68,6 +68,7 @@ namespace Company.WebApplication1
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
#elif (OrganizationalAuth)
@ -120,9 +121,6 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{

View File

@ -68,6 +68,8 @@ namespace Company.WebApplication1
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
#elif (OrganizationalAuth)
@ -120,9 +122,6 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{

View File

@ -42,6 +42,8 @@ namespace Company.WebApplication1
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
#endif
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
@ -70,9 +72,6 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{

View File

@ -42,6 +42,8 @@ namespace Company.WebApplication1
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
#endif
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
@ -72,9 +74,6 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{

View File

@ -64,6 +64,8 @@ namespace Identity.ExternalClaims
// Register no-op EmailSender used by account confirmation and password reset during development
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
services.AddSingleton<IEmailSender, EmailSender>();
services.AddDatabaseDeveloperPageExceptionFilter();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -72,7 +74,6 @@ namespace Identity.ExternalClaims
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{