public virtual async Task Invoke([NotNull] HttpContext context) { Check.NotNull(context, "context"); try { await _next(context); } catch (DataStoreException ex) { if (ex.Context != null && ex.Context.Database is RelationalDatabase) { var databaseExists = ex.Context.Database.AsRelational().Exists(); var serviceProvider = ex.Context.Configuration.Services.ServiceProvider; var migrator = serviceProvider.GetService <Migrator>(); // TODO GetPendingMigrations should handle database not existing (Issue #523) var pendingMigrations = databaseExists ? migrator.GetPendingMigrations().Select(m => m.MigrationId) : migrator.GetLocalMigrations().Select(m => m.MigrationId); var pendingModelChanges = true; var differ = serviceProvider.GetService <ModelDiffer>(); var migrationsAssembly = serviceProvider.GetService <MigrationAssembly>(); var snapshot = migrationsAssembly.Model; if (snapshot != null) { pendingModelChanges = differ.Diff(snapshot, ex.Context.Model).Any(); } if ((!databaseExists && pendingMigrations.Any()) || pendingMigrations.Any() || pendingModelChanges) { var page = new DatabaseErrorPage(); page.Model = new DatabaseErrorPageModel { Options = _options, Exception = ex, DatabaseExists = databaseExists, PendingMigrations = pendingMigrations, PendingModelChanges = pendingModelChanges }; // TODO Building in VS2013 prevents await in catch block // swap to await once we move to just VS14 page.ExecuteAsync(context).Wait(); return; } } throw; } }
/// <summary> /// Process an individual request. /// </summary> /// <param name="httpContext">The HTTP context for the current request.</param> /// <returns>A task that represents the asynchronous operation.</returns> public virtual async Task Invoke(HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } try { // Because CallContext is cloned at each async operation we cannot // lazily create the error object when an error is encountered, otherwise // it will not be available to code outside of the current async context. // We create it ahead of time so that any cloning just clones the reference // to the object that will hold any errors. _localDiagnostic.Value = new DiagnosticHolder(); await _next(httpContext); } catch (Exception exception) { try { if (ShouldDisplayErrorPage(exception)) { var contextType = _localDiagnostic.Value !.ContextType; var details = await httpContext.GetContextDetailsAsync(contextType !, _logger); if (details != null && (details.PendingModelChanges || details.PendingMigrations.Any())) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel(exception, new DatabaseContextDetails[] { details }, _options, httpContext.Request.PathBase) }; await page.ExecuteAsync(httpContext); return; } } } catch (Exception e) { _logger.DatabaseErrorPageMiddlewareException(e); } throw; } }
public async Task HandleExceptionAsync(ErrorContext errorContext, Func <ErrorContext, Task> next) { if (!(errorContext.Exception is DbException)) { await next(errorContext); } try { // Look for DbContext classes registered in the service provider var registeredContexts = errorContext.HttpContext.RequestServices.GetServices <DbContextOptions>() .Select(o => o.ContextType) .Distinct(); // Workaround for https://github.com/dotnet/efcore/issues/22341 if (registeredContexts.Any()) { var contextDetails = new List <DatabaseContextDetails>(); foreach (var registeredContext in registeredContexts) { var details = await errorContext.HttpContext.GetContextDetailsAsync(registeredContext, _logger); if (details != null) { contextDetails.Add(details); } } if (contextDetails.Any(c => c.PendingModelChanges || c.PendingMigrations.Any())) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel(errorContext.Exception, contextDetails, _options, errorContext.HttpContext.Request.PathBase) }; await page.ExecuteAsync(errorContext.HttpContext); return; } } } catch (Exception e) { _logger.DatabaseErrorPageMiddlewareException(e); return; } }
private static async Task <string> ExecutePage(DatabaseErrorPageOptions options, DatabaseErrorPageModel model) { var page = new DatabaseErrorPage(); var context = new Mock <HttpContext>(); var response = new Mock <HttpResponse>(); var stream = new MemoryStream(); response.Setup(r => r.Body).Returns(stream); context.Setup(c => c.Response).Returns(response.Object); page.Model = model; await page.ExecuteAsync(context.Object); var content = Encoding.ASCII.GetString(stream.ToArray()); return(content); }
public virtual async Task Invoke([NotNull] HttpContext context) { Check.NotNull(context, "context"); try { #if !DNXCORE50 // TODO This probably isn't the correct place for this workaround, it // needs to be called before anything is written to CallContext // http://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx System.Configuration.ConfigurationManager.GetSection("system.xml/xmlReader"); #endif _loggerProvider.Logger.StartLoggingForCurrentCallContext(); await _next(context); } catch (Exception ex) { try { if (ShouldDisplayErrorPage(_loggerProvider.Logger.LastError, ex, _logger)) { var dbContextType = _loggerProvider.Logger.LastError.ContextType; var dbContext = (DbContext)context.RequestServices.GetService(dbContextType); if (dbContext == null) { _logger.LogError(Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName)); } else { var creator = dbContext.GetService <IDatabaseCreator>() as IRelationalDatabaseCreator; if (creator == null) { _logger.LogVerbose(Strings.DatabaseErrorPage_NotRelationalDatabase); } else { var databaseExists = creator.Exists(); var historyRepository = dbContext.GetService <IHistoryRepository>(); var migrationsAssembly = dbContext.GetService <IMigrationsAssembly>(); var modelDiffer = dbContext.GetService <IMigrationsModelDiffer>(); var appliedMigrations = historyRepository.GetAppliedMigrations(); var pendingMigrations = ( from m in migrationsAssembly.Migrations where !appliedMigrations.Any( r => string.Equals(r.MigrationId, m.Id, StringComparison.OrdinalIgnoreCase)) select m.Id) .ToList(); var pendingModelChanges = modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, dbContext.Model); if ((!databaseExists && pendingMigrations.Any()) || pendingMigrations.Any() || pendingModelChanges) { var page = new DatabaseErrorPage(); page.Model = new DatabaseErrorPageModel(dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations, _options); await page.ExecuteAsync(context); return; } } } } } catch (Exception e) { _logger.LogError(Strings.DatabaseErrorPageMiddleware_Exception, e); } throw; } }
/// <summary> /// Process an individual request. /// </summary> /// <param name="httpContext">The HTTP context for the current request.</param> /// <returns>A task that represents the asynchronous operation.</returns> public virtual async Task Invoke(HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } try { // Because CallContext is cloned at each async operation we cannot // lazily create the error object when an error is encountered, otherwise // it will not be available to code outside of the current async context. // We create it ahead of time so that any cloning just clones the reference // to the object that will hold any errors. _localDiagnostic.Value = new DiagnosticHolder(); await _next(httpContext); } catch (Exception exception) { try { if (ShouldDisplayErrorPage(exception)) { var contextType = _localDiagnostic.Value.ContextType; var context = (DbContext)httpContext.RequestServices.GetService(contextType); if (context == null) { _logger.ContextNotRegisteredDatabaseErrorPageMiddleware(contextType.FullName); } else { var relationalDatabaseCreator = context.GetService <IDatabaseCreator>() as IRelationalDatabaseCreator; if (relationalDatabaseCreator == null) { _logger.NotRelationalDatabase(); } else { var databaseExists = await relationalDatabaseCreator.ExistsAsync(); if (databaseExists) { // Also check if the database is completely empty - see https://github.com/aspnet/EntityFrameworkCore/issues/15997 databaseExists = (bool)typeof(RelationalDatabaseCreator).GetMethod("HasTables", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(relationalDatabaseCreator, null); } var migrationsAssembly = context.GetService <IMigrationsAssembly>(); var modelDiffer = context.GetService <IMigrationsModelDiffer>(); // HasDifferences will return true if there is no model snapshot, but if there is an existing database // and no model snapshot then we don't want to show the error page since they are most likely targeting // and existing database and have just misconfigured their model var pendingModelChanges = (!databaseExists || migrationsAssembly.ModelSnapshot != null) && modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, context.Model); var pendingMigrations = (databaseExists ? await context.Database.GetPendingMigrationsAsync() : context.Database.GetMigrations()) .ToArray(); if (pendingModelChanges || pendingMigrations.Any()) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel( contextType, exception, databaseExists, pendingModelChanges, pendingMigrations, _options) }; await page.ExecuteAsync(httpContext); return; } } } } } catch (Exception e) { _logger.DatabaseErrorPageMiddlewareException(e); } throw; } }
/// <summary> /// Process an individual request. /// </summary> /// <param name="httpContext">The HTTP context for the current request.</param> /// <returns>A task that represents the asynchronous operation.</returns> public virtual async Task Invoke(HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } try { // Because CallContext is cloned at each async operation we cannot // lazily create the error object when an error is encountered, otherwise // it will not be available to code outside of the current async context. // We create it ahead of time so that any cloning just clones the reference // to the object that will hold any errors. _localDiagnostic.Value = new DiagnosticHolder(); await _next(httpContext); } catch (Exception exception) { try { if (ShouldDisplayErrorPage(exception)) { var contextType = _localDiagnostic.Value.ContextType; var context = (DbContext)httpContext.RequestServices.GetService(contextType); if (context == null) { _logger.ContextNotRegisteredDatabaseErrorPageMiddleware(contextType.FullName); } else { var relationalDatabaseCreator = context.GetService <IDatabaseCreator>() as IRelationalDatabaseCreator; if (relationalDatabaseCreator == null) { _logger.NotRelationalDatabase(); } else { var databaseExists = await relationalDatabaseCreator.ExistsAsync(); if (databaseExists) { databaseExists = await relationalDatabaseCreator.HasTablesAsync(); } var migrationsAssembly = context.GetService <IMigrationsAssembly>(); var modelDiffer = context.GetService <IMigrationsModelDiffer>(); var snapshotModel = migrationsAssembly.ModelSnapshot?.Model; if (snapshotModel is IConventionModel conventionModel) { var conventionSet = context.GetService <IConventionSetBuilder>().CreateConventionSet(); var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType <TypeMappingConvention>().FirstOrDefault(); if (typeMappingConvention != null) { typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null); } var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType <RelationalModelConvention>().FirstOrDefault(); if (relationalModelConvention != null) { snapshotModel = relationalModelConvention.ProcessModelFinalized(conventionModel); } } if (snapshotModel is IMutableModel mutableModel) { snapshotModel = mutableModel.FinalizeModel(); } // HasDifferences will return true if there is no model snapshot, but if there is an existing database // and no model snapshot then we don't want to show the error page since they are most likely targeting // and existing database and have just misconfigured their model var pendingModelChanges = (!databaseExists || migrationsAssembly.ModelSnapshot != null) && modelDiffer.HasDifferences(snapshotModel?.GetRelationalModel(), context.Model.GetRelationalModel()); var pendingMigrations = (databaseExists ? await context.Database.GetPendingMigrationsAsync() : context.Database.GetMigrations()) .ToArray(); if (pendingModelChanges || pendingMigrations.Length > 0) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel( contextType, exception, databaseExists, pendingModelChanges, pendingMigrations, _options) }; await page.ExecuteAsync(httpContext); return; } } } } } catch (Exception e) { _logger.DatabaseErrorPageMiddlewareException(e); } throw; } }
/// <summary> /// Handle <see cref="DbException"/> errors and output an HTML response with additional details. /// </summary> /// <inheritdoc /> public async Task HandleExceptionAsync(ErrorContext errorContext, Func <ErrorContext, Task> next) { var dbException = errorContext.Exception as DbException ?? errorContext.Exception?.InnerException as DbException; if (dbException == null) { await next(errorContext); return; } try { // Look for DbContext classes registered in the service provider var registeredContexts = errorContext.HttpContext.RequestServices.GetServices <DbContextOptions>() .Select(o => o.ContextType) .Distinct(); // Workaround for https://github.com/dotnet/efcore/issues/22341 if (registeredContexts.Any()) { var contextDetails = new List <DatabaseContextDetails>(); foreach (var registeredContext in registeredContexts) { var details = await errorContext.HttpContext.GetContextDetailsAsync(registeredContext, _logger); if (details != null) { contextDetails.Add(details); } } if (contextDetails.Any(c => c.PendingModelChanges || c.PendingMigrations.Any())) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel(dbException, contextDetails, _options, errorContext.HttpContext.Request.PathBase) }; await page.ExecuteAsync(errorContext.HttpContext); return; } } } catch (Exception e) { _logger.DatabaseErrorPageMiddlewareException(e); } // Error could not be handled var response = errorContext.HttpContext.Response; if (response.HasStarted) { _logger.ResponseStartedDatabaseDeveloperPageExceptionFilter(); return; } // Try the next filter response.Clear(); response.StatusCode = 500; await next(errorContext); }
/// <summary> /// Process an individual request. /// </summary> /// <param name="httpContext">The HTTP context for the current request.</param> /// <returns>A task that represents the asynchronous operation.</returns> public virtual async Task Invoke(HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } try { // Because CallContext is cloned at each async operation we cannot // lazily create the error object when an error is encountered, otherwise // it will not be available to code outside of the current async context. // We create it ahead of time so that any cloning just clones the reference // to the object that will hold any errors. _localDiagnostic.Value = new DiagnosticHolder(); await _next(httpContext); } catch (Exception exception) { try { if (ShouldDisplayErrorPage(exception)) { var contextType = _localDiagnostic.Value.ContextType; var context = (DbContext)httpContext.RequestServices.GetService(contextType); if (context == null) { _logger.LogError(Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(contextType.FullName)); } else { var relationalDatabaseCreator = context.GetService <IDatabaseCreator>() as IRelationalDatabaseCreator; if (relationalDatabaseCreator == null) { _logger.LogDebug(Strings.DatabaseErrorPage_NotRelationalDatabase); } else { var databaseExists = await relationalDatabaseCreator.ExistsAsync(); var migrationsAssembly = context.GetService <IMigrationsAssembly>(); var modelDiffer = context.GetService <IMigrationsModelDiffer>(); // HasDifferences will return true if there is no model snapshot, but if there is an existing database // and no model snapshot then we don't want to show the error page since they are most likely targeting // and existing database and have just misconfigured their model var pendingModelChanges = (!databaseExists || migrationsAssembly.ModelSnapshot != null) && modelDiffer.HasDifferences(migrationsAssembly.ModelSnapshot?.Model, context.Model); var pendingMigrations = (databaseExists ? await context.Database.GetPendingMigrationsAsync() : context.Database.GetMigrations()) .ToArray(); if (pendingModelChanges || pendingMigrations.Any()) { var page = new DatabaseErrorPage { Model = new DatabaseErrorPageModel( contextType, exception, databaseExists, pendingModelChanges, pendingMigrations, _options) }; await page.ExecuteAsync(httpContext); return; } } } } } catch (Exception e) { _logger.LogError( eventId: 0, exception: e, message: Strings.DatabaseErrorPageMiddleware_Exception); } throw; } }
public virtual async Task Invoke([NotNull] HttpContext context) { Check.NotNull(context, "context"); try { #if !ASPNETCORE50 // TODO This probably isn't the correct place for this workaround, it // needs to be called before anything is written to CallContext // http://msdn.microsoft.com/en-us/library/dn458353(v=vs.110).aspx System.Configuration.ConfigurationManager.GetSection("system.xml/xmlReader"); #endif _loggerProvider.Logger.StartLoggingForCurrentCallContext(); await _next(context).WithCurrentCulture(); } catch (Exception ex) { try { if (_loggerProvider.Logger.LastError.IsErrorLogged && _loggerProvider.Logger.LastError.Exception == ex) { using (RequestServicesContainer.EnsureRequestServices(context, _serviceProvider)) { var dbContextType = _loggerProvider.Logger.LastError.ContextType; var dbContext = (DbContext)context.RequestServices.GetService(dbContextType); if (dbContext == null) { _logger.WriteError(Strings.FormatDatabaseErrorPageMiddleware_ContextNotRegistered(dbContextType.FullName)); } else { if (dbContext.Database is RelationalDatabase) { var databaseExists = dbContext.Database.AsRelational().Exists(); var databaseInternals = (IMigrationsEnabledDatabaseInternals)dbContext.Database; var migrator = databaseInternals.Migrator; var pendingMigrations = migrator.GetPendingMigrations().Select(m => m.GetMigrationId()); var pendingModelChanges = true; var snapshot = migrator.MigrationAssembly.ModelSnapshot; if (snapshot != null) { pendingModelChanges = migrator.ModelDiffer.Diff(snapshot.Model, dbContext.Model).Any(); } if ((!databaseExists && pendingMigrations.Any()) || pendingMigrations.Any() || pendingModelChanges) { var page = new DatabaseErrorPage(); page.Model = new DatabaseErrorPageModel(dbContextType, ex, databaseExists, pendingModelChanges, pendingMigrations, _options); await page.ExecuteAsync(context).WithCurrentCulture(); return; } } } } } } catch (Exception e) { _logger.WriteError(Strings.DatabaseErrorPageMiddleware_Exception, e); } throw; } }