#77 Catch startup exceptions and show them in the browser.
This commit is contained in:
parent
520fc2b5fd
commit
a9e7948d72
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Features;
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Features.Internal;
|
using Microsoft.AspNet.Http.Features.Internal;
|
||||||
using Microsoft.AspNet.Server.Features;
|
using Microsoft.AspNet.Server.Features;
|
||||||
|
using Microsoft.Dnx.Runtime;
|
||||||
using Microsoft.Framework.Configuration;
|
using Microsoft.Framework.Configuration;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
|
|
@ -24,11 +25,13 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
{
|
{
|
||||||
// This is defined by IIS's HttpPlatformHandler.
|
// This is defined by IIS's HttpPlatformHandler.
|
||||||
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
|
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
|
||||||
|
private static readonly string DetailedErrors = "Hosting:DetailedErrors";
|
||||||
|
|
||||||
private readonly IServiceCollection _applicationServiceCollection;
|
private readonly IServiceCollection _applicationServiceCollection;
|
||||||
private readonly IStartupLoader _startupLoader;
|
private readonly IStartupLoader _startupLoader;
|
||||||
private readonly ApplicationLifetime _applicationLifetime;
|
private readonly ApplicationLifetime _applicationLifetime;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly bool _captureStartupErrors;
|
||||||
|
|
||||||
private IServiceProvider _applicationServices;
|
private IServiceProvider _applicationServices;
|
||||||
|
|
||||||
|
|
@ -45,7 +48,8 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
public HostingEngine(
|
public HostingEngine(
|
||||||
IServiceCollection appServices,
|
IServiceCollection appServices,
|
||||||
IStartupLoader startupLoader,
|
IStartupLoader startupLoader,
|
||||||
IConfiguration config)
|
IConfiguration config,
|
||||||
|
bool captureStartupErrors)
|
||||||
{
|
{
|
||||||
if (appServices == null)
|
if (appServices == null)
|
||||||
{
|
{
|
||||||
|
|
@ -65,6 +69,7 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
_config = config;
|
_config = config;
|
||||||
_applicationServiceCollection = appServices;
|
_applicationServiceCollection = appServices;
|
||||||
_startupLoader = startupLoader;
|
_startupLoader = startupLoader;
|
||||||
|
_captureStartupErrors = captureStartupErrors;
|
||||||
_applicationLifetime = new ApplicationLifetime();
|
_applicationLifetime = new ApplicationLifetime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,8 +84,6 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
|
|
||||||
public virtual IApplication Start()
|
public virtual IApplication Start()
|
||||||
{
|
{
|
||||||
EnsureApplicationServices();
|
|
||||||
|
|
||||||
var application = BuildApplication();
|
var application = BuildApplication();
|
||||||
|
|
||||||
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
|
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
|
||||||
|
|
@ -174,6 +177,67 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestDelegate BuildApplication()
|
private RequestDelegate BuildApplication()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EnsureApplicationServices();
|
||||||
|
EnsureServer();
|
||||||
|
|
||||||
|
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
|
||||||
|
var builder = builderFactory.CreateBuilder(_serverInstance);
|
||||||
|
builder.ApplicationServices = _applicationServices;
|
||||||
|
|
||||||
|
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
|
||||||
|
var configure = Startup.ConfigureDelegate;
|
||||||
|
foreach (var filter in startupFilters)
|
||||||
|
{
|
||||||
|
configure = filter.Configure(configure);
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(builder);
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!_captureStartupErrors)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureApplicationServices may have failed due to a missing or throwing Startup class.
|
||||||
|
if (_applicationServices == null)
|
||||||
|
{
|
||||||
|
_applicationServices = _applicationServiceCollection.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureServer();
|
||||||
|
|
||||||
|
// Write errors to standard out so they can be retrieved when not in development mode.
|
||||||
|
Console.Out.WriteLine("Application startup exception: " + ex.ToString());
|
||||||
|
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
|
||||||
|
logger.LogError("Application startup exception", ex);
|
||||||
|
|
||||||
|
// Generate an HTML error page.
|
||||||
|
var runtimeEnv = _applicationServices.GetRequiredService<IRuntimeEnvironment>();
|
||||||
|
var hostingEnv = _applicationServices.GetRequiredService<IHostingEnvironment>();
|
||||||
|
var showDetailedErrors = hostingEnv.IsDevelopment()
|
||||||
|
|| string.Equals("true", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals("1", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase);
|
||||||
|
var errorBytes = StartupExceptionPage.GenerateErrorHtml(showDetailedErrors, runtimeEnv, ex);
|
||||||
|
|
||||||
|
return context =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
context.Response.Headers["Cache-Control"] = "private, max-age=0";
|
||||||
|
context.Response.ContentType = "text/html; charset=utf-8";
|
||||||
|
context.Response.ContentLength = errorBytes.Length;
|
||||||
|
return context.Response.Body.WriteAsync(errorBytes, 0, errorBytes.Length);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureServer()
|
||||||
{
|
{
|
||||||
if (ServerFactory == null)
|
if (ServerFactory == null)
|
||||||
{
|
{
|
||||||
|
|
@ -186,37 +250,25 @@ namespace Microsoft.AspNet.Hosting.Internal
|
||||||
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
|
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
_serverInstance = ServerFactory.Initialize(_config);
|
if (_serverInstance == null)
|
||||||
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
|
|
||||||
var builder = builderFactory.CreateBuilder(_serverInstance);
|
|
||||||
builder.ApplicationServices = _applicationServices;
|
|
||||||
|
|
||||||
var addresses = builder.ServerFeatures?.Get<IServerAddressesFeature>()?.Addresses;
|
|
||||||
if (addresses != null && !addresses.IsReadOnly)
|
|
||||||
{
|
{
|
||||||
var port = _config[ServerPort];
|
_serverInstance = ServerFactory.Initialize(_config);
|
||||||
if (!string.IsNullOrEmpty(port))
|
var addresses = _serverInstance?.Get<IServerAddressesFeature>()?.Addresses;
|
||||||
|
if (addresses != null && !addresses.IsReadOnly)
|
||||||
{
|
{
|
||||||
addresses.Add("http://localhost:" + port);
|
var port = _config[ServerPort];
|
||||||
}
|
if (!string.IsNullOrEmpty(port))
|
||||||
|
{
|
||||||
|
addresses.Add("http://localhost:" + port);
|
||||||
|
}
|
||||||
|
|
||||||
// Provide a default address if there aren't any configured.
|
// Provide a default address if there aren't any configured.
|
||||||
if (addresses.Count == 0)
|
if (addresses.Count == 0)
|
||||||
{
|
{
|
||||||
addresses.Add("http://localhost:5000");
|
addresses.Add("http://localhost:5000");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
|
|
||||||
var configure = Startup.ConfigureDelegate;
|
|
||||||
foreach (var filter in startupFilters)
|
|
||||||
{
|
|
||||||
configure = filter.Configure(configure);
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(builder);
|
|
||||||
|
|
||||||
return builder.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetRequestIdentifier(HttpContext httpContext)
|
private string GetRequestIdentifier(HttpContext httpContext)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Hosting
|
||||||
builder.AddCommandLine(args);
|
builder.AddCommandLine(args);
|
||||||
var config = builder.Build();
|
var config = builder.Build();
|
||||||
|
|
||||||
var host = new WebHostBuilder(_serviceProvider, config).Build();
|
var host = new WebHostBuilder(_serviceProvider, config, captureStartupErrors: true).Build();
|
||||||
using (var app = host.Start())
|
using (var app = host.Start())
|
||||||
{
|
{
|
||||||
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();
|
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,520 @@
|
||||||
|
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Dnx.Compilation;
|
||||||
|
using Microsoft.Dnx.Runtime;
|
||||||
|
using Microsoft.Framework.WebEncoders;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Startup
|
||||||
|
{
|
||||||
|
internal static class StartupExceptionPage
|
||||||
|
{
|
||||||
|
private const int MaxCompilationErrorsToShow = 20;
|
||||||
|
|
||||||
|
private static readonly string _errorPageFormatString = GetResourceString("GenericError.html", escapeBraces: true);
|
||||||
|
private static readonly string _errorMessageFormatString = GetResourceString("GenericError_Message.html");
|
||||||
|
private static readonly string _errorExceptionFormatString = GetResourceString("GenericError_Exception.html");
|
||||||
|
private static readonly string _errorFooterFormatString = GetResourceString("GenericError_Footer.html");
|
||||||
|
private static readonly string _compilationExceptionFormatString = GetResourceString("Compilation_Exception.html");
|
||||||
|
|
||||||
|
public static byte[] GenerateErrorHtml(bool showDetails, IRuntimeEnvironment runtimeEnvironment, params object[] errorDetails)
|
||||||
|
{
|
||||||
|
if (!showDetails)
|
||||||
|
{
|
||||||
|
errorDetails = new[] { "An error occurred while starting the application." };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the message for each error
|
||||||
|
var wasSourceCodeWrittenOntoPage = false;
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var rawExceptionDetails = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (object error in errorDetails ?? new object[0])
|
||||||
|
{
|
||||||
|
var ex = error as Exception;
|
||||||
|
if (ex == null && error is ExceptionDispatchInfo)
|
||||||
|
{
|
||||||
|
ex = ((ExceptionDispatchInfo)error).SourceException;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
var flattenedExceptions = FlattenAndReverseExceptionTree(ex);
|
||||||
|
|
||||||
|
var compilationException = flattenedExceptions.OfType<ICompilationException>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (compilationException != null)
|
||||||
|
{
|
||||||
|
WriteException(compilationException, builder, ref wasSourceCodeWrittenOntoPage);
|
||||||
|
|
||||||
|
var compilationErrorMessages = compilationException.CompilationFailures
|
||||||
|
.SelectMany(f => f.Messages.Select(m => m.FormattedMessage))
|
||||||
|
.Take(MaxCompilationErrorsToShow);
|
||||||
|
|
||||||
|
WriteRawExceptionDetails("Show raw compilation error details", compilationErrorMessages, rawExceptionDetails);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var innerEx in flattenedExceptions)
|
||||||
|
{
|
||||||
|
WriteException(innerEx, builder, ref wasSourceCodeWrittenOntoPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteRawExceptionDetails("Show raw exception details", new[] { ex.ToString() }, rawExceptionDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var message = Convert.ToString(error, CultureInfo.InvariantCulture);
|
||||||
|
WriteMessage(message, builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the footer
|
||||||
|
var footer = showDetails ? GenerateFooterEncoded(runtimeEnvironment) : null;
|
||||||
|
|
||||||
|
// And generate the full markup
|
||||||
|
return Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, _errorPageFormatString, builder, rawExceptionDetails, footer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildCodeSnippetDiv(StackFrame frame)
|
||||||
|
{
|
||||||
|
var filename = frame.GetFileName();
|
||||||
|
if (!string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
int failingLineNumber = frame.GetFileLineNumber();
|
||||||
|
if (failingLineNumber >= 1)
|
||||||
|
{
|
||||||
|
var lines = GetFailingCallSiteInFile(filename, failingLineNumber);
|
||||||
|
if (lines != null)
|
||||||
|
{
|
||||||
|
return @"<div class=""codeSnippet"">"
|
||||||
|
+ @"<div class=""filename""><code>" + HtmlEncodeAndReplaceLineBreaks(filename) + "</code></div>" + Environment.NewLine
|
||||||
|
+ string.Join(Environment.NewLine, lines) + "</div>" + Environment.NewLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildLineForStackFrame(StackFrame frame)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder("<pre>");
|
||||||
|
var method = frame.GetMethod();
|
||||||
|
|
||||||
|
// Special case: no method available
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, write the type name
|
||||||
|
var type = method.DeclaringType;
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
// Special-case ExceptionDispatchInfo.Throw()
|
||||||
|
if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw")
|
||||||
|
{
|
||||||
|
return @"<pre><span class=""faded"">--- exception rethrown ---</span></pre>";
|
||||||
|
}
|
||||||
|
|
||||||
|
string prefix, friendlyName;
|
||||||
|
SplitTypeIntoPrefixAndFriendlyName(type, out prefix, out friendlyName);
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, @"<span class=""faded"">at {0}</span>", HtmlEncodeAndReplaceLineBreaks(prefix));
|
||||||
|
builder.Append(HtmlEncodeAndReplaceLineBreaks(friendlyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, write the method signature
|
||||||
|
builder.Append(HtmlEncodeAndReplaceLineBreaks("." + method.Name));
|
||||||
|
|
||||||
|
// Is this method generic?
|
||||||
|
if (method.IsGenericMethod)
|
||||||
|
{
|
||||||
|
builder.Append(HtmlEncodeAndReplaceLineBreaks(BuildMethodGenericParametersUnescaped(method)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build method parameters
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, @"<span class=""faded"">{0}</span>", HtmlEncodeAndReplaceLineBreaks(BuildMethodParametersUnescaped(method)));
|
||||||
|
|
||||||
|
// Do we have source information for this frame?
|
||||||
|
if (frame.GetILOffset() != -1)
|
||||||
|
{
|
||||||
|
var filename = frame.GetFileName();
|
||||||
|
if (!string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, " in {0}:line {1:D}", HtmlEncodeAndReplaceLineBreaks(filename), frame.GetFileLineNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish
|
||||||
|
builder.Append("</pre>");
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildMethodGenericParametersUnescaped(MethodBase method)
|
||||||
|
{
|
||||||
|
Debug.Assert(method.IsGenericMethod);
|
||||||
|
return "<" + string.Join(", ", method.GetGenericArguments().Select(PrettyPrintTypeName)) + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildMethodParametersUnescaped(MethodBase method)
|
||||||
|
{
|
||||||
|
return "(" + string.Join(", ", method.GetParameters().Select(p => {
|
||||||
|
Type parameterType = p.ParameterType;
|
||||||
|
return ((parameterType != null) ? PrettyPrintTypeName(parameterType) : "?") + " " + p.Name;
|
||||||
|
})) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildCodeSnippetDiv(CompilationFailure failure,
|
||||||
|
StringBuilder builder,
|
||||||
|
ref int totalErrorsShown)
|
||||||
|
{
|
||||||
|
const int NumContextLines = 3;
|
||||||
|
var fileName = failure.SourceFilePath;
|
||||||
|
if (totalErrorsShown < MaxCompilationErrorsToShow &&
|
||||||
|
!string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
builder.Append(@"<div class=""codeSnippet"">")
|
||||||
|
.AppendFormat(@"<div class=""filename""><code>{0}</code></div>", HtmlEncodeAndReplaceLineBreaks(fileName))
|
||||||
|
.AppendLine();
|
||||||
|
|
||||||
|
IEnumerable<string> fileContent;
|
||||||
|
if (string.IsNullOrEmpty(failure.SourceFileContent))
|
||||||
|
{
|
||||||
|
fileContent = File.ReadLines(fileName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fileContent = failure.SourceFileContent.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||||
|
}
|
||||||
|
foreach (var message in failure.Messages)
|
||||||
|
{
|
||||||
|
if (totalErrorsShown++ > MaxCompilationErrorsToShow)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalErrorsShown > 1)
|
||||||
|
{
|
||||||
|
builder.AppendLine("<br />");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(@"<div class=""error-message"">")
|
||||||
|
.Append(HtmlEncodeAndReplaceLineBreaks(message.Message))
|
||||||
|
.Append("</div>");
|
||||||
|
|
||||||
|
// StartLine and EndLine are 1-based
|
||||||
|
var startLine = message.StartLine - 1;
|
||||||
|
var endLine = message.EndLine - 1;
|
||||||
|
var preContextIndex = Math.Max(startLine - NumContextLines, 0);
|
||||||
|
var index = preContextIndex + 1;
|
||||||
|
foreach (var line in fileContent.Skip(preContextIndex).Take(startLine - preContextIndex))
|
||||||
|
{
|
||||||
|
builder.Append(@"<div class=""line faded"">")
|
||||||
|
.AppendFormat(@"<span class=""line-number"">{0}</span><code>", index++)
|
||||||
|
.Append(HtmlEncodeAndReplaceLineBreaks(line))
|
||||||
|
.AppendLine("</code></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
var numErrorLines = 1 + Math.Max(0, endLine - startLine);
|
||||||
|
foreach (var line in fileContent.Skip(startLine).Take(numErrorLines))
|
||||||
|
{
|
||||||
|
builder.Append(@"<div class=""line error"">")
|
||||||
|
.AppendFormat(@"<span class=""line-number"">{0}</span><code>", index++)
|
||||||
|
.Append(HtmlEncodeAndReplaceLineBreaks(line))
|
||||||
|
.AppendLine("</code></div>");
|
||||||
|
}
|
||||||
|
foreach (var line in fileContent.Skip(message.EndLine).Take(NumContextLines))
|
||||||
|
{
|
||||||
|
builder.Append(@"<div class=""line faded"">")
|
||||||
|
.AppendFormat(@"<span class=""line-number"">{0}</span><code>", index++)
|
||||||
|
.Append(HtmlEncodeAndReplaceLineBreaks(line))
|
||||||
|
.AppendLine("</code></div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("</div>"); // Close codeSnippet div
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetResourceString(string name, bool escapeBraces = false)
|
||||||
|
{
|
||||||
|
// '{' and '}' are special in CSS, so we use "[[[0]]]" instead for {0} (and so on).
|
||||||
|
var assembly = typeof(StartupExceptionPage).GetTypeInfo().Assembly;
|
||||||
|
var resourceName = assembly.GetName().Name + ".compiler.resources." + name;
|
||||||
|
var manifestStream = assembly.GetManifestResourceStream(resourceName);
|
||||||
|
var formatString = new StreamReader(manifestStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false).ReadToEnd();
|
||||||
|
if (escapeBraces)
|
||||||
|
{
|
||||||
|
formatString = formatString.Replace("{", "{{").Replace("}", "}}").Replace("[[[", "{").Replace("]]]", "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> GetFailingCallSiteInFile(string filename, int failedLineNumber)
|
||||||
|
{
|
||||||
|
// We figure out the [first, last] range of lines to read from the file.
|
||||||
|
var firstLineNumber = failedLineNumber - 2;
|
||||||
|
firstLineNumber = Math.Max(1, firstLineNumber);
|
||||||
|
var lastLineNumber = failedLineNumber + 2;
|
||||||
|
lastLineNumber = Math.Max(lastLineNumber, failedLineNumber);
|
||||||
|
|
||||||
|
// Figure out how many characters lastLineNumber will take to print.
|
||||||
|
var lastLineNumberCharLength = lastLineNumber.ToString("D", CultureInfo.InvariantCulture).Length;
|
||||||
|
|
||||||
|
var errorSubContents = new List<string>();
|
||||||
|
var didReadFailingLine = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var thisLineNum = 0;
|
||||||
|
foreach (var line in File.ReadLines(filename))
|
||||||
|
{
|
||||||
|
thisLineNum++;
|
||||||
|
|
||||||
|
// Are we within the correct range?
|
||||||
|
if (thisLineNum < firstLineNumber)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (thisLineNum > lastLineNumber)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodedLine = HtmlEncodeAndReplaceLineBreaks("Line "
|
||||||
|
+ thisLineNum.ToString("D", CultureInfo.InvariantCulture).PadLeft(lastLineNumberCharLength)
|
||||||
|
+ ": "
|
||||||
|
+ line);
|
||||||
|
|
||||||
|
if (thisLineNum == failedLineNumber)
|
||||||
|
{
|
||||||
|
didReadFailingLine = true;
|
||||||
|
errorSubContents.Add(@"<div class=""line error""><code>" + encodedLine + "</code></div>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorSubContents.Add(@"<div class=""line""><code>" + encodedLine + "</code></div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If there's an error for any reason, don't show source.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (didReadFailingLine) ? errorSubContents : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PrettyPrintTypeName(Type t)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
|
var name = t.Name;
|
||||||
|
|
||||||
|
// Degenerate case
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
name = "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle generic types
|
||||||
|
if (t.GetTypeInfo().IsGenericType)
|
||||||
|
{
|
||||||
|
// strip off the CLR generic type marker if it exists
|
||||||
|
var indexOfGenericTypeMarker = name.LastIndexOf('`');
|
||||||
|
if (indexOfGenericTypeMarker >= 0)
|
||||||
|
{
|
||||||
|
name = name.Substring(0, indexOfGenericTypeMarker);
|
||||||
|
name += "<" + string.Join(", ", t.GetGenericArguments().Select(PrettyPrintTypeName)) + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle nested types
|
||||||
|
if (!t.IsGenericParameter)
|
||||||
|
{
|
||||||
|
var containerType = t.DeclaringType;
|
||||||
|
if (containerType != null)
|
||||||
|
{
|
||||||
|
name = PrettyPrintTypeName(containerType) + "." + name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If anything at all goes wrong, fall back to the full type name so that we don't crash the server.
|
||||||
|
return t.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SplitTypeIntoPrefixAndFriendlyName(Type type, out string prefix, out string friendlyName)
|
||||||
|
{
|
||||||
|
prefix = type.Namespace;
|
||||||
|
friendlyName = PrettyPrintTypeName(type);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(friendlyName) && !string.IsNullOrEmpty(prefix))
|
||||||
|
{
|
||||||
|
prefix += ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateFooterEncoded(IRuntimeEnvironment environment)
|
||||||
|
{
|
||||||
|
var runtimeType = HtmlEncodeAndReplaceLineBreaks(environment.RuntimeType);
|
||||||
|
#if DNXCORE50
|
||||||
|
var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly;
|
||||||
|
var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString();
|
||||||
|
var clrVersion = HtmlEncodeAndReplaceLineBreaks(assemblyVersion);
|
||||||
|
#else
|
||||||
|
var clrVersion = HtmlEncodeAndReplaceLineBreaks(Environment.Version.ToString());
|
||||||
|
#endif
|
||||||
|
var runtimeArch = HtmlEncodeAndReplaceLineBreaks(environment.RuntimeArchitecture);
|
||||||
|
var dnxVersion = HtmlEncodeAndReplaceLineBreaks(environment.RuntimeVersion);
|
||||||
|
var currentAssembly = typeof(StartupExceptionPage).GetTypeInfo().Assembly;
|
||||||
|
var currentAssemblyVersion = currentAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||||
|
currentAssemblyVersion = HtmlEncodeAndReplaceLineBreaks(currentAssemblyVersion);
|
||||||
|
|
||||||
|
var os = HtmlEncodeAndReplaceLineBreaks(environment.OperatingSystem);
|
||||||
|
var osVersion = HtmlEncodeAndReplaceLineBreaks(environment.OperatingSystemVersion);
|
||||||
|
|
||||||
|
return string.Format(CultureInfo.InvariantCulture, _errorFooterFormatString, runtimeType, clrVersion,
|
||||||
|
runtimeArch, dnxVersion, currentAssemblyVersion, os, osVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HtmlEncodeAndReplaceLineBreaks(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split on line breaks before passing it through the encoder.
|
||||||
|
// We use the static default encoder since we can't depend on DI in the error handling logic.
|
||||||
|
return string.Join("<br />" + Environment.NewLine,
|
||||||
|
input.Split(new[] { "\r\n" }, StringSplitOptions.None)
|
||||||
|
.SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None))
|
||||||
|
.Select(HtmlEncoder.Default.HtmlEncode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteException(Exception ex, StringBuilder builder, ref bool wasFailingCallSiteSourceWritten)
|
||||||
|
{
|
||||||
|
string inlineSourceDiv = null;
|
||||||
|
|
||||||
|
// First, build the stack trace
|
||||||
|
var firstStackFrame = true;
|
||||||
|
var stackTraceBuilder = new StringBuilder();
|
||||||
|
var needFileInfo = true;
|
||||||
|
foreach (var frame in new StackTrace(ex, needFileInfo).GetFrames() ?? Enumerable.Empty<StackFrame>())
|
||||||
|
{
|
||||||
|
if (!firstStackFrame)
|
||||||
|
{
|
||||||
|
stackTraceBuilder.Append("<br />");
|
||||||
|
}
|
||||||
|
firstStackFrame = false;
|
||||||
|
var thisFrameLine = BuildLineForStackFrame(frame);
|
||||||
|
stackTraceBuilder.AppendLine(thisFrameLine);
|
||||||
|
|
||||||
|
// Try to include the source code in the error page if we can.
|
||||||
|
if (!wasFailingCallSiteSourceWritten && inlineSourceDiv == null)
|
||||||
|
{
|
||||||
|
inlineSourceDiv = BuildCodeSnippetDiv(frame);
|
||||||
|
if (inlineSourceDiv != null)
|
||||||
|
{
|
||||||
|
wasFailingCallSiteSourceWritten = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, build the rest of the <div>
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, _errorExceptionFormatString,
|
||||||
|
HtmlEncodeAndReplaceLineBreaks(ex.GetType().FullName),
|
||||||
|
HtmlEncodeAndReplaceLineBreaks(ex.Message),
|
||||||
|
inlineSourceDiv,
|
||||||
|
stackTraceBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteRawExceptionDetails(string linkText, IEnumerable<string> lines, StringBuilder rawExceptionDetails)
|
||||||
|
{
|
||||||
|
rawExceptionDetails
|
||||||
|
.AppendLine("<div class=\"rawExceptionBlock\">")
|
||||||
|
.AppendFormat($" <div><a href=\"#\" onclick=\"javascript: showRawException(); return false;\">{linkText}</a></div>")
|
||||||
|
.AppendLine()
|
||||||
|
.AppendLine(" <div id=\"rawException\">")
|
||||||
|
.Append(" <pre>");
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
rawExceptionDetails.AppendLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
rawExceptionDetails
|
||||||
|
.AppendLine("</pre>")
|
||||||
|
.AppendLine(" </div>")
|
||||||
|
.AppendLine("</div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteException(ICompilationException compilationException,
|
||||||
|
StringBuilder builder,
|
||||||
|
ref bool wasSourceCodeWrittenOntoPage)
|
||||||
|
{
|
||||||
|
var totalErrorsShown = 0;
|
||||||
|
var inlineSourceDiv = new StringBuilder();
|
||||||
|
var firstStackFrame = true;
|
||||||
|
foreach (var failure in compilationException.CompilationFailures)
|
||||||
|
{
|
||||||
|
if (firstStackFrame)
|
||||||
|
{
|
||||||
|
firstStackFrame = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inlineSourceDiv.AppendLine("<br/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildCodeSnippetDiv(failure, inlineSourceDiv, ref totalErrorsShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasSourceCodeWrittenOntoPage = totalErrorsShown > 0;
|
||||||
|
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture,
|
||||||
|
_compilationExceptionFormatString,
|
||||||
|
inlineSourceDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteMessage(string message, StringBuilder builder)
|
||||||
|
{
|
||||||
|
// Build the <div>
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, _errorMessageFormatString,
|
||||||
|
HtmlEncodeAndReplaceLineBreaks(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Exception> FlattenAndReverseExceptionTree(Exception ex)
|
||||||
|
{
|
||||||
|
var list = new List<Exception>();
|
||||||
|
for (; ex != null; ex = ex.InnerException)
|
||||||
|
{
|
||||||
|
list.Add(ex);
|
||||||
|
}
|
||||||
|
list.Reverse();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,7 @@ namespace Microsoft.AspNet.Hosting
|
||||||
private StartupMethods _startup;
|
private StartupMethods _startup;
|
||||||
private Type _startupType;
|
private Type _startupType;
|
||||||
private string _startupAssemblyName;
|
private string _startupAssemblyName;
|
||||||
|
private readonly bool _captureStartupErrors;
|
||||||
|
|
||||||
// Only one of these should be set
|
// Only one of these should be set
|
||||||
private string _serverFactoryLocation;
|
private string _serverFactoryLocation;
|
||||||
|
|
@ -50,6 +51,11 @@ namespace Microsoft.AspNet.Hosting
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebHostBuilder(IServiceProvider services, IConfiguration config)
|
public WebHostBuilder(IServiceProvider services, IConfiguration config)
|
||||||
|
: this(services, config: config, captureStartupErrors: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebHostBuilder(IServiceProvider services, IConfiguration config, bool captureStartupErrors)
|
||||||
{
|
{
|
||||||
if (services == null)
|
if (services == null)
|
||||||
{
|
{
|
||||||
|
|
@ -65,6 +71,7 @@ namespace Microsoft.AspNet.Hosting
|
||||||
_loggerFactory = new LoggerFactory();
|
_loggerFactory = new LoggerFactory();
|
||||||
_services = services;
|
_services = services;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_captureStartupErrors = captureStartupErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceCollection BuildHostingServices()
|
private IServiceCollection BuildHostingServices()
|
||||||
|
|
@ -117,8 +124,7 @@ namespace Microsoft.AspNet.Hosting
|
||||||
var startupLoader = hostingContainer.GetRequiredService<IStartupLoader>();
|
var startupLoader = hostingContainer.GetRequiredService<IStartupLoader>();
|
||||||
|
|
||||||
_hostingEnvironment.Initialize(appEnvironment.ApplicationBasePath, _config?[EnvironmentKey] ?? _config?[OldEnvironmentKey]);
|
_hostingEnvironment.Initialize(appEnvironment.ApplicationBasePath, _config?[EnvironmentKey] ?? _config?[OldEnvironmentKey]);
|
||||||
|
var engine = new HostingEngine(hostingServices, startupLoader, _config, _captureStartupErrors);
|
||||||
var engine = new HostingEngine(hostingServices, startupLoader, _config);
|
|
||||||
|
|
||||||
// Only one of these should be set, but they are used in priority
|
// Only one of these should be set, but they are used in priority
|
||||||
engine.ServerFactory = _serverFactory;
|
engine.ServerFactory = _serverFactory;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="message">
|
||||||
|
<span class="light exception">One or more compilation errors occured:</span><br />
|
||||||
|
<div class="stacktrace">
|
||||||
|
{0}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>500 Internal Server Error</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
color: #111111;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 2em 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: darkblue;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
margin-bottom: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stacktrace pre {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faded {
|
||||||
|
color: #999999;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.message {
|
||||||
|
margin-top: 2.5em;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
border-left: 0.25em solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heavy {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exception {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stacktrace {
|
||||||
|
padding-top: 0.3em;
|
||||||
|
padding-left: 2em;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet {
|
||||||
|
margin-left: 2em;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 0.2em solid #cccccc;
|
||||||
|
border-bottom: 0.2em solid #cccccc;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div:nth-of-type(2n) {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div:nth-of-type(2n + 1) {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet .error-message {
|
||||||
|
color: red;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div.filename {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: white;
|
||||||
|
margin: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div.line {
|
||||||
|
padding: 0.2em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div.line .line-number {
|
||||||
|
color: #999999;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet div.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bolder;
|
||||||
|
background-color: #ffeda7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet code {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rawExceptionBlock {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rawException {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 2em;
|
||||||
|
font-size: smaller;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function showRawException() {
|
||||||
|
var div = document.getElementById('rawException');
|
||||||
|
div.style.display = 'inline-block';
|
||||||
|
div.scrollIntoView(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<div style="font-size: 6em; display: inline-block;">
|
||||||
|
:(
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block; padding-left: 3em;">
|
||||||
|
<span style="font-size: 2em;">Oops.</span><br />
|
||||||
|
<span style="font-size: 1.65em; font-weight: lighter;">500 Internal Server Error</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[[[0]]]
|
||||||
|
|
||||||
|
[[[1]]]
|
||||||
|
|
||||||
|
[[[2]]]
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="message">
|
||||||
|
<span class="light exception">{0}</span><br />
|
||||||
|
<span class="heavy">{1}</span><br />
|
||||||
|
{2}
|
||||||
|
<div class="stacktrace">
|
||||||
|
{3}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<footer>
|
||||||
|
.NET Framework {0} version {1} | DNX {2} version {3} | Microsoft.AspNet.Hosting version {4} | {5} {6} | <a href="http://go.microsoft.com/fwlink/?LinkId=517394">Need help?</a>
|
||||||
|
</footer>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="message">
|
||||||
|
<span class="heavy">{0}</span><br />
|
||||||
|
</div>
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"Microsoft.Framework.Configuration.Json": "1.0.0-*",
|
"Microsoft.Framework.Configuration.Json": "1.0.0-*",
|
||||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||||
"Microsoft.Framework.Logging": "1.0.0-*",
|
"Microsoft.Framework.Logging": "1.0.0-*",
|
||||||
|
"Microsoft.Dnx.Compilation.Abstractions": "1.0.0-*",
|
||||||
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
|
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
|
||||||
"Newtonsoft.Json": "6.0.6",
|
"Newtonsoft.Json": "6.0.6",
|
||||||
"System.Diagnostics.Tracing.Telemetry": "4.0.0-beta-*"
|
"System.Diagnostics.Tracing.Telemetry": "4.0.0-beta-*"
|
||||||
|
|
@ -30,7 +31,8 @@
|
||||||
},
|
},
|
||||||
"dnxcore50": {
|
"dnxcore50": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Console": "4.0.0-beta-*"
|
"System.Console": "4.0.0-beta-*",
|
||||||
|
"System.Diagnostics.StackTrace": "4.0.1-beta-*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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.Dnx.Runtime;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Fakes
|
||||||
|
{
|
||||||
|
public class RuntimeEnvironment : IRuntimeEnvironment
|
||||||
|
{
|
||||||
|
public string OperatingSystem { get; } = "TestOs";
|
||||||
|
|
||||||
|
public string OperatingSystemVersion { get; } = "TestOsVersion";
|
||||||
|
|
||||||
|
public string RuntimeArchitecture { get; } = "TestArch";
|
||||||
|
|
||||||
|
public string RuntimeType { get; } = "TestRuntime";
|
||||||
|
|
||||||
|
public string RuntimeVersion { get; } = "TestRuntimeVersion";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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 System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Fakes
|
||||||
|
{
|
||||||
|
public class StartupConfigureServicesThrows
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
throw new Exception("Exception from ConfigureServices");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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 System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Fakes
|
||||||
|
{
|
||||||
|
public class StartupConfigureThrows
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
throw new Exception("Exception from Configure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Fakes
|
||||||
|
{
|
||||||
|
public class StartupCtorThrows
|
||||||
|
{
|
||||||
|
public StartupCtorThrows()
|
||||||
|
{
|
||||||
|
throw new Exception("Exception from constructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Hosting.Fakes
|
||||||
|
{
|
||||||
|
public class StartupStaticCtorThrows
|
||||||
|
{
|
||||||
|
static StartupStaticCtorThrows()
|
||||||
|
{
|
||||||
|
throw new Exception("Exception from static constructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Hosting.Fakes;
|
||||||
using Microsoft.AspNet.Hosting.Internal;
|
using Microsoft.AspNet.Hosting.Internal;
|
||||||
|
using Microsoft.AspNet.Hosting.Server;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.Dnx.Runtime.Infrastructure;
|
using Microsoft.Dnx.Runtime.Infrastructure;
|
||||||
|
using Microsoft.Framework.Configuration;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Hosting
|
namespace Microsoft.AspNet.Hosting
|
||||||
|
|
@ -29,6 +38,110 @@ namespace Microsoft.AspNet.Hosting
|
||||||
Assert.Equal("MyStartupAssembly", engine.StartupAssemblyName);
|
Assert.Equal("MyStartupAssembly", engine.StartupAssemblyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebHostBuilder CreateWebHostBuilder() => new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider);
|
[Fact]
|
||||||
|
public async Task StartupMissing_Fallback()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder();
|
||||||
|
var serverFactory = new TestServerFactory();
|
||||||
|
var engine = (HostingEngine)builder.UseServer(serverFactory).UseStartup("MissingStartupAssembly").Build();
|
||||||
|
using (engine.Start())
|
||||||
|
{
|
||||||
|
await AssertResponseContains(serverFactory.Application, "MissingStartupAssembly");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task StartupStaticCtorThrows_Fallback()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder();
|
||||||
|
var serverFactory = new TestServerFactory();
|
||||||
|
var engine = (HostingEngine)builder.UseServer(serverFactory).UseStartup<StartupStaticCtorThrows>().Build();
|
||||||
|
using (engine.Start())
|
||||||
|
{
|
||||||
|
await AssertResponseContains(serverFactory.Application, "Exception from static constructor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task StartupCtorThrows_Fallback()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder();
|
||||||
|
var serverFactory = new TestServerFactory();
|
||||||
|
var engine = (HostingEngine)builder.UseServer(serverFactory).UseStartup<StartupCtorThrows>().Build();
|
||||||
|
using (engine.Start())
|
||||||
|
{
|
||||||
|
await AssertResponseContains(serverFactory.Application, "Exception from constructor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task StartupConfigureServicesThrows_Fallback()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder();
|
||||||
|
var serverFactory = new TestServerFactory();
|
||||||
|
var engine = (HostingEngine)builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||||
|
using (engine.Start())
|
||||||
|
{
|
||||||
|
await AssertResponseContains(serverFactory.Application, "Exception from ConfigureServices");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task StartupConfigureThrows_Fallback()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder();
|
||||||
|
var serverFactory = new TestServerFactory();
|
||||||
|
var engine = (HostingEngine)builder.UseServer(serverFactory).UseStartup<StartupConfigureServicesThrows>().Build();
|
||||||
|
using (engine.Start())
|
||||||
|
{
|
||||||
|
await AssertResponseContains(serverFactory.Application, "Exception from Configure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebHostBuilder CreateWebHostBuilder()
|
||||||
|
{
|
||||||
|
var vals = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "server", "Microsoft.AspNet.Hosting.Tests" },
|
||||||
|
{ "Hosting:DetailedErrors", "true" },
|
||||||
|
};
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(vals);
|
||||||
|
var config = builder.Build();
|
||||||
|
return new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config, captureStartupErrors: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AssertResponseContains(Func<IFeatureCollection, Task> app, string expectedText)
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
await app(httpContext.Features);
|
||||||
|
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||||
|
var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd();
|
||||||
|
Assert.Contains(expectedText, bodyText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestServerFactory : IServerFactory
|
||||||
|
{
|
||||||
|
public Func<IFeatureCollection, Task> Application { get; set; }
|
||||||
|
|
||||||
|
public IFeatureCollection Initialize(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
return new FeatureCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application)
|
||||||
|
{
|
||||||
|
Application = application;
|
||||||
|
return new Disposable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Disposable : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue