diff --git a/src/Microsoft.AspNet.Diagnostics.Entity/DatabaseErrorPageMiddleware.cs b/src/Microsoft.AspNet.Diagnostics.Entity/DatabaseErrorPageMiddleware.cs index bc32702c55..b4fb72d6d2 100644 --- a/src/Microsoft.AspNet.Diagnostics.Entity/DatabaseErrorPageMiddleware.cs +++ b/src/Microsoft.AspNet.Diagnostics.Entity/DatabaseErrorPageMiddleware.cs @@ -66,8 +66,7 @@ namespace Microsoft.AspNet.Diagnostics.Entity { try { - if (_loggerProvider.Logger.LastError.IsErrorLogged - && _loggerProvider.Logger.LastError.Exception == ex) + if (ShouldDisplayErrorPage(_loggerProvider.Logger.LastError, ex, _logger)) { using (RequestServicesContainer.EnsureRequestServices(context, _serviceProvider)) { @@ -79,7 +78,11 @@ namespace Microsoft.AspNet.Diagnostics.Entity } else { - if (dbContext.Database is RelationalDatabase) + if (!(dbContext.Database is RelationalDatabase)) + { + _logger.WriteVerbose(Strings.DatabaseErrorPage_NotRelationalDatabase); + } + else { var databaseExists = dbContext.Database.AsRelational().Exists(); @@ -115,5 +118,31 @@ namespace Microsoft.AspNet.Diagnostics.Entity throw; } } + + private static bool ShouldDisplayErrorPage(DataStoreErrorLogger.DataStoreErrorLog lastError, Exception exception, ILogger logger) + { + logger.WriteVerbose(Strings.FormatDatabaseErrorPage_AttemptingToMatchException(exception.GetType())); + + if (!lastError.IsErrorLogged) + { + logger.WriteVerbose(Strings.DatabaseErrorPage_NoRecordedException); + return false; + } + + bool match = false; + for (var e = exception; e != null && !match; e = e.InnerException) + { + match = lastError.Exception == e; + } + + if (!match) + { + logger.WriteVerbose(Strings.DatabaseErrorPage_NoMatch); + return false; + } + + logger.WriteVerbose(Strings.DatabaseErrorPage_Matched); + return true; + } } } diff --git a/src/Microsoft.AspNet.Diagnostics.Entity/Properties/Strings.Designer.cs b/src/Microsoft.AspNet.Diagnostics.Entity/Properties/Strings.Designer.cs index 01ed143a61..713fe5e89f 100644 --- a/src/Microsoft.AspNet.Diagnostics.Entity/Properties/Strings.Designer.cs +++ b/src/Microsoft.AspNet.Diagnostics.Entity/Properties/Strings.Designer.cs @@ -458,6 +458,86 @@ namespace Microsoft.AspNet.Diagnostics.Entity return GetString("DatabaseErrorPage_EnableMigrationsCommandsInfo"); } + /// + /// {0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation. + /// + internal static string DatabaseErrorPage_AttemptingToMatchException + { + get { return GetString("DatabaseErrorPage_AttemptingToMatchException"); } + } + + /// + /// {0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation. + /// + internal static string FormatDatabaseErrorPage_AttemptingToMatchException(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("DatabaseErrorPage_AttemptingToMatchException"), p0); + } + + /// + /// Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page. + /// + internal static string DatabaseErrorPage_Matched + { + get { return GetString("DatabaseErrorPage_Matched"); } + } + + /// + /// Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page. + /// + internal static string FormatDatabaseErrorPage_Matched() + { + return GetString("DatabaseErrorPage_Matched"); + } + + /// + /// Entity Framework did not record any exceptions due to failed database operations. This means the current exception is not a failed Entity Framework database operation, or the current exception occurred from a DbContext that was not obtained from request services. + /// + internal static string DatabaseErrorPage_NoRecordedException + { + get { return GetString("DatabaseErrorPage_NoRecordedException"); } + } + + /// + /// Entity Framework did not record any exceptions due to failed database operations. This means the current exception is not a failed Entity Framework database operation, or the current exception occurred from a DbContext that was not obtained from request services. + /// + internal static string FormatDatabaseErrorPage_NoRecordedException() + { + return GetString("DatabaseErrorPage_NoRecordedException"); + } + + /// + /// The target data store is not a relational database. Skipping the database error page. + /// + internal static string DatabaseErrorPage_NotRelationalDatabase + { + get { return GetString("DatabaseErrorPage_NotRelationalDatabase"); } + } + + /// + /// The target data store is not a relational database. Skipping the database error page. + /// + internal static string FormatDatabaseErrorPage_NotRelationalDatabase() + { + return GetString("DatabaseErrorPage_NotRelationalDatabase"); + } + + /// + /// The current exception (and its inner exceptions) do not match the last exception Entity Framework recorded due to a failed database operation. This means the database operation exception was handled and another exception occurred later in the request. + /// + internal static string DatabaseErrorPage_NoMatch + { + get { return GetString("DatabaseErrorPage_NoMatch"); } + } + + /// + /// The current exception (and its inner exceptions) do not match the last exception Entity Framework recorded due to a failed database operation. This means the database operation exception was handled and another exception occurred later in the request. + /// + internal static string FormatDatabaseErrorPage_NoMatch() + { + return GetString("DatabaseErrorPage_NoMatch"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Diagnostics.Entity/Strings.resx b/src/Microsoft.AspNet.Diagnostics.Entity/Strings.resx index 12b4868cf5..7e57f9d7b1 100644 --- a/src/Microsoft.AspNet.Diagnostics.Entity/Strings.resx +++ b/src/Microsoft.AspNet.Diagnostics.Entity/Strings.resx @@ -201,4 +201,19 @@ To use migrations from a command prompt you will need to <a href='http://go.microsoft.com/fwlink/?LinkId=518242'>install K Version Manager (KVM)</a>. Once installed, you can run migration commands from a standard command prompt in the project directory. + + {0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation. + + + Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page. + + + Entity Framework did not record any exceptions due to failed database operations. This means the current exception is not a failed Entity Framework database operation, or the current exception occurred from a DbContext that was not obtained from request services. + + + The target data store is not a relational database. Skipping the database error page. + + + The current exception (and its inner exceptions) do not match the last exception Entity Framework recorded due to a failed database operation. This means the database operation exception was handled and another exception occurred later in the request. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Diagnostics.Entity.FunctionalTests/DatabaseErrorPageMiddlewareTest.cs b/test/Microsoft.AspNet.Diagnostics.Entity.FunctionalTests/DatabaseErrorPageMiddlewareTest.cs index 61516a7a97..e62828c312 100644 --- a/test/Microsoft.AspNet.Diagnostics.Entity.FunctionalTests/DatabaseErrorPageMiddlewareTest.cs +++ b/test/Microsoft.AspNet.Diagnostics.Entity.FunctionalTests/DatabaseErrorPageMiddlewareTest.cs @@ -338,6 +338,41 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests } } + [Fact] + public async Task Error_page_displayed_when_exception_wrapped() + { + TestServer server = SetupTestServer(); + HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); + + 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); + } + + class WrappedExceptionMiddleware + { + public WrappedExceptionMiddleware(RequestDelegate next) + { } + + public virtual Task Invoke(HttpContext context) + { + using (var db = context.ApplicationServices.GetService()) + { + db.Blogs.Add(new Blog()); + try + { + db.SaveChanges(); + throw new Exception("SaveChanges should have thrown"); + } + catch (Exception ex) + { + throw new Exception("I wrapped your exception", ex); + } + } + } + } + private static TestServer SetupTestServer(ILoggerProvider logProvider = null) where TContext : DbContext {