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:
Andrew Peters 2017-05-30 12:43:27 -07:00
parent bd921bf96d
commit 1b6993fb91
15 changed files with 138 additions and 547 deletions

View File

@ -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
{
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -37,7 +36,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
_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
&& LastError != null
@ -73,11 +73,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
private Type _contextType;
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;
_exception = exception;
}

View File

@ -5,6 +5,7 @@ using System;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
using Microsoft.Extensions.Options;
// ReSharper disable once CheckNamespace
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
@ -35,12 +36,14 @@ 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>
public static IApplicationBuilder UseDatabaseErrorPage(this IApplicationBuilder app, DatabaseErrorPageOptions options)
public static IApplicationBuilder UseDatabaseErrorPage(
this IApplicationBuilder app, DatabaseErrorPageOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
@ -56,4 +59,4 @@ namespace Microsoft.AspNetCore.Builder
return app;
}
}
}
}

View File

@ -4,10 +4,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -26,7 +24,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
private readonly RequestDelegate _next;
private readonly DatabaseErrorPageOptions _options;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly DataStoreErrorLoggerProvider _loggerProvider;
@ -34,21 +31,32 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
/// Initializes a new instance of the <see cref="DatabaseErrorPageMiddleware"/> class
/// </summary>
/// <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">
/// The <see cref="ILoggerFactory"/> for the application. This middleware both produces logging messages and
/// consumes them to detect database related exception.
/// </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));
Check.NotNull(serviceProvider, nameof(serviceProvider));
Check.NotNull(loggerFactory, nameof(loggerFactory));
Check.NotNull(options, nameof(options));
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next;
_serviceProvider = serviceProvider;
_options = options.Value;
_logger = loggerFactory.CreateLogger<DatabaseErrorPageMiddleware>();
@ -63,9 +71,12 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
/// </summary>
/// <param name="context">The context for the current request.</param>
/// <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
{
@ -80,14 +91,17 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
if (ShouldDisplayErrorPage(_loggerProvider.Logger.LastError, ex, _logger))
{
var dbContextType = _loggerProvider.Logger.LastError.ContextType;
var dbContext = (DbContext)context.RequestServices.GetService(dbContextType);
var dbContext = (DbContext) context.RequestServices.GetService(dbContextType);
if (dbContext == null)
{
_logger.LogError(Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName));
_logger.LogError(
Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName));
}
else
{
var creator = dbContext.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator;
if (creator == null)
{
_logger.LogDebug(Strings.DatabaseErrorPage_NotRelationalDatabase);
@ -101,25 +115,36 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
var modelDiffer = dbContext.GetService<IMigrationsModelDiffer>();
var appliedMigrations = historyRepository.GetAppliedMigrations();
var pendingMigrations = (
from m in migrationsAssembly.Migrations
var pendingMigrations
= (from m in migrationsAssembly.Migrations
where !appliedMigrations.Any(
r => string.Equals(r.MigrationId, m.Key, StringComparison.OrdinalIgnoreCase))
r => string.Equals(r.MigrationId, m.Key,
StringComparison.OrdinalIgnoreCase))
select m.Key)
.ToList();
// 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 = migrationsAssembly.ModelSnapshot == null && databaseExists
? false
: modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, dbContext.Model);
var pendingModelChanges
= (migrationsAssembly.ModelSnapshot != null || !databaseExists)
&& 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();
page.Model = new DatabaseErrorPageModel(dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations, _options);
var page = new DatabaseErrorPage
{
Model = new DatabaseErrorPageModel(
dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations,
_options)
};
await page.ExecuteAsync(context);
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()));
@ -145,7 +171,8 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
return false;
}
bool match = false;
var match = false;
for (var e = exception; e != null && !match; e = e.InnerException)
{
match = lastError.Exception == e;
@ -154,11 +181,13 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
if (!match)
{
logger.LogDebug(Strings.DatabaseErrorPage_NoMatch);
return false;
}
logger.LogDebug(Strings.DatabaseErrorPage_Matched);
return true;
}
}
}
}

View File

