Ejemplo n.º 1
0
        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;
            }
        }
Ejemplo n.º 3
0
        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;
            }
        }
Ejemplo n.º 4
0
        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;
            }
        }
Ejemplo n.º 6
0
        /// <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;
            }
        }
Ejemplo n.º 7
0
        /// <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);
    }
Ejemplo n.º 9
0
        /// <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;
            }
        }