Handle wrapped exceptions in Database Error Page
If a database exception is wrapped later in the request (after EF has logged it) then we were not displaying the database error page. This was occurring in ASP.NET Identity where the exception was getting wrapped in an AggregateException. We now walk the tree of inner exceptions looking for a match. Also adding some extra logging as this was hard to debug without resorting to source code.
This commit is contained in:
parent
b014a9ef41
commit
5e00937d59
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,6 +458,86 @@ namespace Microsoft.AspNet.Diagnostics.Entity
|
|||
return GetString("DatabaseErrorPage_EnableMigrationsCommandsInfo");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation.
|
||||
/// </summary>
|
||||
internal static string DatabaseErrorPage_AttemptingToMatchException
|
||||
{
|
||||
get { return GetString("DatabaseErrorPage_AttemptingToMatchException"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation.
|
||||
/// </summary>
|
||||
internal static string FormatDatabaseErrorPage_AttemptingToMatchException(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("DatabaseErrorPage_AttemptingToMatchException"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page.
|
||||
/// </summary>
|
||||
internal static string DatabaseErrorPage_Matched
|
||||
{
|
||||
get { return GetString("DatabaseErrorPage_Matched"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page.
|
||||
/// </summary>
|
||||
internal static string FormatDatabaseErrorPage_Matched()
|
||||
{
|
||||
return GetString("DatabaseErrorPage_Matched");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string DatabaseErrorPage_NoRecordedException
|
||||
{
|
||||
get { return GetString("DatabaseErrorPage_NoRecordedException"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatDatabaseErrorPage_NoRecordedException()
|
||||
{
|
||||
return GetString("DatabaseErrorPage_NoRecordedException");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target data store is not a relational database. Skipping the database error page.
|
||||
/// </summary>
|
||||
internal static string DatabaseErrorPage_NotRelationalDatabase
|
||||
{
|
||||
get { return GetString("DatabaseErrorPage_NotRelationalDatabase"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target data store is not a relational database. Skipping the database error page.
|
||||
/// </summary>
|
||||
internal static string FormatDatabaseErrorPage_NotRelationalDatabase()
|
||||
{
|
||||
return GetString("DatabaseErrorPage_NotRelationalDatabase");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string DatabaseErrorPage_NoMatch
|
||||
{
|
||||
get { return GetString("DatabaseErrorPage_NoMatch"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatDatabaseErrorPage_NoMatch()
|
||||
{
|
||||
return GetString("DatabaseErrorPage_NoMatch");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -201,4 +201,19 @@
|
|||
<data name="DatabaseErrorPage_EnableMigrationsCommandsInfo" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="DatabaseErrorPage_AttemptingToMatchException" xml:space="preserve">
|
||||
<value>{0} occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation.</value>
|
||||
</data>
|
||||
<data name="DatabaseErrorPage_Matched" xml:space="preserve">
|
||||
<value>Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page.</value>
|
||||
</data>
|
||||
<data name="DatabaseErrorPage_NoRecordedException" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="DatabaseErrorPage_NotRelationalDatabase" xml:space="preserve">
|
||||
<value>The target data store is not a relational database. Skipping the database error page.</value>
|
||||
</data>
|
||||
<data name="DatabaseErrorPage_NoMatch" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -338,6 +338,41 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Error_page_displayed_when_exception_wrapped()
|
||||
{
|
||||
TestServer server = SetupTestServer<BloggingContext, WrappedExceptionMiddleware>();
|
||||
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<BloggingContext>())
|
||||
{
|
||||
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<TContext, TMiddleware>(ILoggerProvider logProvider = null)
|
||||
where TContext : DbContext
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue