Skip to content
This repository has been archived by the owner on Dec 8, 2018. It is now read-only.

Commit

Permalink
Bring back "Apply Migrations" button on database error page
Browse files Browse the repository at this point in the history
Mostly just a revert of the commit that removed this functionality, with a few changes:
* Perform proper JavaScript encoding in the script part of the database error page Views/BaseView.cs
* Use the builder pattern in the UseXyz extension methods on IApplicationBuilder (per #184). Also applying this to DatabaseErrorPageOptions to keep things consistent.
* Fixing a few tests that were getting the context from DI but putting it in a using block so that it got disposed (rather than letting DI handle disposing).
  • Loading branch information
rowanmiller committed Oct 20, 2015
1 parent 111dab7 commit 1c26436
Show file tree
Hide file tree
Showing 22 changed files with 1,189 additions and 138 deletions.
15 changes: 15 additions & 0 deletions DiagnosticsPages.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Diagnostic
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ElmPageSample", "samples\ElmPageSample\ElmPageSample.xproj", "{FFD28DCF-C24F-4C59-9B6B-F3B74CE13129}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DatabaseErrorPageSample", "samples\DatabaseErrorPageSample\DatabaseErrorPageSample.xproj", "{FF7F11A1-14E7-4948-A853-2487D99DE0C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -206,6 +208,18 @@ Global
{FFD28DCF-C24F-4C59-9B6B-F3B74CE13129}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FFD28DCF-C24F-4C59-9B6B-F3B74CE13129}.Release|x86.ActiveCfg = Release|Any CPU
{FFD28DCF-C24F-4C59-9B6B-F3B74CE13129}.Release|x86.Build.0 = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|x86.ActiveCfg = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Debug|x86.Build.0 = Debug|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|Any CPU.Build.0 = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|x86.ActiveCfg = Release|Any CPU
{FF7F11A1-14E7-4948-A853-2487D99DE0C6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -225,5 +239,6 @@ Global
{CC1F5841-FE10-4DDB-8477-C4DE92BA759F} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
{83FFB65A-97B1-45AA-BCB8-3F43966BC8A3} = {509A6F36-AD80-4A18-B5B1-717D38DFF29D}
{FFD28DCF-C24F-4C59-9B6B-F3B74CE13129} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
{FF7F11A1-14E7-4948-A853-2487D99DE0C6} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
EndGlobalSection
EndGlobal
19 changes: 19 additions & 0 deletions samples/DatabaseErrorPageSample/DatabaseErrorPageSample.xproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>ff7f11a1-14e7-4948-a853-2487d99de0c6</ProjectGuid>
<RootNamespace>DatabaseErrorPageSample</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>10233</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
15 changes: 15 additions & 0 deletions samples/DatabaseErrorPageSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNET_ENV": "Development"
}
},
"web": {
"commandName": "web",
"commandLineArgs": " "
}
}
}
41 changes: 41 additions & 0 deletions samples/DatabaseErrorPageSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using Microsoft.AspNet.Builder;
using Microsoft.Data.Entity;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;

namespace DatabaseErrorPageSample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyContext>(options => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=DatabaseErrorPageSample;Trusted_Connection=True;"));
}

public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.Run(context =>
{
context.ApplicationServices.GetService<MyContext>().Blog.FirstOrDefault();
return Task.FromResult(0);
});
}
}

public class MyContext : DbContext
{
public DbSet<Blog> Blog { get; set; }
}

public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
}
18 changes: 18 additions & 0 deletions samples/DatabaseErrorPageSample/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"webroot": "wwwroot",
"dependencies": {
"Microsoft.AspNet.Diagnostics.Entity": "7.0.0-*",
"EntityFramework.MicrosoftSqlServer": "7.0.0-*",
"EntityFramework.Commands": "7.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*"
},
"commands": {
"ef": "EntityFramework.Commands",
"web": "Microsoft.AspNet.Server.Kestrel"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
}
}
1 change: 1 addition & 0 deletions samples/DatabaseErrorPageSample/wwwroot/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sample demonstrating ErrorPage middleware.
9 changes: 9 additions & 0 deletions samples/DatabaseErrorPageSample/wwwroot/web.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" forwardWindowsAuthToken="false" startupTimeLimit="3600" />
</system.webServer>
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using JetBrains.Annotations;
using Microsoft.AspNet.Diagnostics.Entity;
using Microsoft.AspNet.Diagnostics.Entity.Utilities;
using System;