@ -4,9 +4,7 @@
using System;
using System.Net;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -20,7 +18,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
public class MigrationsEndPointMiddleware
{
private readonly RequestDelegate _next;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly MigrationsEndPointOptions _options;
@ -28,22 +25,29 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
/// Initializes a new instance of the <see cref="MigrationsEndPointMiddleware"/> class
/// </summary>
/// <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="options">The options to control the behavior of the middleware.</param>
public MigrationsEndPointMiddleware(
[NotNull] RequestDelegate next,
[NotNull] IServiceProvider serviceProvider,
[NotNull] ILogger<MigrationsEndPointMiddleware> logger,
[NotNull] IOptions<MigrationsEndPointOptions> options)
RequestDelegate next,
ILogger<MigrationsEndPointMiddleware> logger,
IOptions<MigrationsEndPointOptions> options)
{
Check.NotNull(next, "next");
Check.NotNull(serviceProvider, "serviceProvider");
Check.NotNull(logger, "logger");
Check.NotNull(options, "options");
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next;
_serviceProvider = serviceProvider;
_logger = logger;
_options = options.Value;
}
@ -53,15 +57,19 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
/// </summary>
/// <param name="context">The context for the current request.</param>
/// <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))
{
_logger.LogDebug(Strings.FormatMigrationsEndPointMiddleware_RequestPathMatched(context.Request.Path));
var db = await GetDbContext(context, _logger);
if (db != null)
{
try
@ -78,8 +86,10 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
}
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);
throw new InvalidOperationException(message, ex);
}
}
@ -94,28 +104,39 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
{
var form = await context.Request.ReadFormAsync();
var contextTypeName = form["context"];
if (string.IsNullOrWhiteSpace(contextTypeName))
{
logger.LogError(Strings.MigrationsEndPointMiddleware_NoContextType);
await WriteErrorToResponse(context.Response, Strings.MigrationsEndPointMiddleware_NoContextType);
return null;
}
var contextType = Type.GetType(contextTypeName);
if (contextType == null)
{
var message = Strings.FormatMigrationsEndPointMiddleware_InvalidContextType(contextTypeName);
logger.LogError(message);
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.LogError(message);
await WriteErrorToResponse(context.Response, message);
return null;
}

View File

@ -10,38 +10,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
private static readonly ResourceManager _resourceManager
= 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>
/// 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. Skipping display of the database error page.
/// </summary>
@ -298,22 +266,6 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
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>
/// Migrations successfully applied for context '{0}'.
/// </summary>

View File

@ -117,12 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</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">
<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. Skipping display of the database error page.</value>
</data>
@ -171,9 +165,6 @@
<data name="DatabaseErrorPage_ApplyMigrationsCommandCLI" xml:space="preserve">
<value>&gt; dotnet ef database update</value>
</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">
<value>Migrations successfully applied for context '{0}'.</value>
</data>

View File

@ -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;
}
}
}

View File

@ -1,4 +1,4 @@
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
{
#line 1 "DatabaseErrorPage.cshtml"
using System
@ -19,7 +19,7 @@ using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
#line hidden
;
#line 4 "DatabaseErrorPage.cshtml"
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
#line default
#line hidden

View File

@ -1,7 +1,7 @@
@using System
@using System.Linq
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
@using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
@{
Response.StatusCode = 500;
Response.ContentType = "text/html; charset=utf-8";

View File

@ -1,72 +1,35 @@
// 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 JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Utilities;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.RazorViews
namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views
{
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(
[NotNull] Type contextType,
[NotNull] Exception exception,
Type contextType,
Exception exception,
bool databaseExists,
bool pendingModelChanges,
[NotNull] IEnumerable<string> pendingMigrations,
[NotNull] DatabaseErrorPageOptions options)
IEnumerable<string> pendingMigrations,
DatabaseErrorPageOptions options)
{
Check.NotNull(contextType, "contextType");
Check.NotNull(exception, "exception");
Check.NotNull(pendingMigrations, "pendingMigrations");
Check.NotNull(options, "options");
_contextType = contextType;
_exception = exception;
_databaseExists = databaseExists;
_pendingModelChanges = pendingModelChanges;
_pendingMigrations = pendingMigrations;
_options = options;
ContextType = contextType;
Exception = exception;
DatabaseExists = databaseExists;
PendingModelChanges = pendingModelChanges;
PendingMigrations = pendingMigrations;
Options = options;
}
public virtual Type ContextType
{
get { return _contextType; }
}
public virtual Exception Exception
{
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; }
}
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 DatabaseErrorPageOptions Options { get; }
}
}
}

View File

@ -6,5 +6,15 @@
{
"TypeId": "public class Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Views.DatabaseErrorPageModel",
"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"
}
]

View File

@ -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"
};
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -3,7 +3,7 @@
using Microsoft.AspNetCore.Builder;
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.EntityFrameworkCore;
using Moq;
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
exception: new Exception(),
databaseExists: false,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" },
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" },
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
exception: new Exception(),
databaseExists: true,
pendingModelChanges: true,
pendingMigrations: new string[] { "111_MigrationOne" },
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.Tests
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" },
pendingMigrations: new[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);