Database Error Page: Prep for removal of ILogger-based interception.
- Removed EF ApiCheck tests; not required for middleware. - Removed JB annotations and Check usage; align with other Diagnostics Pages projects. - Fixed code redundancies.
This commit is contained in:
parent
bd921bf96d
commit
1b6993fb91
|
|
@ -1,107 +0,0 @@
|
||||||
// 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;
|
|
||||||
|
|
||||||
namespace JetBrains.Annotations
|
|
||||||
{
|
|
||||||
[AttributeUsage(
|
|
||||||
AttributeTargets.Method | AttributeTargets.Parameter |
|
|
||||||
AttributeTargets.Property | AttributeTargets.Delegate |
|
|
||||||
AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
|
||||||
internal sealed class NotNullAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(
|
|
||||||
AttributeTargets.Method | AttributeTargets.Parameter |
|
|
||||||
AttributeTargets.Property | AttributeTargets.Delegate |
|
|
||||||
AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
|
||||||
internal sealed class CanBeNullAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
|
||||||
internal sealed class InvokerParameterNameAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
|
||||||
internal sealed class NoEnumerationAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
|
||||||
internal sealed class ContractAnnotationAttribute : Attribute
|
|
||||||
{
|
|
||||||
public string Contract { get; private set; }
|
|
||||||
|
|
||||||
public bool ForceFullStates { get; private set; }
|
|
||||||
|
|
||||||
public ContractAnnotationAttribute([NotNull] string contract)
|
|
||||||
: this(contract, false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)
|
|
||||||
{
|
|
||||||
Contract = contract;
|
|
||||||
ForceFullStates = forceFullStates;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
|
|
||||||
internal sealed class UsedImplicitlyAttribute : Attribute
|
|
||||||
{
|
|
||||||
public UsedImplicitlyAttribute()
|
|
||||||
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
|
|
||||||
: this(useKindFlags, ImplicitUseTargetFlags.Default)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
|
|
||||||
: this(ImplicitUseKindFlags.Default, targetFlags)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UsedImplicitlyAttribute(
|
|
||||||
ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
|
|
||||||
{
|
|
||||||
UseKindFlags = useKindFlags;
|
|
||||||
TargetFlags = targetFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImplicitUseKindFlags UseKindFlags { get; private set; }
|
|
||||||
public ImplicitUseTargetFlags TargetFlags { get; private set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
internal enum ImplicitUseKindFlags
|
|
||||||
{
|
|
||||||
Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
|
|
||||||
Access = 1,
|
|
||||||
Assign = 2,
|
|
||||||
InstantiatedWithFixedConstructorSignature = 4,
|
|
||||||
InstantiatedNoFixedConstructorSignature = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
internal enum ImplicitUseTargetFlags
|
|
||||||
{
|
|
||||||
Default = Itself,
|
|
||||||
Itself = 1,
|
|
||||||
Members = 2,
|
|
||||||
WithMembers = Itself | Members
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Microsoft.EntityFrameworkCore.Relational.Utilities
|
|
||||||
{
|
|
||||||
internal sealed class ValidatedNotNullAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
|
@ -37,7 +36,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
_log.Value = new DataStoreErrorLog();
|
_log.Value = new DataStoreErrorLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, [CanBeNull] TState state, [CanBeNull] Exception exception, [CanBeNull] Func<TState, Exception, string> formatter)
|
public virtual void Log<TState>(
|
||||||
|
LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
{
|
{
|
||||||
if (exception != null
|
if (exception != null
|
||||||
&& LastError != null
|
&& LastError != null
|
||||||
|
|
@ -73,11 +73,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
private Type _contextType;
|
private Type _contextType;
|
||||||
private Exception _exception;
|
private Exception _exception;
|
||||||
|
|
||||||
public virtual void SetError([NotNull] Type contextType, [NotNull] Exception exception)
|
public virtual void SetError(Type contextType, Exception exception)
|
||||||
{
|
{
|
||||||
Check.NotNull(contextType, nameof(contextType));
|
|
||||||
Check.NotNull(exception, nameof(exception));
|
|
||||||
|
|
||||||
_contextType = contextType;
|
_contextType = contextType;
|
||||||
_exception = exception;
|
_exception = exception;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -35,12 +36,14 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// <param name="app">The <see cref="IApplicationBuilder"/> to register the middleware with.</param>
|
/// <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>
|
/// <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>
|
/// <returns>The same <see cref="IApplicationBuilder"/> instance so that multiple calls can be chained.</returns>
|
||||||
public static IApplicationBuilder UseDatabaseErrorPage(this IApplicationBuilder app, DatabaseErrorPageOptions options)
|
public static IApplicationBuilder UseDatabaseErrorPage(
|
||||||
|
this IApplicationBuilder app, DatabaseErrorPageOptions options)
|
||||||
{
|
{
|
||||||
if (app == null)
|
if (app == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(app));
|
throw new ArgumentNullException(nameof(app));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
|
@ -56,4 +59,4 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
|
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
|
@ -26,7 +24,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly DatabaseErrorPageOptions _options;
|
private readonly DatabaseErrorPageOptions _options;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly DataStoreErrorLoggerProvider _loggerProvider;
|
private readonly DataStoreErrorLoggerProvider _loggerProvider;
|
||||||
|
|
||||||
|
|
@ -34,21 +31,32 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
/// Initializes a new instance of the <see cref="DatabaseErrorPageMiddleware"/> class
|
/// Initializes a new instance of the <see cref="DatabaseErrorPageMiddleware"/> class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">Delegate to execute the next piece of middleware in the request pipeline.</param>
|
/// <param name="next">Delegate to execute the next piece of middleware in the request pipeline.</param>
|
||||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to resolve services from.</param>
|
|
||||||
/// <param name="loggerFactory">
|
/// <param name="loggerFactory">
|
||||||
/// The <see cref="ILoggerFactory"/> for the application. This middleware both produces logging messages and
|
/// The <see cref="ILoggerFactory"/> for the application. This middleware both produces logging messages and
|
||||||
/// consumes them to detect database related exception.
|
/// consumes them to detect database related exception.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="options">The options to control what information is displayed on the error page.</param>
|
/// <param name="options">The options to control what information is displayed on the error page.</param>
|
||||||
public DatabaseErrorPageMiddleware([NotNull] RequestDelegate next, [NotNull] IServiceProvider serviceProvider, [NotNull] ILoggerFactory loggerFactory, [NotNull] IOptions<DatabaseErrorPageOptions> options)
|
public DatabaseErrorPageMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IOptions<DatabaseErrorPageOptions> options)
|
||||||
{
|
{
|
||||||
Check.NotNull(next, nameof(next));
|
if (next == null)
|
||||||
Check.NotNull(serviceProvider, nameof(serviceProvider));
|
{
|
||||||
Check.NotNull(loggerFactory, nameof(loggerFactory));
|
throw new ArgumentNullException(nameof(next));
|
||||||
Check.NotNull(options, nameof(options));
|
}
|
||||||
|
|
||||||
|
if (loggerFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(loggerFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = loggerFactory.CreateLogger<DatabaseErrorPageMiddleware>();
|
_logger = loggerFactory.CreateLogger<DatabaseErrorPageMiddleware>();
|
||||||
|
|
||||||
|
|
@ -63,9 +71,12 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The context for the current request.</param>
|
/// <param name="context">The context for the current request.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
public virtual async Task Invoke([NotNull] HttpContext context)
|
public virtual async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
Check.NotNull(context, "context");
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -80,14 +91,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
if (ShouldDisplayErrorPage(_loggerProvider.Logger.LastError, ex, _logger))
|
if (ShouldDisplayErrorPage(_loggerProvider.Logger.LastError, ex, _logger))
|
||||||
{
|
{
|
||||||
var dbContextType = _loggerProvider.Logger.LastError.ContextType;
|
var dbContextType = _loggerProvider.Logger.LastError.ContextType;
|
||||||
var dbContext = (DbContext)context.RequestServices.GetService(dbContextType);
|
var dbContext = (DbContext) context.RequestServices.GetService(dbContextType);
|
||||||
|
|
||||||
if (dbContext == null)
|
if (dbContext == null)
|
||||||
{
|
{
|
||||||
_logger.LogError(Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName));
|
_logger.LogError(
|
||||||
|
Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var creator = dbContext.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
|
var creator = dbContext.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
|
||||||
|
|
||||||
if (creator == null)
|
if (creator == null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(Strings.DatabaseErrorPage_NotRelationalDatabase);
|
_logger.LogDebug(Strings.DatabaseErrorPage_NotRelationalDatabase);
|
||||||
|
|
@ -101,25 +115,36 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
var modelDiffer = dbContext.GetService<IMigrationsModelDiffer>();
|
var modelDiffer = dbContext.GetService<IMigrationsModelDiffer>();
|
||||||
|
|
||||||
var appliedMigrations = historyRepository.GetAppliedMigrations();
|
var appliedMigrations = historyRepository.GetAppliedMigrations();
|
||||||
var pendingMigrations = (
|
|
||||||
from m in migrationsAssembly.Migrations
|
var pendingMigrations
|
||||||
|
= (from m in migrationsAssembly.Migrations
|
||||||
where !appliedMigrations.Any(
|
where !appliedMigrations.Any(
|
||||||
r => string.Equals(r.MigrationId, m.Key, StringComparison.OrdinalIgnoreCase))
|
r => string.Equals(r.MigrationId, m.Key,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
select m.Key)
|
select m.Key)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// HasDifferences will return true if there is no model snapshot, but if there is an existing database
|
// 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 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
|
// and existing database and have just misconfigured their model
|
||||||
var pendingModelChanges = migrationsAssembly.ModelSnapshot == null && databaseExists
|
var pendingModelChanges
|
||||||
? false
|
= (migrationsAssembly.ModelSnapshot != null || !databaseExists)
|
||||||
: modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, dbContext.Model);
|
&& modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model,
|
||||||
|
dbContext.Model);
|
||||||
|
|
||||||
if ((!databaseExists && pendingMigrations.Any()) || pendingMigrations.Any() || pendingModelChanges)
|
if (!databaseExists && pendingMigrations.Any()
|
||||||
|
|| pendingMigrations.Any()
|
||||||
|
|| pendingModelChanges)
|
||||||
{
|
{
|
||||||
var page = new DatabaseErrorPage();
|
var page = new DatabaseErrorPage
|
||||||
page.Model = new DatabaseErrorPageModel(dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations, _options);
|
{
|
||||||
|
Model = new DatabaseErrorPageModel(
|
||||||
|
dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations,
|
||||||
|
_options)
|
||||||
|
};
|
||||||
|
|
||||||
await page.ExecuteAsync(context);
|
await page.ExecuteAsync(context);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +160,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldDisplayErrorPage(DataStoreErrorLogger.DataStoreErrorLog lastError, Exception exception, ILogger logger)
|
private static bool ShouldDisplayErrorPage(DataStoreErrorLogger.DataStoreErrorLog lastError,
|
||||||
|
Exception exception, ILogger logger)
|
||||||
{
|
{
|
||||||
logger.LogDebug(Strings.FormatDatabaseErrorPage_AttemptingToMatchException(exception.GetType()));
|
logger.LogDebug(Strings.FormatDatabaseErrorPage_AttemptingToMatchException(exception.GetType()));
|
||||||
|
|
||||||
|
|
@ -145,7 +171,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool match = false;
|
var match = false;
|
||||||
|
|
||||||
for (var e = exception; e != null && !match; e = e.InnerException)
|
for (var e = exception; e != null && !match; e = e.InnerException)
|
||||||
{
|
{
|
||||||
match = lastError.Exception == e;
|
match = lastError.Exception == e;
|
||||||
|
|
@ -154,11 +181,13 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
if (!match)
|
if (!match)
|
||||||
{
|
{
|
||||||
logger.LogDebug(Strings.DatabaseErrorPage_NoMatch);
|
logger.LogDebug(Strings.DatabaseErrorPage_NoMatch);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug(Strings.DatabaseErrorPage_Matched);
|
logger.LogDebug(Strings.DatabaseErrorPage_Matched);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -20,7 +18,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
public class MigrationsEndPointMiddleware
|
public class MigrationsEndPointMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly MigrationsEndPointOptions _options;
|
private readonly MigrationsEndPointOptions _options;
|
||||||
|
|
||||||
|
|
@ -28,22 +25,29 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
/// Initializes a new instance of the <see cref="MigrationsEndPointMiddleware"/> class
|
/// Initializes a new instance of the <see cref="MigrationsEndPointMiddleware"/> class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">Delegate to execute the next piece of middleware in the request pipeline.</param>
|
/// <param name="next">Delegate to execute the next piece of middleware in the request pipeline.</param>
|
||||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to resolve services from.</param>
|
|
||||||
/// <param name="logger">The <see cref="Logger{T}"/> to write messages to.</param>
|
/// <param name="logger">The <see cref="Logger{T}"/> to write messages to.</param>
|
||||||
/// <param name="options">The options to control the behavior of the middleware.</param>
|
/// <param name="options">The options to control the behavior of the middleware.</param>
|
||||||
public MigrationsEndPointMiddleware(
|
public MigrationsEndPointMiddleware(
|
||||||
[NotNull] RequestDelegate next,
|
RequestDelegate next,
|
||||||
[NotNull] IServiceProvider serviceProvider,
|
ILogger<MigrationsEndPointMiddleware> logger,
|
||||||
[NotNull] ILogger<MigrationsEndPointMiddleware> logger,
|
IOptions<MigrationsEndPointOptions> options)
|
||||||
[NotNull] IOptions<MigrationsEndPointOptions> options)
|
|
||||||
{
|
{
|
||||||
Check.NotNull(next, "next");
|
if (next == null)
|
||||||
Check.NotNull(serviceProvider, "serviceProvider");
|
{
|
||||||
Check.NotNull(logger, "logger");
|
throw new ArgumentNullException(nameof(next));
|
||||||
Check.NotNull(options, "options");
|
}
|
||||||
|
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
@ -53,15 +57,19 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The context for the current request.</param>
|
/// <param name="context">The context for the current request.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
public virtual async Task Invoke([NotNull] HttpContext context)
|
public virtual async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
Check.NotNull(context, "context");
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Request.Path.Equals(_options.Path))
|
if (context.Request.Path.Equals(_options.Path))
|
||||||
{
|
{
|
||||||
_logger.LogDebug(Strings.FormatMigrationsEndPointMiddleware_RequestPathMatched(context.Request.Path));
|
_logger.LogDebug(Strings.FormatMigrationsEndPointMiddleware_RequestPathMatched(context.Request.Path));
|
||||||
|
|
||||||
var db = await GetDbContext(context, _logger);
|
var db = await GetDbContext(context, _logger);
|
||||||
|
|
||||||
if (db != null)
|
if (db != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -78,8 +86,10 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var message = Strings.FormatMigrationsEndPointMiddleware_Exception(db.GetType().FullName) + ex.ToString();
|
var message = Strings.FormatMigrationsEndPointMiddleware_Exception(db.GetType().FullName) + ex;
|
||||||
|
|
||||||
_logger.LogError(message);
|
_logger.LogError(message);
|
||||||
|
|
||||||
throw new InvalidOperationException(message, ex);
|
throw new InvalidOperationException(message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,28 +104,39 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
{
|
{
|
||||||
var form = await context.Request.ReadFormAsync();
|
var form = await context.Request.ReadFormAsync();
|
||||||
var contextTypeName = form["context"];
|
var contextTypeName = form["context"];
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(contextTypeName))
|
if (string.IsNullOrWhiteSpace(contextTypeName))
|
||||||
{
|
{
|
||||||
logger.LogError(Strings.MigrationsEndPointMiddleware_NoContextType);
|
logger.LogError(Strings.MigrationsEndPointMiddleware_NoContextType);
|
||||||
|
|
||||||
await WriteErrorToResponse(context.Response, Strings.MigrationsEndPointMiddleware_NoContextType);
|
await WriteErrorToResponse(context.Response, Strings.MigrationsEndPointMiddleware_NoContextType);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextType = Type.GetType(contextTypeName);
|
var contextType = Type.GetType(contextTypeName);
|
||||||
|
|
||||||
if (contextType == null)
|
if (contextType == null)
|
||||||
{
|
{
|
||||||
var message = Strings.FormatMigrationsEndPointMiddleware_InvalidContextType(contextTypeName);
|
var message = Strings.FormatMigrationsEndPointMiddleware_InvalidContextType(contextTypeName);
|
||||||
|
|
||||||
logger.LogError(message);
|
logger.LogError(message);
|
||||||
|
|
||||||
await WriteErrorToResponse(context.Response, message);
|
await WriteErrorToResponse(context.Response, message);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var db = (DbContext)context.RequestServices.GetService(contextType);
|
var db = (DbContext)context.RequestServices.GetService(contextType);
|
||||||
|
|
||||||
if (db == null)
|
if (db == null)
|
||||||
{
|
{
|
||||||
var message = Strings.FormatMigrationsEndPointMiddleware_ContextNotRegistered(contextType.FullName);
|
var message = Strings.FormatMigrationsEndPointMiddleware_ContextNotRegistered(contextType.FullName);
|
||||||
|
|
||||||
logger.LogError(message);
|
logger.LogError(message);
|
||||||
|
|
||||||
await WriteErrorToResponse(context.Response, message);
|
await WriteErrorToResponse(context.Response, message);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,38 +10,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
private static readonly ResourceManager _resourceManager
|
private static readonly ResourceManager _resourceManager
|
||||||
= new ResourceManager("Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Strings", typeof(Strings).GetTypeInfo().Assembly);
|
= new ResourceManager("Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Strings", typeof(Strings).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The string argument '{argumentName}' cannot be empty.
|
|
||||||
/// </summary>
|
|
||||||
internal static string ArgumentIsEmpty
|
|
||||||
{
|
|
||||||
get { return GetString("ArgumentIsEmpty"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The string argument '{argumentName}' cannot be empty.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatArgumentIsEmpty(object argumentName)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentIsEmpty", "argumentName"), argumentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The collection argument '{argumentName}' must contain at least one element.
|
|
||||||
/// </summary>
|
|
||||||
internal static string CollectionArgumentIsEmpty
|
|
||||||
{
|
|
||||||
get { return GetString("CollectionArgumentIsEmpty"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The collection argument '{argumentName}' must contain at least one element.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatCollectionArgumentIsEmpty(object argumentName)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("CollectionArgumentIsEmpty", "argumentName"), argumentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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<{0}>() inside the UseServices(...) call in your application startup code. Skipping display of the database error page.
|
/// 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<{0}>() inside the UseServices(...) call in your application startup code. Skipping display of the database error page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -298,22 +266,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
return GetString("DatabaseErrorPage_ApplyMigrationsCommandCLI");
|
return GetString("DatabaseErrorPage_ApplyMigrationsCommandCLI");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'.
|
|
||||||
/// </summary>
|
|
||||||
internal static string InvalidEnumValue
|
|
||||||
{
|
|
||||||
get { return GetString("InvalidEnumValue"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatInvalidEnumValue(object argumentName, object enumType)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidEnumValue", "argumentName", "enumType"), argumentName, enumType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Migrations successfully applied for context '{0}'.
|
/// Migrations successfully applied for context '{0}'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -117,12 +117,6 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="ArgumentIsEmpty" xml:space="preserve">
|
|
||||||
<value>The string argument '{argumentName}' cannot be empty.</value>
|
|
||||||
</data>
|
|
||||||
<data name="CollectionArgumentIsEmpty" xml:space="preserve">
|
|
||||||
<value>The collection argument '{argumentName}' must contain at least one element.</value>
|
|
||||||
</data>
|
|
||||||
<data name="DatabaseErrorPageMiddleware_ContextNotRegistered" xml:space="preserve">
|
<data name="DatabaseErrorPageMiddleware_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<{0}>() inside the UseServices(...) call in your application startup code. Skipping display of the database error page.</value>
|
<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<{0}>() inside the UseServices(...) call in your application startup code. Skipping display of the database error page.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
@ -171,9 +165,6 @@
|
||||||
<data name="DatabaseErrorPage_ApplyMigrationsCommandCLI" xml:space="preserve">
|
<data name="DatabaseErrorPage_ApplyMigrationsCommandCLI" xml:space="preserve">
|
||||||
<value>> dotnet ef database update</value>
|
<value>> dotnet ef database update</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidEnumValue" xml:space="preserve">
|
|
||||||
<value>The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'.</value>
|
|
||||||
</data>
|
|
||||||
<data name="MigrationsEndPointMiddleware_Applied" xml:space="preserve">
|
<data name="MigrationsEndPointMiddleware_Applied" xml:space="preserve">
|
||||||
<value>Migrations successfully applied for context '{0}'.</value>
|
<value>Migrations successfully applied for context '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
// 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.Diagnostics;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities
|
|
||||||
{
|
|
||||||
[DebuggerStepThrough]
|
|
||||||
internal static class Check
|
|
||||||
{
|
|
||||||
[ContractAnnotation("value:null => halt")]
|
|
||||||
public static T NotNull<T>([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName)
|
|
||||||
{
|
|
||||||
NotEmpty(parameterName, nameof(parameterName));
|
|
||||||
|
|
||||||
if (ReferenceEquals(value, null))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(parameterName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ContractAnnotation("value:null => halt")]
|
|
||||||
public static IReadOnlyList<T> NotEmpty<T>(IReadOnlyList<T> value, [InvokerParameterName] [NotNull] string parameterName)
|
|
||||||
{
|
|
||||||
NotEmpty(parameterName, nameof(parameterName));
|
|
||||||
NotNull(value, parameterName);
|
|
||||||
|
|
||||||
if (value.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Strings.FormatCollectionArgumentIsEmpty(parameterName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ContractAnnotation("value:null => halt")]
|
|
||||||
public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(parameterName, null))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(parameterName));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameterName.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Strings.FormatArgumentIsEmpty("parameterName"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReferenceEquals(value, null))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(parameterName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Strings.FormatArgumentIsEmpty(parameterName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T IsDefined<T>(T value, [InvokerParameterName] [NotNull] string parameterName)
|
|
||||||
where T : struct
|
|
||||||
{
|
|
||||||
NotEmpty(parameterName, nameof(parameterName));
|
|
||||||
|
|
||||||
if (!Enum.IsDefined(typeof(T), value))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Strings.FormatInvalidEnumValue(parameterName, typeof(T)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
|
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
|
||||||
{
|
{
|
||||||
#line 1 "DatabaseErrorPage.cshtml"
|
#line 1 "DatabaseErrorPage.cshtml"
|
||||||
using System
|
using System
|
||||||
|
|
@ -19,7 +19,7 @@ using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
#line hidden
|
#line hidden
|
||||||
;
|
;
|
||||||
#line 4 "DatabaseErrorPage.cshtml"
|
#line 4 "DatabaseErrorPage.cshtml"
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
|
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
|
||||||
|
|
||||||
#line default
|
#line default
|
||||||
#line hidden
|
#line hidden
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
@using System
|
@using System
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
|
||||||
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
|
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
|
||||||
@{
|
@{
|
||||||
Response.StatusCode = 500;
|
Response.StatusCode = 500;
|
||||||
Response.ContentType = "text/html; charset=utf-8";
|
Response.ContentType = "text/html; charset=utf-8";
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,35 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
|
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
|
||||||
{
|
{
|
||||||
internal class DatabaseErrorPageModel
|
internal class DatabaseErrorPageModel
|
||||||
{
|
{
|
||||||
private readonly Type _contextType;
|
|
||||||
private readonly Exception _exception;
|
|
||||||
private readonly bool _databaseExists;
|
|
||||||
private readonly bool _pendingModelChanges;
|
|
||||||
private readonly IEnumerable<string> _pendingMigrations;
|
|
||||||
private readonly DatabaseErrorPageOptions _options;
|
|
||||||
|
|
||||||
public DatabaseErrorPageModel(
|
public DatabaseErrorPageModel(
|
||||||
[NotNull] Type contextType,
|
Type contextType,
|
||||||
[NotNull] Exception exception,
|
Exception exception,
|
||||||
bool databaseExists,
|
bool databaseExists,
|
||||||
bool pendingModelChanges,
|
bool pendingModelChanges,
|
||||||
[NotNull] IEnumerable<string> pendingMigrations,
|
IEnumerable<string> pendingMigrations,
|
||||||
[NotNull] DatabaseErrorPageOptions options)
|
DatabaseErrorPageOptions options)
|
||||||
{
|
{
|
||||||
Check.NotNull(contextType, "contextType");
|
ContextType = contextType;
|
||||||
Check.NotNull(exception, "exception");
|
Exception = exception;
|
||||||
Check.NotNull(pendingMigrations, "pendingMigrations");
|
DatabaseExists = databaseExists;
|
||||||
Check.NotNull(options, "options");
|
PendingModelChanges = pendingModelChanges;
|
||||||
|
PendingMigrations = pendingMigrations;
|
||||||
_contextType = contextType;
|
Options = options;
|
||||||
_exception = exception;
|
|
||||||
_databaseExists = databaseExists;
|
|
||||||
_pendingModelChanges = pendingModelChanges;
|
|
||||||
_pendingMigrations = pendingMigrations;
|
|
||||||
_options = options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Type ContextType
|
public virtual Type ContextType { get; }
|
||||||
{
|
public virtual Exception Exception { get; }
|
||||||
get { return _contextType; }
|
public virtual bool DatabaseExists { get; }
|
||||||
}
|
public virtual bool PendingModelChanges { get; }
|
||||||
|
public virtual IEnumerable<string> PendingMigrations { get; }
|
||||||
public virtual Exception Exception
|
public virtual DatabaseErrorPageOptions Options { get; }
|
||||||
{
|
|
||||||
get { return _exception; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool DatabaseExists
|
|
||||||
{
|
|
||||||
get { return _databaseExists; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool PendingModelChanges
|
|
||||||
{
|
|
||||||
get { return _pendingModelChanges; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IEnumerable<string> PendingMigrations
|
|
||||||
{
|
|
||||||
get { return _pendingMigrations; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual DatabaseErrorPageOptions Options
|
|
||||||
{
|
|
||||||
get { return _options; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,5 +6,15 @@
|
||||||
{
|
{
|
||||||
"TypeId": "public class Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views.DatabaseErrorPageModel",
|
"TypeId": "public class Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views.DatabaseErrorPageModel",
|
||||||
"Kind": "Removal"
|
"Kind": "Removal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeId": "public class Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware",
|
||||||
|
"MemberId": "public .ctor(Microsoft.AspNetCore.Http.RequestDelegate next, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware> logger, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.MigrationsEndPointOptions> options)",
|
||||||
|
"Kind": "Removal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeId": "public class Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware",
|
||||||
|
"MemberId": "public .ctor(Microsoft.AspNetCore.Http.RequestDelegate next, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.DatabaseErrorPageOptions> options)",
|
||||||
|
"Kind": "Removal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// 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.AspNetCore.Diagnostics.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCoreTests
|
|
||||||
{
|
|
||||||
public class ApiConsistencyTest : ApiConsistencyTestBase
|
|
||||||
{
|
|
||||||
protected override Assembly TargetAssembly => typeof(DatabaseErrorPageMiddleware).GetTypeInfo().Assembly;
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetCancellationTokenExceptions()
|
|
||||||
{
|
|
||||||
return new string[]
|
|
||||||
{
|
|
||||||
"DatabaseErrorPageMiddleware.Invoke",
|
|
||||||
"MigrationsEndPointMiddleware.Invoke",
|
|
||||||
"DatabaseErrorPage.ExecuteAsync",
|
|
||||||
"BaseView.ExecuteAsync"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetAsyncSuffixExceptions()
|
|
||||||
{
|
|
||||||
return new string[]
|
|
||||||
{
|
|
||||||
"DatabaseErrorPageMiddleware.Invoke",
|
|
||||||
"MigrationsEndPointMiddleware.Invoke"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.EntityFrameworkCore
|
|
||||||
{
|
|
||||||
public abstract class ApiConsistencyTestBase
|
|
||||||
{
|
|
||||||
private static readonly HashSet<TypeInfo> _typesToSkip = new HashSet<TypeInfo>
|
|
||||||
{
|
|
||||||
typeof(DatabaseErrorPage).GetTypeInfo()
|
|
||||||
};
|
|
||||||
|
|
||||||
protected const BindingFlags PublicInstance
|
|
||||||
= BindingFlags.Instance | BindingFlags.Public;
|
|
||||||
|
|
||||||
protected const BindingFlags AnyInstance
|
|
||||||
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
|
||||||
|
|
||||||
[ConditionalFact(Skip = "Skipping until we update to mono")]
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public void Public_inheritable_apis_should_be_virtual()
|
|
||||||
{
|
|
||||||
var nonVirtualMethods
|
|
||||||
= (from type in GetAllTypes(TargetAssembly.DefinedTypes)
|
|
||||||
where type.IsVisible
|
|
||||||
&& !type.IsSealed
|
|
||||||
&& type.DeclaredConstructors.Any(c => c.IsPublic || c.IsFamily || c.IsFamilyOrAssembly)
|
|
||||||
&& type.Namespace != null
|
|
||||||
&& !type.Namespace.EndsWith(".Compiled")
|
|
||||||
&& !_typesToSkip.Contains(type)
|
|
||||||
from method in type.DeclaredMethods.Where(m => m.IsPublic && !m.IsStatic)
|
|
||||||
where method.DeclaringType.GetTypeInfo() == type
|
|
||||||
&& !(method.IsVirtual && !method.IsFinal)
|
|
||||||
select type.FullName + "." + method.Name)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Assert.False(
|
|
||||||
nonVirtualMethods.Any(),
|
|
||||||
"\r\n-- Missing virtual APIs --\r\n" + string.Join("\r\n", nonVirtualMethods));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact(Skip = "Skipping until we update to mono")]
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public void Public_api_arguments_should_have_not_null_annotation()
|
|
||||||
{
|
|
||||||
var parametersMissingAttribute
|
|
||||||
= (from type in GetAllTypes(TargetAssembly.DefinedTypes)
|
|
||||||
where type.IsVisible && !typeof(Delegate).GetTypeInfo().IsAssignableFrom(type) && !_typesToSkip.Contains(type)
|
|
||||||
let interfaceMappings = type.ImplementedInterfaces.Select(type.GetRuntimeInterfaceMap)
|
|
||||||
let events = type.DeclaredEvents
|
|
||||||
from method in type.DeclaredMethods.Where(m => m.IsPublic)
|
|
||||||
.Concat<MethodBase>(type.DeclaredConstructors)
|
|
||||||
where method.DeclaringType.GetTypeInfo() == type
|
|
||||||
where type.IsInterface || !interfaceMappings.Any(im => im.TargetMethods.Contains(method))
|
|
||||||
where !events.Any(e => e.AddMethod == method || e.RemoveMethod == method)
|
|
||||||
from parameter in method.GetParameters()
|
|
||||||
where !parameter.ParameterType.GetTypeInfo().IsValueType
|
|
||||||
&& !parameter.GetCustomAttributes()
|
|
||||||
.Any(
|
|
||||||
a => a.GetType().Name == "NotNullAttribute"
|
|
||||||
|| a.GetType().Name == "CanBeNullAttribute")
|
|
||||||
select type.FullName + "." + method.Name + "[" + parameter.Name + "]")
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Assert.False(
|
|
||||||
parametersMissingAttribute.Any(),
|
|
||||||
"\r\n-- Missing NotNull annotations --\r\n" + string.Join("\r\n", parametersMissingAttribute));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact(Skip = "Skipping until we update to mono")]
|
|
||||||
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
|
|
||||||
public void Async_methods_should_have_overload_with_cancellation_token_and_end_with_async_suffix()
|
|
||||||
{
|
|
||||||
var asyncMethods
|
|
||||||
= (from type in GetAllTypes(TargetAssembly.DefinedTypes)
|
|
||||||
where type.IsVisible
|
|
||||||
from method in type.DeclaredMethods.Where(m => m.IsPublic)
|
|
||||||
where method.DeclaringType.GetTypeInfo() == type
|
|
||||||
where typeof(Task).IsAssignableFrom(method.ReturnType)
|
|
||||||
select method).ToList();
|
|
||||||
|
|
||||||
var asyncMethodsWithToken
|
|
||||||
= (from method in asyncMethods
|
|
||||||
where method.GetParameters().Any(pi => pi.ParameterType == typeof(CancellationToken))
|
|
||||||
select method).ToList();
|
|
||||||
|
|
||||||
var asyncMethodsWithoutToken
|
|
||||||
= (from method in asyncMethods
|
|
||||||
where method.GetParameters().All(pi => pi.ParameterType != typeof(CancellationToken))
|
|
||||||
select method).ToList();
|
|
||||||
|
|
||||||
var missingOverloads
|
|
||||||
= (from methodWithoutToken in asyncMethodsWithoutToken
|
|
||||||
where !asyncMethodsWithToken
|
|
||||||
.Any(methodWithToken => methodWithoutToken.Name == methodWithToken.Name
|
|
||||||
&& methodWithoutToken.DeclaringType == methodWithToken.DeclaringType)
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
|
||||||
select methodWithoutToken.DeclaringType.Name + "." + methodWithoutToken.Name)
|
|
||||||
.Except(GetCancellationTokenExceptions())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Assert.False(
|
|
||||||
missingOverloads.Any(),
|
|
||||||
"\r\n-- Missing async overloads --\r\n" + string.Join("\r\n", missingOverloads));
|
|
||||||
|
|
||||||
var missingSuffixMethods
|
|
||||||
= asyncMethods
|
|
||||||
.Where(method => !method.Name.EndsWith("Async"))
|
|
||||||
.Select(method => method.DeclaringType.Name + "." + method.Name)
|
|
||||||
.Except(GetAsyncSuffixExceptions())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Assert.False(
|
|
||||||
missingSuffixMethods.Any(),
|
|
||||||
"\r\n-- Missing async suffix --\r\n" + string.Join("\r\n", missingSuffixMethods));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual IEnumerable<string> GetCancellationTokenExceptions()
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual IEnumerable<string> GetAsyncSuffixExceptions()
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Assembly TargetAssembly { get; }
|
|
||||||
|
|
||||||
protected virtual IEnumerable<TypeInfo> GetAllTypes(IEnumerable<TypeInfo> typeInfos)
|
|
||||||
{
|
|
||||||
foreach (var typeInfo in typeInfos)
|
|
||||||
{
|
|
||||||
yield return typeInfo;
|
|
||||||
|
|
||||||
foreach (var nestedType in GetAllTypes(typeInfo.DeclaredNestedTypes))
|
|
||||||
{
|
|
||||||
yield return nestedType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.Helpers;
|
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests.Helpers;
|
||||||
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews;
|
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
|
||||||
exception: new Exception(),
|
exception: new Exception(),
|
||||||
databaseExists: false,
|
databaseExists: false,
|
||||||
pendingModelChanges: false,
|
pendingModelChanges: false,
|
||||||
pendingMigrations: new string[] { "111_MigrationOne" },
|
pendingMigrations: new[] { "111_MigrationOne" },
|
||||||
options: options);
|
options: options);
|
||||||
|
|
||||||
var content = await ExecutePage(options, model);
|
var content = await ExecutePage(options, model);
|
||||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
|
||||||
exception: new Exception(),
|
exception: new Exception(),
|
||||||
databaseExists: true,
|
databaseExists: true,
|
||||||
pendingModelChanges: false,
|
pendingModelChanges: false,
|
||||||
pendingMigrations: new string[] { "111_MigrationOne" },
|
pendingMigrations: new[] { "111_MigrationOne" },
|
||||||
options: options);
|
options: options);
|
||||||
|
|
||||||
var content = await ExecutePage(options, model);
|
var content = await ExecutePage(options, model);
|
||||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
|
||||||
exception: new Exception(),
|
exception: new Exception(),
|
||||||
databaseExists: true,
|
databaseExists: true,
|
||||||
pendingModelChanges: true,
|
pendingModelChanges: true,
|
||||||
pendingMigrations: new string[] { "111_MigrationOne" },
|
pendingMigrations: new[] { "111_MigrationOne" },
|
||||||
options: options);
|
options: options);
|
||||||
|
|
||||||
var content = await ExecutePage(options, model);
|
var content = await ExecutePage(options, model);
|
||||||
|
|
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
|
||||||
exception: new Exception(),
|
exception: new Exception(),
|
||||||
databaseExists: true,
|
databaseExists: true,
|
||||||
pendingModelChanges: false,
|
pendingModelChanges: false,
|
||||||
pendingMigrations: new string[] { "111_MigrationOne" },
|
pendingMigrations: new[] { "111_MigrationOne" },
|
||||||
options: options);
|
options: options);
|
||||||
|
|
||||||
var content = await ExecutePage(options, model);
|
var content = await ExecutePage(options, model);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue