Bring back "Apply Migrations" button on database error page

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).
This commit is contained in:
Rowan Miller 2015-10-16 09:19:59 -07:00
parent 111dab7ddf
commit 1c26436041
22 changed files with 1189 additions and 138 deletions

View File

@ -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
@ -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
@ -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

View File

@ -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>

View File

@ -0,0 +1,15 @@
{
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNET_ENV": "Development"
}
},
"web": {
"commandName": "web",
"commandLineArgs": " "
}
}
}

View File

@ -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; }
}
}

View File

@ -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": { }
}
}

View File

@ -0,0 +1 @@
Sample demonstrating ErrorPage middleware.

View File

@ -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>

View File

@ -4,6 +4,7 @@
using JetBrains.Annotations;
using Microsoft.AspNet.Diagnostics.Entity;
using Microsoft.AspNet.Diagnostics.Entity.Utilities;
using System;
namespace Microsoft.AspNet.Builder
{
@ -13,15 +14,35 @@ namespace Microsoft.AspNet.Builder
{
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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -90,6 +90,70 @@ namespace Microsoft.AspNet.Diagnostics.Entity
return GetString("DatabaseErrorPage_AddMigrationCommand");
}
/// <summary>
/// Apply Migrations
/// </summary>
internal static string DatabaseErrorPage_ApplyMigrationsButton
{
get { return GetString("DatabaseErrorPage_ApplyMigrationsButton"); }
}
/// <summary>
/// Apply Migrations
/// </summary>
internal static string FormatDatabaseErrorPage_ApplyMigrationsButton()
{
return GetString("DatabaseErrorPage_ApplyMigrationsButton");
}
/// <summary>
/// Migrations Applied
/// </summary>
internal static string DatabaseErrorPage_ApplyMigrationsButtonDone
{
get { return GetString("DatabaseErrorPage_ApplyMigrationsButtonDone"); }
}
/// <summary>
/// Migrations Applied
/// </summary>
internal static string FormatDatabaseErrorPage_ApplyMigrationsButtonDone()
{
return GetString("DatabaseErrorPage_ApplyMigrationsButtonDone");
}
/// <summary>
/// Applying Migrations...
/// </summary>
internal static string DatabaseErrorPage_ApplyMigrationsButtonRunning
{
get { return GetString("DatabaseErrorPage_ApplyMigrationsButtonRunning"); }
}
/// <summary>
/// Applying Migrations...
/// </summary>
internal static string FormatDatabaseErrorPage_ApplyMigrationsButtonRunning()
{
return GetString("DatabaseErrorPage_ApplyMigrationsButtonRunning");
}
/// <summary>
/// An error occurred applying migrations, try applying them from the command line
/// </summary>
internal static string DatabaseErrorPage_ApplyMigrationsFailed
{
get { return GetString("DatabaseErrorPage_ApplyMigrationsFailed"); }
}
/// <summary>
/// An error occurred applying migrations, try applying them from the command line
/// </summary>
internal static string FormatDatabaseErrorPage_ApplyMigrationsFailed()
{
return GetString("DatabaseErrorPage_ApplyMigrationsFailed");
}
/// <summary>
/// You can also apply migrations from the command line:
/// </summary>
@ -106,6 +170,22 @@ namespace Microsoft.AspNet.Diagnostics.Entity
return GetString("DatabaseErrorPage_HowToApplyFromCmd");
}
/// <summary>
/// Try refreshing the page
/// </summary>
internal static string DatabaseErrorPage_MigrationsAppliedRefresh
{
get { return GetString("DatabaseErrorPage_MigrationsAppliedRefresh"); }
}
/// <summary>
/// Try refreshing the page
/// </summary>
internal static string FormatDatabaseErrorPage_MigrationsAppliedRefresh()
{
return GetString("DatabaseErrorPage_MigrationsAppliedRefresh");
}
/// <summary>
/// From the command line, scaffold a new migration and apply it to the database:
/// </summary>
@ -234,6 +314,118 @@ namespace Microsoft.AspNet.Diagnostics.Entity
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidEnumValue", "argumentName", "enumType"), argumentName, enumType);
}
/// <summary>
/// Migrations successfully applied for context '{0}'.
/// </summary>
internal static string MigrationsEndPointMiddleware_Applied
{
get { return GetString("MigrationsEndPointMiddleware_Applied"); }
}
/// <summary>
/// Migrations successfully applied for context '{0}'.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_Applied(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_Applied"), p0);
}
/// <summary>
/// Request is valid, applying migrations for context '{0}'.
/// </summary>
internal static string MigrationsEndPointMiddleware_ApplyingMigrations
{
get { return GetString("MigrationsEndPointMiddleware_ApplyingMigrations"); }
}
/// <summary>
/// Request is valid, applying migrations for context '{0}'.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_ApplyingMigrations(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_ApplyingMigrations"), p0);
}
/// <summary>
/// The context type '{0}' was not found in services. This usually means the context was not registered in services during startup. You probably want to call AddScoped&lt;{0}&gt;() inside the UseServices(...) call in your application startup code.
/// </summary>
internal static string MigrationsEndPointMiddleware_ContextNotRegistered
{
get { return GetString("MigrationsEndPointMiddleware_ContextNotRegistered"); }
}
/// <summary>
/// The context type '{0}' was not found in services. This usually means the context was not registered in services during startup. You probably want to call AddScoped&lt;{0}&gt;() inside the UseServices(...) call in your application startup code.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_ContextNotRegistered(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_ContextNotRegistered"), p0);
}
/// <summary>
/// An error occurred while applying the migrations for '{0}'. See InnerException for details.
/// </summary>
internal static string MigrationsEndPointMiddleware_Exception
{
get { return GetString("MigrationsEndPointMiddleware_Exception"); }
}
/// <summary>
/// An error occurred while applying the migrations for '{0}'. See InnerException for details.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_Exception(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_Exception"), p0);
}
/// <summary>
/// The context type '{0}' could not be loaded. Ensure this is the correct type name for the context you are trying to apply migrations for.
/// </summary>
internal static string MigrationsEndPointMiddleware_InvalidContextType
{
get { return GetString("MigrationsEndPointMiddleware_InvalidContextType"); }
}
/// <summary>
/// The context type '{0}' could not be loaded. Ensure this is the correct type name for the context you are trying to apply migrations for.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_InvalidContextType(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_InvalidContextType"), p0);
}
/// <summary>
/// No context type was specified. Ensure the form data from the request includes a contextTypeName value, specifying the context to apply migrations for.
/// </summary>
internal static string MigrationsEndPointMiddleware_NoContextType
{
get { return GetString("MigrationsEndPointMiddleware_NoContextType"); }
}
/// <summary>
/// No context type was specified. Ensure the form data from the request includes a contextTypeName value, specifying the context to apply migrations for.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_NoContextType()
{
return GetString("MigrationsEndPointMiddleware_NoContextType");
}
/// <summary>
/// Request path matched the path configured for this migrations endpoint ({0}). Attempting to process the migrations request.
/// </summary>
internal static string MigrationsEndPointMiddleware_RequestPathMatched
{
get { return GetString("MigrationsEndPointMiddleware_RequestPathMatched"); }
}
/// <summary>
/// Request path matched the path configured for this migrations endpoint ({0}). Attempting to process the migrations request.
/// </summary>
internal static string FormatMigrationsEndPointMiddleware_RequestPathMatched(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("MigrationsEndPointMiddleware_RequestPathMatched"), p0);
}
/// <summary>
/// A database operation failed while processing the request.
/// </summary>

View File

@ -132,9 +132,24 @@
<data name="DatabaseErrorPage_AddMigrationCommand" xml:space="preserve">
<value>&gt; dnx ef migrations add [migration name]</value>
</data>
<data name="DatabaseErrorPage_ApplyMigrationsButton" xml:space="preserve">
<value>Apply Migrations</value>
</data>
<data name="DatabaseErrorPage_ApplyMigrationsButtonDone" xml:space="preserve">
<value>Migrations Applied</value>
</data>
<data name="DatabaseErrorPage_ApplyMigrationsButtonRunning" xml:space="preserve">
<value>Applying Migrations...</value>
</data>
<data name="DatabaseErrorPage_ApplyMigrationsFailed" xml:space="preserve">
<value>An error occurred applying migrations, try applying them from the command line</value>
</data>
<data name="DatabaseErrorPage_HowToApplyFromCmd" xml:space="preserve">
<value>You can also apply migrations from the command line:</value>
</data>
<data name="DatabaseErrorPage_MigrationsAppliedRefresh" xml:space="preserve">
<value>Try refreshing the page</value>
</data>
<data name="DatabaseErrorPage_NoDbOrMigrationsInfo" xml:space="preserve">
<value>From the command line, scaffold a new migration and apply it to the database:</value>
</data>
@ -159,6 +174,27 @@
<data name="InvalidEnumValue" xml:space="preserve">
<value>The value provided for argument '{argumentName}' must be a valid value of enum type '{enumType}'.</value>
</data>
<data name="MigrationsEndPointMiddleware_Applied" xml:space="preserve">
<value>Migrations successfully applied for context '{0}'.</value>
</data>
<data name="MigrationsEndPointMiddleware_ApplyingMigrations" xml:space="preserve">
<value>Request is valid, applying migrations for context '{0}'.</value>
</data>
<data name="MigrationsEndPointMiddleware_ContextNotRegistered" xml:space="preserve">
<value>The context type '{0}' was not found in services. This usually means the context was not registered in services during startup. You probably want to call AddScoped&lt;{0}&gt;() inside the UseServices(...) call in your application startup code.</value>
</data>
<data name="MigrationsEndPointMiddleware_Exception" xml:space="preserve">
<value>An error occurred while applying the migrations for '{0}'. See InnerException for details.</value>
</data>
<data name="MigrationsEndPointMiddleware_InvalidContextType" xml:space="preserve">
<value>The context type '{0}' could not be loaded. Ensure this is the correct type name for the context you are trying to apply migrations for.</value>
</data>
<data name="MigrationsEndPointMiddleware_NoContextType" xml:space="preserve">
<value>No context type was specified. Ensure the form data from the request includes a contextTypeName value, specifying the context to apply migrations for.</value>
</data>
<data name="MigrationsEndPointMiddleware_RequestPathMatched" xml:space="preserve">
<value>Request path matched the path configured for this migrations endpoint ({0}). Attempting to process the migrations request.</value>
</data>
<data name="DatabaseErrorPage_Title" xml:space="preserve">
<value>A database operation failed while processing the request.</value>
</data>

View File

@ -13,22 +13,12 @@ using System.Linq
#line hidden
;
#line 3 "DatabaseErrorPage.cshtml"
using JetBrains.Annotations;
#line default
#line hidden
#line 4 "DatabaseErrorPage.cshtml"
using Microsoft.AspNet.Diagnostics.Entity
#line default
#line hidden
;
#line 5 "DatabaseErrorPage.cshtml"
using Microsoft.AspNet.Diagnostics.Entity.Utilities;
#line default
#line hidden
#line 6 "DatabaseErrorPage.cshtml"
#line 4 "DatabaseErrorPage.cshtml"
using Microsoft.AspNet.Diagnostics.Entity.Views
#line default
@ -38,20 +28,18 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
public class DatabaseErrorPage : Microsoft.AspNet.Diagnostics.Views.BaseView
{
#line 14 "DatabaseErrorPage.cshtml"
#line 11 "DatabaseErrorPage.cshtml"
private DatabaseErrorPageModel _model;
public DatabaseErrorPageModel Model { get; set; }
public virtual DatabaseErrorPageModel Model
public string UrlEncode(string content)
{
get { return _model; }
[param: NotNull]
set
{
Check.NotNull(value, "value");
return UrlEncoder.UrlEncode(content);
}
_model = value;
}
public string JavaScriptEncode(string content)
{
return JavaScriptStringEncoder.JavaScriptStringEncode(content);
}
#line default
@ -64,10 +52,9 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
#line 7 "DatabaseErrorPage.cshtml"
#line 5 "DatabaseErrorPage.cshtml"
Response.StatusCode = 500;
// TODO: Response.ReasonPhrase = "Internal Server Error";
Response.ContentType = "text/html";
Response.ContentLength = null; // Clear any prior Content-Length
@ -76,26 +63,21 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
WriteLiteral("<!DOCTYPE html>\r\n\r\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r" +
"\n <meta charset=\"utf-8\" />\r\n <title>Internal Server Error</title>\r\n <st" +
"yle>\r\n body {\r\n font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;\r\n font-size: .813em;\r\n line-height: 1.4em;\r\n color: #222;\r\n}\r\n\r\nh1, h2, h3, h4, h5 {\r\n font-weight: 100;\r\n}\r\n\r\nh1 {\r\n color: #44525e;\r\n margin: 15px 0 15px 0;\r\n}\r\n\r\nh2 {\r\n margin: 10px 5px 0 0;\r\n}\r\n\r\nh3 {\r\n color: #363636;\r\n margin: 5px 5px 0 0;\r\n}\r\n\r\ncode {\r\n font-family: Consolas, \"Courier New\", courier, monospace;\r\n}\r\n\r\na {\r\n color: #1ba1e2;\r\n text-decoration: none;\r\n}\r\n\r\n a:hover {\r\n color: #13709e;\r\n text-decoration: underline;\r\n }\r\n\r\nhr {\r\n border: 1px #ddd solid;\r\n}\r\n\r\nbody .titleerror {\r\n padding: 3px;\r\n}\r\n\r\n#applyMigrations {\r\n font-size: 14px;\r\n background: #44c5f2;\r\n color: #ffffff;\r\n display: inline-block;\r\n padding: 6px 12px;\r\n margin-bottom: 0;\r\n font-weight: normal;\r\n text-align: center;\r\n white-space: nowrap;\r\n vertical-align: middle;\r\n cursor: pointer;\r\n border: 1px solid transparent;\r\n}\r\n\r\n #applyMigrations:disabled {\r\n background-color: #a9e4f9;\r\n border-color: #44c5f2;\r\n }\r\n\r\n.error {\r\n color: red;\r\n}\r\n\r\n.expanded {\r\n display: block;\r\n}\r\n\r\n.collapsed {\r\n display: none;\r\n}\r\n\r\n ");
#line 37 "DatabaseErrorPage.cshtml"
Write(string.Empty);
#line default
#line hidden
WriteLiteral("\r\n </style>\r\n</head>\r\n<body>\r\n <h1>");
#line 41 "DatabaseErrorPage.cshtml"
"yle>\r\n body {\r\n font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;\r\n font-size: .813em;\r\n line-height: 1.4em;\r\n color: #222;\r\n}\r\n\r\nh1, h2, h3, h4, h5 {\r\n font-weight: 100;\r\n}\r\n\r\nh1 {\r\n color: #44525e;\r\n margin: 15px 0 15px 0;\r\n}\r\n\r\nh2 {\r\n margin: 10px 5px 0 0;\r\n}\r\n\r\nh3 {\r\n color: #363636;\r\n margin: 5px 5px 0 0;\r\n}\r\n\r\ncode {\r\n font-family: Consolas, \"Courier New\", courier, monospace;\r\n}\r\n\r\na {\r\n color: #1ba1e2;\r\n text-decoration: none;\r\n}\r\n\r\n a:hover {\r\n color: #13709e;\r\n text-decoration: underline;\r\n }\r\n\r\nhr {\r\n border: 1px #ddd solid;\r\n}\r\n\r\nbody .titleerror {\r\n padding: 3px;\r\n}\r\n\r\n#applyMigrations {\r\n font-size: 14px;\r\n background: #44c5f2;\r\n color: #ffffff;\r\n display: inline-block;\r\n padding: 6px 12px;\r\n margin-bottom: 0;\r\n font-weight: normal;\r\n text-align: center;\r\n white-space: nowrap;\r\n vertical-align: middle;\r\n cursor: pointer;\r\n border: 1px solid transparent;\r\n}\r\n\r\n #applyMigrations:disabled {\r\n background-color: #a9e4f9;\r\n border-color: #44c5f2;\r\n }\r\n\r\n.error {\r\n color: red;\r\n}\r\n\r\n.expanded {\r\n display: block;\r\n}\r\n\r\n.collapsed {\r\n display: none;\r\n}\r\n\r\n </style>\r\n</head>\r\n<body>\r\n" +
" <h1>");
#line 35 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_Title);
#line default
#line hidden
WriteLiteral("</h1>\r\n");
#line 42 "DatabaseErrorPage.cshtml"
#line 36 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 42 "DatabaseErrorPage.cshtml"
#line 36 "DatabaseErrorPage.cshtml"
if (Model.Options.ShowExceptionDetails)
{
@ -103,13 +85,13 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <p>\r\n");
#line 45 "DatabaseErrorPage.cshtml"
#line 39 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 45 "DatabaseErrorPage.cshtml"
#line 39 "DatabaseErrorPage.cshtml"
for (Exception ex = Model.Exception; ex != null; ex = ex.InnerException)
{
@ -117,39 +99,39 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <span>");
#line 47 "DatabaseErrorPage.cshtml"
#line 41 "DatabaseErrorPage.cshtml"
Write(ex.GetType().Name);
#line default
#line hidden
WriteLiteral(": ");
#line 47 "DatabaseErrorPage.cshtml"
#line 41 "DatabaseErrorPage.cshtml"
Write(ex.Message);
#line default
#line hidden
WriteLiteral("</span>\r\n <br />\r\n");
#line 49 "DatabaseErrorPage.cshtml"
#line 43 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral(" </p>\r\n <hr />\r\n");
#line 52 "DatabaseErrorPage.cshtml"
#line 46 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral("\r\n");
#line 54 "DatabaseErrorPage.cshtml"
#line 48 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 54 "DatabaseErrorPage.cshtml"
#line 48 "DatabaseErrorPage.cshtml"
if (!Model.DatabaseExists && !Model.PendingMigrations.Any())
{
@ -157,31 +139,31 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <h2>");
#line 56 "DatabaseErrorPage.cshtml"
#line 50 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_NoDbOrMigrationsTitle(Model.ContextType.Name));
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 57 "DatabaseErrorPage.cshtml"
#line 51 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_NoDbOrMigrationsInfo);
#line default
#line hidden
WriteLiteral("</p>\r\n <code> ");
#line 58 "DatabaseErrorPage.cshtml"
#line 52 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommand);
#line default
#line hidden
WriteLiteral(" </code>\r\n <br />\r\n <code> ");
#line 60 "DatabaseErrorPage.cshtml"
#line 54 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommand);
#line default
#line hidden
WriteLiteral(" </code>\r\n <hr />\r\n");
#line 62 "DatabaseErrorPage.cshtml"
#line 56 "DatabaseErrorPage.cshtml"
}
else if (Model.PendingMigrations.Any())
{
@ -190,25 +172,25 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <div>\r\n <h2>");
#line 66 "DatabaseErrorPage.cshtml"
#line 60 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingMigrationsTitle(Model.ContextType.Name));
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 67 "DatabaseErrorPage.cshtml"
#line 61 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingMigrationsInfo(Model.ContextType.Name));
#line default
#line hidden
WriteLiteral("</p>\r\n\r\n");
#line 69 "DatabaseErrorPage.cshtml"
#line 63 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 69 "DatabaseErrorPage.cshtml"
#line 63 "DatabaseErrorPage.cshtml"
if (Model.Options.ListMigrations)
{
@ -216,13 +198,13 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <ul>\r\n");
#line 72 "DatabaseErrorPage.cshtml"
#line 66 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 72 "DatabaseErrorPage.cshtml"
#line 66 "DatabaseErrorPage.cshtml"
foreach (var migration in Model.PendingMigrations)
{
@ -230,39 +212,138 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <li>");
#line 74 "DatabaseErrorPage.cshtml"
#line 68 "DatabaseErrorPage.cshtml"
Write(migration);
#line default
#line hidden
WriteLiteral("</li>\r\n");
#line 75 "DatabaseErrorPage.cshtml"
#line 69 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral(" </ul>\r\n");
#line 77 "DatabaseErrorPage.cshtml"
#line 71 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral("\r\n");
#line 73 "DatabaseErrorPage.cshtml"
#line default
#line hidden
#line 73 "DatabaseErrorPage.cshtml"
if (Model.Options.EnableMigrationCommands)
{
#line default
#line hidden
WriteLiteral(" <p>\r\n <button id=\"applyMigrations\" onclick=\"Ap" +
"plyMigrations()\">");
#line 76 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsButton);
#line default
#line hidden
WriteLiteral(@"</button>
<span id=""applyMigrationsError"" class=""error""></span>
<span id=""applyMigrationsSuccess""></span>
</p>
<script>
function ApplyMigrations() {
applyMigrations.disabled = true;
applyMigrationsError.innerHTML = """";
applyMigrations.innerHTML = """);
#line 84 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonRunning));
#line default
#line hidden
WriteLiteral("\";\r\n\r\n var req = new XMLHttpRequest();\r\n\r\n " +
" req.onload = function (e) {\r\n if (req.status " +
"=== 204) {\r\n applyMigrations.innerHTML = \"");
#line 90 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonDone));
#line default
#line hidden
WriteLiteral("\";\r\n applyMigrationsSuccess.innerHTML = \"");
#line 91 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_MigrationsAppliedRefresh));
#line default
#line hidden
WriteLiteral(@""";
} else {
ErrorApplyingMigrations();
}
};
req.onerror = function (e) {
ErrorApplyingMigrations();
};
var formBody = ""context=");
#line 101 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(UrlEncode(Model.ContextType.AssemblyQualifiedName)));
#line default
#line hidden
WriteLiteral("\";\r\n req.open(\"POST\", \"");
#line 102 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Model.Options.MigrationsEndPointPath.Value));
#line default
#line hidden
WriteLiteral(@""", true);
req.setRequestHeader(""Content-type"", ""application/x-www-form-urlencoded"");
req.setRequestHeader(""Content-length"", formBody.length);
req.setRequestHeader(""Connection"", ""close"");
req.send(formBody);
}
function ErrorApplyingMigrations() {
applyMigrations.innerHTML = """);
#line 110 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButton));
#line default
#line hidden
WriteLiteral("\";\r\n applyMigrationsError.innerHTML = \"");
#line 111 "DatabaseErrorPage.cshtml"
Write(JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsFailed));
#line default
#line hidden
WriteLiteral("\";\r\n applyMigrations.disabled = false;\r\n " +
" }\r\n </script>\r\n");
#line 115 "DatabaseErrorPage.cshtml"
}
#line default
#line hidden
WriteLiteral("\r\n <p>");
#line 79 "DatabaseErrorPage.cshtml"
#line 117 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_HowToApplyFromCmd);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 80 "DatabaseErrorPage.cshtml"
#line 118 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommand);
#line default
#line hidden
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#line 83 "DatabaseErrorPage.cshtml"
#line 121 "DatabaseErrorPage.cshtml"
}
else if (Model.PendingModelChanges)
{
@ -271,31 +352,31 @@ using Microsoft.AspNet.Diagnostics.Entity.Views
#line hidden
WriteLiteral(" <div>\r\n <h2>");
#line 87 "DatabaseErrorPage.cshtml"
#line 125 "DatabaseErrorPage.cshtml"
Write(Strings.FormatDatabaseErrorPage_PendingChangesTitle(Model.ContextType.Name));
#line default
#line hidden
WriteLiteral("</h2>\r\n <p>");
#line 88 "DatabaseErrorPage.cshtml"
#line 126 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_PendingChangesInfo);
#line default
#line hidden
WriteLiteral("</p>\r\n <code>");
#line 89 "DatabaseErrorPage.cshtml"
#line 127 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_AddMigrationCommand);
#line default
#line hidden
WriteLiteral("</code>\r\n <br />\r\n <code>");
#line 91 "DatabaseErrorPage.cshtml"
#line 129 "DatabaseErrorPage.cshtml"
Write(Strings.DatabaseErrorPage_ApplyMigrationsCommand);
#line default
#line hidden
WriteLiteral("</code>\r\n <hr />\r\n </div>\r\n");
#line 94 "DatabaseErrorPage.cshtml"
#line 132 "DatabaseErrorPage.cshtml"
}
#line default

View File

@ -1,29 +1,24 @@
@using System
@using System.Linq
@using JetBrains.Annotations;
@using Microsoft.AspNet.Diagnostics.Entity
@using Microsoft.AspNet.Diagnostics.Entity.Utilities;
@using Microsoft.AspNet.Diagnostics.Entity.Views
@{
Response.StatusCode = 500;
// TODO: Response.ReasonPhrase = "Internal Server Error";
Response.ContentType = "text/html";
Response.ContentLength = null; // Clear any prior Content-Length
}
@functions
{
private DatabaseErrorPageModel _model;
public DatabaseErrorPageModel Model { get; set; }
public virtual DatabaseErrorPageModel Model
public string UrlEncode(string content)
{
get { return _model; }
[param: NotNull]
set
{
Check.NotNull(value, "value");
return UrlEncoder.UrlEncode(content);
}
_model = value;
}
public string JavaScriptEncode(string content)
{
return JavaScriptStringEncoder.JavaScriptStringEncode(content);
}
}
<!DOCTYPE html>
@ -34,7 +29,6 @@
<title>Internal Server Error</title>
<style>
<%$ include: ErrorPage.css %>
@string.Empty
</style>
</head>
<body>
@ -76,6 +70,50 @@
</ul>
}
@if (Model.Options.EnableMigrationCommands)
{
<p>
<button id="applyMigrations" onclick="ApplyMigrations()">@Strings.DatabaseErrorPage_ApplyMigrationsButton</button>
<span id="applyMigrationsError" class="error"></span>
<span id="applyMigrationsSuccess"></span>
</p>
<script>
function ApplyMigrations() {
applyMigrations.disabled = true;
applyMigrationsError.innerHTML = "";
applyMigrations.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonRunning)";
var req = new XMLHttpRequest();
req.onload = function (e) {
if (req.status === 204) {
applyMigrations.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButtonDone)";
applyMigrationsSuccess.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_MigrationsAppliedRefresh)";
} else {
ErrorApplyingMigrations();
}
};
req.onerror = function (e) {
ErrorApplyingMigrations();
};
var formBody = "context=@JavaScriptEncode(UrlEncode(Model.ContextType.AssemblyQualifiedName))";
req.open("POST", "@JavaScriptEncode(Model.Options.MigrationsEndPointPath.Value)", true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.setRequestHeader("Content-length", formBody.length);
req.setRequestHeader("Connection", "close");
req.send(formBody);
}
function ErrorApplyingMigrations() {
applyMigrations.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsButton)";
applyMigrationsError.innerHTML = "@JavaScriptEncode(Strings.DatabaseErrorPage_ApplyMigrationsFailed)";
applyMigrations.disabled = false;
}
</script>
}
<p>@Strings.DatabaseErrorPage_HowToApplyFromCmd</p>
<code>@Strings.DatabaseErrorPage_ApplyMigrationsCommand</code>
<hr />

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using JetBrains.Annotations;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics.Entity.Utilities;
using System;
using System.Collections.Generic;

View File

@ -45,6 +45,16 @@ namespace Microsoft.AspNet.Diagnostics.Views
/// </summary>
protected IHtmlEncoder HtmlEncoder { get; set; }
/// <summary>
/// Url encoder used to encode content.
/// </summary>
protected IUrlEncoder UrlEncoder { get; set; }
/// <summary>
/// JavaScript encoder used to encode content.
/// </summary>
protected IJavaScriptStringEncoder JavaScriptStringEncoder { get; set; }
/// <summary>
/// Execute an individual request
/// </summary>
@ -56,6 +66,8 @@ namespace Microsoft.AspNet.Diagnostics.Views
Response = Context.Response;
Output = new StreamWriter(Response.Body, Encoding.UTF8, 4096, leaveOpen: true);
HtmlEncoder = context.ApplicationServices.GetHtmlEncoder();
UrlEncoder = context.ApplicationServices.GetUrlEncoder();
JavaScriptStringEncoder = context.ApplicationServices.GetJavaScriptStringEncoder();
await ExecuteAsync();
Output.Dispose();
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Net;
@ -17,6 +18,7 @@ using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.WebEncoders;
using Xunit;
namespace Microsoft.AspNet.Diagnostics.Entity.Tests
@ -121,12 +123,10 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
using (var db = context.ApplicationServices.GetService<BloggingContext>())
{
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
var db = context.ApplicationServices.GetService<BloggingContext>();
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
}
@ -155,12 +155,10 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
using (var db = context.ApplicationServices.GetService<BloggingContextWithMigrations>())
{
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
var db = context.ApplicationServices.GetService<BloggingContextWithMigrations>();
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
}
@ -186,19 +184,104 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
using (var db = context.ApplicationServices.GetService<BloggingContextWithPendingModelChanges>())
{
var db = context.ApplicationServices.GetService<BloggingContextWithPendingModelChanges>();
db.Database.Migrate();
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
}
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task Error_page_then_apply_migrations()
{
TestServer server = SetupTestServer<BloggingContextWithMigrations, ApplyMigrationsMiddleware>();
var client = server.CreateClient();
var expectedMigrationsEndpoint = "/ApplyDatabaseMigrations";
var expectedContextType = typeof(BloggingContextWithMigrations).AssemblyQualifiedName;
// Step One: Initial request with database failure
HttpResponseMessage response = await client.GetAsync("http://localhost/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
// Ensure the url we're going to test is what the page is using in it's JavaScript
var javaScriptEncoder = new JavaScriptStringEncoder();
Assert.Contains("req.open(\"POST\", \"" + JavaScriptEncode(expectedMigrationsEndpoint) + "\", true);", content);
Assert.Contains("var formBody = \"context=" + JavaScriptEncode(UrlEncode(expectedContextType)) + "\";", content);
// Step Two: Request to migrations endpoint
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("context", expectedContextType)
});
response = await client.PostAsync("http://localhost" + expectedMigrationsEndpoint, formData);
content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
// Step Three: Successful request after migrations applied
response = await client.GetAsync("http://localhost/");
content = await response.Content.ReadAsStringAsync();
Assert.Equal("Saved a Blog", content);
}
class ApplyMigrationsMiddleware
{
public ApplyMigrationsMiddleware(RequestDelegate next)
{ }
public virtual async Task Invoke(HttpContext context)
{
var db = context.ApplicationServices.GetService<BloggingContextWithMigrations>();
db.Blogs.Add(new Blog());
db.SaveChanges();
await context.Response.WriteAsync("Saved a Blog");
}
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task Customize_migrations_end_point()
{
var migrationsEndpoint = "/MyCustomEndPoints/ApplyMyMigrationsHere";
using (var database = SqlServerTestStore.CreateScratch())
{
var server = TestServer.Create(app =>
{
app.UseDatabaseErrorPage(options =>
{
options.EnableAll();
options.MigrationsEndPointPath = new PathString(migrationsEndpoint);
});
app.UseMiddleware<PendingMigrationsMiddleware>();
},
services =>
{
services.AddEntityFramework().AddSqlServer();
services.AddScoped<BloggingContextWithMigrations>();
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(database.ConnectionString);
services.AddInstance<DbContextOptions>(optionsBuilder.Options);
});
HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("req.open(\"POST\", \"" + JavaScriptEncode(migrationsEndpoint) + "\", true);", content);
}
}
[Fact]
public async Task Pass_thru_when_context_not_in_services()
{
using (var database = SqlServerTestStore.CreateScratch())
@ -242,12 +325,10 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
var options = context.ApplicationServices.GetService<DbContextOptions>();
using (var db = new BloggingContext(context.ApplicationServices, options))
{
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
var db = new BloggingContext(context.ApplicationServices, options);
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
}
@ -276,12 +357,10 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
using (var db = context.ApplicationServices.GetService<BloggingContextWithSnapshotThatThrows>())
{
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
var db = context.ApplicationServices.GetService<BloggingContextWithSnapshotThatThrows>();
db.Blogs.Add(new Blog());
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
}
@ -305,18 +384,16 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public virtual Task Invoke(HttpContext context)
{
using (var db = context.ApplicationServices.GetService<BloggingContext>())
var db = context.ApplicationServices.GetService<BloggingContext>();
db.Blogs.Add(new Blog());
try
{
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);
}
db.SaveChanges();
throw new Exception("SaveChanges should have thrown");
}
catch (Exception ex)
{
throw new Exception("I wrapped your exception", ex);
}
}
}
@ -357,5 +434,17 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
});
}
}
private static UrlEncoder _urlEncoder = new UrlEncoder();
private static string UrlEncode(string content)
{
return _urlEncoder.UrlEncode(content);
}
private static JavaScriptStringEncoder _javaScriptEncoder = new JavaScriptStringEncoder();
private static string JavaScriptEncode(string content)
{
return _javaScriptEncoder.JavaScriptStringEncode(content);
}
}
}

View File

@ -0,0 +1,209 @@
// 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.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics.Entity.Tests.Helpers;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.TestHost;
using Microsoft.AspNet.Testing.xunit;
using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Data.Entity.Migrations;
using Microsoft.Data.Entity.Storage;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNet.Diagnostics.Entity.Tests
{
public class MigrationsEndPointMiddlewareTest
{
[Fact]
public async Task Non_migration_requests_pass_thru()
{
TestServer server = TestServer.Create(app => app
.UseMigrationsEndPoint()
.UseMiddleware<SuccessMiddleware>());
HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/");
Assert.Equal("Request Handled", await response.Content.ReadAsStringAsync());
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
class SuccessMiddleware
{
public SuccessMiddleware(RequestDelegate next)
{ }
public virtual async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync("Request Handled");
context.Response.StatusCode = (int)HttpStatusCode.OK;
}
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task Migration_request_default_path()
{
await Migration_request(useCustomPath: false);
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task Migration_request_custom_path()
{
await Migration_request(useCustomPath: true);
}
private async Task Migration_request(bool useCustomPath)
{
using (var database = SqlServerTestStore.CreateScratch())
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(database.ConnectionString);
var path = useCustomPath ? new PathString("/EndPoints/ApplyMyMigrations") : MigrationsEndPointOptions.DefaultPath;
TestServer server = TestServer.Create(app =>
{
if (useCustomPath)
{
app.UseMigrationsEndPoint(o => o.Path = path);
}
else
{
app.UseMigrationsEndPoint();
}
},
services =>
{
services.AddEntityFramework().AddSqlServer();
services.AddScoped<BloggingContextWithMigrations>();
services.AddInstance(optionsBuilder.Options);
});
using (var db = BloggingContextWithMigrations.CreateWithoutExternalServiceProvider(optionsBuilder.Options))
{
var databaseCreator = ((IAccessor<IServiceProvider>)db).GetService<IRelationalDatabaseCreator>();
Assert.False(databaseCreator.Exists());
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("context", typeof(BloggingContextWithMigrations).AssemblyQualifiedName)
});
HttpResponseMessage response = await server.CreateClient()
.PostAsync("http://localhost" + path, formData);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.True(databaseCreator.Exists());
var historyRepository = ((IAccessor<IServiceProvider>)db).Service.GetRequiredService<IHistoryRepository>();
var appliedMigrations = historyRepository.GetAppliedMigrations();
Assert.Equal(2, appliedMigrations.Count);
Assert.Equal("111111111111111_MigrationOne", appliedMigrations.ElementAt(0).MigrationId);
Assert.Equal("222222222222222_MigrationTwo", appliedMigrations.ElementAt(1).MigrationId);
}
}
}
[Fact]
public async Task Context_type_not_specified()
{
var server = TestServer.Create(app =>
{
app.UseMigrationsEndPoint();
});
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>());
var response = await server.CreateClient().PostAsync("http://localhost" + MigrationsEndPointOptions.DefaultPath, formData);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_NoContextType"), content);
Assert.True(content.Length > 512);
}
[Fact]
public async Task Invalid_context_type_specified()
{
var server = TestServer.Create(app =>
{
app.UseMigrationsEndPoint();
});
var typeName = "You won't find this type ;)";
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("context", typeName)
});
var response = await server.CreateClient().PostAsync("http://localhost" + MigrationsEndPointOptions.DefaultPath, formData);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_InvalidContextType", typeName), content);
Assert.True(content.Length > 512);
}
[Fact]
public async Task Context_not_registered_in_services()
{
var server = TestServer.Create(
app => app.UseMigrationsEndPoint(),
services => services.AddEntityFramework().AddSqlServer());
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("context", typeof(BloggingContext).AssemblyQualifiedName)
});
var response = await server.CreateClient().PostAsync("http://localhost" + MigrationsEndPointOptions.DefaultPath, formData);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_ContextNotRegistered", typeof(BloggingContext)), content);
Assert.True(content.Length > 512);
}
[ConditionalTheory]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task Exception_while_applying_migrations()
{
using (var database = SqlServerTestStore.CreateScratch())
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(database.ConnectionString);
TestServer server = TestServer.Create(
app => app.UseMigrationsEndPoint(),
services =>
{
services.AddEntityFramework().AddSqlServer();
services.AddScoped<BloggingContextWithSnapshotThatThrows>();
services.AddInstance(optionsBuilder.Options);
});
var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("context", typeof(BloggingContextWithSnapshotThatThrows).AssemblyQualifiedName)
});
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
await server.CreateClient().PostAsync("http://localhost" + MigrationsEndPointOptions.DefaultPath, formData));
Assert.StartsWith(StringsHelpers.GetResourceString("FormatMigrationsEndPointMiddleware_Exception", typeof(BloggingContextWithSnapshotThatThrows)), ex.Message);
Assert.Equal("Welcome to the invalid migration!", ex.InnerException.Message);
}
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Builder;
using Xunit;
namespace Microsoft.AspNet.Diagnostics.Entity.Tests
@ -8,39 +9,78 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
public class DatabaseErrorPageOptionsTest
{
[Fact]
public void Default_visibility_is_false()
public void Everything_disabled_by_default()
{
var options = new DatabaseErrorPageOptions();
Assert.False(options.ShowExceptionDetails);
Assert.False(options.ListMigrations);
Assert.False(options.EnableMigrationCommands);
Assert.Equal(string.Empty, options.MigrationsEndPointPath);
}
[Fact]
public void ShowAll_shows_all_errors()
public void EnableAll_enables_everything()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
Assert.True(options.ShowExceptionDetails);
Assert.True(options.ListMigrations);
Assert.True(options.EnableMigrationCommands);
Assert.Equal(MigrationsEndPointOptions.DefaultPath, options.MigrationsEndPointPath);
}
[Fact]
public void ShowExceptionDetails_is_respected()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.ShowExceptionDetails = false;
Assert.False(options.ShowExceptionDetails);
Assert.True(options.ListMigrations);
Assert.True(options.EnableMigrationCommands);
Assert.Equal(MigrationsEndPointOptions.DefaultPath, options.MigrationsEndPointPath);
}
[Fact]
public void ListMigrations_is_respected()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.ListMigrations = false;
Assert.True(options.ShowExceptionDetails);
Assert.False(options.ListMigrations);
Assert.True(options.EnableMigrationCommands);
Assert.Equal(MigrationsEndPointOptions.DefaultPath, options.MigrationsEndPointPath);
}
[Fact]
public void EnableMigrationCommands_is_respected()
{
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.EnableMigrationCommands = false;
Assert.True(options.ShowExceptionDetails);
Assert.True(options.ListMigrations);
Assert.False(options.EnableMigrationCommands);
Assert.Equal(MigrationsEndPointOptions.DefaultPath, options.MigrationsEndPointPath);
}
[Fact]
public void MigrationsEndPointPath_is_respected()
{
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.MigrationsEndPointPath = "/test";
Assert.True(options.ShowExceptionDetails);
Assert.True(options.ListMigrations);
Assert.True(options.EnableMigrationCommands);
Assert.Equal("/test", options.MigrationsEndPointPath);
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Builder;
using Microsoft.AspNet.Diagnostics.Entity.Tests.Helpers;
using Microsoft.AspNet.Diagnostics.Entity.Views;
using Microsoft.AspNet.Http;
@ -19,7 +20,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task No_database_or_migrations_only_displays_scaffold_first_migration()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -39,7 +41,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task No_database_with_migrations_only_displays_apply_migrations()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -59,7 +62,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task Existing_database_with_migrations_only_displays_apply_migrations()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -79,7 +83,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task Existing_database_with_migrations_and_pending_model_changes_only_displays_apply_migrations()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -99,7 +104,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task Pending_model_changes_only_displays_scaffold_next_migration()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -119,7 +125,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task Exception_details_are_displayed()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -137,7 +144,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task Inner_exception_details_are_displayed()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
@ -156,7 +164,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task ShowExceptionDetails_is_respected()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.ShowExceptionDetails = false;
var model = new DatabaseErrorPageModel(
@ -175,7 +184,8 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
[Fact]
public async Task ListMigrations_is_respected()
{
var options = DatabaseErrorPageOptions.ShowAll;
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.ListMigrations = false;
var model = new DatabaseErrorPageModel(
@ -191,6 +201,47 @@ namespace Microsoft.AspNet.Diagnostics.Entity.Tests
Assert.DoesNotContain("111_MigrationOne", content);
}
[Fact]
public async Task EnableMigrationCommands_is_respected()
{
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.EnableMigrationCommands = false;
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);
Assert.DoesNotContain(options.MigrationsEndPointPath.Value, content);
}
[Fact]
public async Task MigrationsEndPointPath_is_respected()
{
var options = new DatabaseErrorPageOptions();
options.EnableAll();
options.MigrationsEndPointPath = "/HitThisEndPoint";
var model = new DatabaseErrorPageModel(
contextType: typeof(BloggingContext),
exception: new Exception(),
databaseExists: true,
pendingModelChanges: false,
pendingMigrations: new string[] { "111_MigrationOne" },
options: options);
var content = await ExecutePage(options, model);
Assert.Contains(options.MigrationsEndPointPath.Value, content);
}
private static async Task<string> ExecutePage(DatabaseErrorPageOptions options, DatabaseErrorPageModel model)
{
var page = new DatabaseErrorPage();