namespace Microsoft.AspNet.Builder
{
Expand All @@ -13,15 +14,35 @@ public static IApplicationBuilder UseDatabaseErrorPage([NotNull] this IApplicati
{
Check.NotNull(builder, nameof(builder));

return builder.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
return builder.UseDatabaseErrorPage(options => options.EnableAll());
}

public static IApplicationBuilder UseDatabaseErrorPage([NotNull] this IApplicationBuilder builder, [NotNull] DatabaseErrorPageOptions options)
public static IApplicationBuilder UseDatabaseErrorPage([NotNull] this IApplicationBuilder builder, [NotNull] Action<DatabaseErrorPageOptions> optionsAction)
{
Check.NotNull(builder, nameof(builder));
Check.NotNull(optionsAction, nameof(optionsAction));

var options = new DatabaseErrorPageOptions();
optionsAction(options);

builder = builder.UseMiddleware<DatabaseErrorPageMiddleware>(options);

if(options.EnableMigrationCommands)
{
builder.UseMigrationsEndPoint(o => o.Path = options.MigrationsEndPointPath);
}

return builder;
}

public static void EnableAll([NotNull] this DatabaseErrorPageOptions options)
{
Check.NotNull(options, nameof(options));

return builder.UseMiddleware<DatabaseErrorPageMiddleware>(options);
options.ShowExceptionDetails = true;
options.ListMigrations = true;
options.EnableMigrationCommands = true;
options.MigrationsEndPointPath = MigrationsEndPointOptions.DefaultPath;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
// 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.AspNet.Http;

namespace Microsoft.AspNet.Diagnostics.Entity
{
public class DatabaseErrorPageOptions
{
public static DatabaseErrorPageOptions ShowAll => new DatabaseErrorPageOptions
{
ShowExceptionDetails = true,
ListMigrations = true
};

public virtual bool ShowExceptionDetails { get; set; }

public virtual bool ListMigrations { get; set; }
public virtual bool EnableMigrationCommands { get; set; }
public virtual PathString MigrationsEndPointPath { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;
using Microsoft.AspNet.Diagnostics.Entity;
using Microsoft.AspNet.Diagnostics.Entity.Utilities;

namespace Microsoft.AspNet.Builder
{
public static class MigrationsEndPointExtensions
{
public static IApplicationBuilder UseMigrationsEndPoint([NotNull] this IApplicationBuilder builder)
{
Check.NotNull(builder, "builder");

return builder.UseMigrationsEndPoint(options => { });
}

public static IApplicationBuilder UseMigrationsEndPoint([NotNull] this IApplicationBuilder builder, [NotNull] Action<MigrationsEndPointOptions> optionsAction)
{
Check.NotNull(builder, "builder");
Check.NotNull(optionsAction, "optionsAction");

var options = new MigrationsEndPointOptions();
optionsAction(options);

return builder.UseMiddleware<MigrationsEndPointMiddleware>(options);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics.Entity.Utilities;
using Microsoft.AspNet.Http;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNet.Diagnostics.Entity
{
public class MigrationsEndPointMiddleware
{
private readonly RequestDelegate _next;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private readonly MigrationsEndPointOptions _options;

public MigrationsEndPointMiddleware(
[NotNull] RequestDelegate next,
[NotNull] IServiceProvider serviceProvider,
[NotNull] ILogger<MigrationsEndPointMiddleware> logger,
[NotNull] MigrationsEndPointOptions options)
{
Check.NotNull(next, "next");
Check.NotNull(serviceProvider, "serviceProvider");
Check.NotNull(logger, "logger");
Check.NotNull(options, "options");

_next = next;
_serviceProvider = serviceProvider;
_logger = logger;
_options = options;
}

public virtual async Task Invoke([NotNull] HttpContext context)
{
Check.NotNull(context, "context");

if (context.Request.Path.Equals(_options.Path))
{
_logger.LogVerbose(Strings.FormatMigrationsEndPointMiddleware_RequestPathMatched(context.Request.Path));

var db = await GetDbContext(context, _logger);
if (db != null)
{
try
{
_logger.LogVerbose(Strings.FormatMigrationsEndPointMiddleware_ApplyingMigrations(db.GetType().FullName));

db.Database.Migrate();

context.Response.StatusCode = (int)HttpStatusCode.NoContent;
context.Response.Headers.Add("Pragma", new[] { "no-cache" });
context.Response.Headers.Add("Cache-Control", new[] { "no-cache" });

_logger.LogVerbose(Strings.FormatMigrationsEndPointMiddleware_Applied(db.GetType().FullName));
}
catch (Exception ex)
{
var message = Strings.FormatMigrationsEndPointMiddleware_Exception(db.GetType().FullName) + ex.ToString();
_logger.LogError(message);
throw new InvalidOperationException(message, ex);
}
}
}
else
{
await _next(context);
}
}

private static async Task<DbContext> GetDbContext(HttpContext context, ILogger logger)
{
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;
}

return db;
}

private static async Task WriteErrorToResponse(HttpResponse response, string error)
{
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Headers.Add("Pragma", new[] { "no-cache" });
response.Headers.Add("Cache-Control", new[] { "no-cache" });
response.ContentType = "text/plain";

// Padding to >512 to ensure IE doesn't hide the message
// http://stackoverflow.com/questions/16741062/what-rules-does-ie-use-to-determine-whether-to-show-the-entity-body
await response.WriteAsync(error.PadRight(513));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNet.Http;

namespace Microsoft.AspNet.Diagnostics.Entity
{
public class MigrationsEndPointOptions
{
public static PathString DefaultPath = new PathString("/ApplyDatabaseMigrations");

public virtual PathString Path { get; set; } = DefaultPath;
}
}
Loading

0 comments on commit 1c26436

Please sign in to comment.