#77 Catch startup exceptions and show them in the browser.

This commit is contained in:
Chris R 2015-09-23 20:54:42 -07:00
parent 520fc2b5fd
commit a9e7948d72
16 changed files with 1001 additions and 34 deletions

View File

@ -14,6 +14,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Internal;
using Microsoft.AspNet.Server.Features;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Configuration;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
@ -24,11 +25,13 @@ namespace Microsoft.AspNet.Hosting.Internal
{
// This is defined by IIS's HttpPlatformHandler.
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
private static readonly string DetailedErrors = "Hosting:DetailedErrors";
private readonly IServiceCollection _applicationServiceCollection;
private readonly IStartupLoader _startupLoader;
private readonly ApplicationLifetime _applicationLifetime;
private readonly IConfiguration _config;
private readonly bool _captureStartupErrors;
private IServiceProvider _applicationServices;
@ -45,7 +48,8 @@ namespace Microsoft.AspNet.Hosting.Internal
public HostingEngine(
IServiceCollection appServices,
IStartupLoader startupLoader,
IConfiguration config)
IConfiguration config,
bool captureStartupErrors)
{
if (appServices == null)
{
@ -65,6 +69,7 @@ namespace Microsoft.AspNet.Hosting.Internal
_config = config;
_applicationServiceCollection = appServices;
_startupLoader = startupLoader;
_captureStartupErrors = captureStartupErrors;
_applicationLifetime = new ApplicationLifetime();
}
@ -79,8 +84,6 @@ namespace Microsoft.AspNet.Hosting.Internal
public virtual IApplication Start()
{
EnsureApplicationServices();
var application = BuildApplication();
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
@ -174,6 +177,67 @@ namespace Microsoft.AspNet.Hosting.Internal
}
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)
{
@ -186,37 +250,25 @@ namespace Microsoft.AspNet.Hosting.Internal
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
}
_serverInstance = ServerFactory.Initialize(_config);
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)
if (_serverInstance == null)
{
var port = _config[ServerPort];
if (!string.IsNullOrEmpty(port))
_serverInstance = ServerFactory.Initialize(_config);
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.
if (addresses.Count == 0)
{
addresses.Add("http://localhost:5000");
// Provide a default address if there aren't any configured.
if (addresses.Count == 0)
{
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)

View File

@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Hosting
builder.AddCommandLine(args);
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())
{
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();

View File

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

View File

@ -39,6 +39,7 @@ namespace Microsoft.AspNet.Hosting
private StartupMethods _startup;
private Type _startupType;
private string _startupAssemblyName;
private readonly bool _captureStartupErrors;
// Only one of these should be set
private string _serverFactoryLocation;
@ -50,6 +51,11 @@ namespace Microsoft.AspNet.Hosting
}
public WebHostBuilder(IServiceProvider services, IConfiguration config)
: this(services, config: config, captureStartupErrors: false)
{
}
public WebHostBuilder(IServiceProvider services, IConfiguration config, bool captureStartupErrors)
{
if (services == null)
{
@ -65,6 +71,7 @@ namespace Microsoft.AspNet.Hosting
_loggerFactory = new LoggerFactory();
_services = services;
_config = config;
_captureStartupErrors = captureStartupErrors;
}
private IServiceCollection BuildHostingServices()
@ -117,8 +124,7 @@ namespace Microsoft.AspNet.Hosting
var startupLoader = hostingContainer.GetRequiredService<IStartupLoader>();
_hostingEnvironment.Initialize(appEnvironment.ApplicationBasePath, _config?[EnvironmentKey] ?? _config?[OldEnvironmentKey]);
var engine = new HostingEngine(hostingServices, startupLoader, _config);
var engine = new HostingEngine(hostingServices, startupLoader, _config, _captureStartupErrors);
// Only one of these should be set, but they are used in priority
engine.ServerFactory = _serverFactory;

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
<footer>
.NET Framework {0} version {1}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;DNX {2} version {3}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;Microsoft.AspNet.Hosting version {4}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;{5} {6}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="http://go.microsoft.com/fwlink/?LinkId=517394">Need help?</a>
</footer>

View File

@ -0,0 +1,3 @@
<div class="message">
<span class="heavy">{0}</span><br />
</div>

View File

@ -18,6 +18,7 @@
"Microsoft.Framework.Configuration.Json": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.Logging": "1.0.0-*",
"Microsoft.Dnx.Compilation.Abstractions": "1.0.0-*",
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
"Newtonsoft.Json": "6.0.6",
"System.Diagnostics.Tracing.Telemetry": "4.0.0-beta-*"
@ -30,7 +31,8 @@
},
"dnxcore50": {
"dependencies": {
"System.Console": "4.0.0-beta-*"
"System.Console": "4.0.0-beta-*",
"System.Diagnostics.StackTrace": "4.0.1-beta-*"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,17 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting.Fakes;
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.Framework.Configuration;
using Xunit;
namespace Microsoft.AspNet.Hosting
@ -29,6 +38,110 @@ namespace Microsoft.AspNet.Hosting
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()
{
}
}
}
}
}