Skip to content

Commit a9e7948

Browse files
committed
#77 Catch startup exceptions and show them in the browser.
1 parent 520fc2b commit a9e7948

16 files changed

+1000
-33
lines changed

src/Microsoft.AspNet.Hosting/Internal/HostingEngine.cs

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.AspNet.Http.Features;
1515
using Microsoft.AspNet.Http.Features.Internal;
1616
using Microsoft.AspNet.Server.Features;
17+
using Microsoft.Dnx.Runtime;
1718
using Microsoft.Framework.Configuration;
1819
using Microsoft.Framework.DependencyInjection;
1920
using Microsoft.Framework.Logging;
@@ -24,11 +25,13 @@ public class HostingEngine : IHostingEngine
2425
{
2526
// This is defined by IIS's HttpPlatformHandler.
2627
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
28+
private static readonly string DetailedErrors = "Hosting:DetailedErrors";
2729

2830
private readonly IServiceCollection _applicationServiceCollection;
2931
private readonly IStartupLoader _startupLoader;
3032
private readonly ApplicationLifetime _applicationLifetime;
3133
private readonly IConfiguration _config;
34+
private readonly bool _captureStartupErrors;
3235

3336
private IServiceProvider _applicationServices;
3437

@@ -45,7 +48,8 @@ public class HostingEngine : IHostingEngine
4548
public HostingEngine(
4649
IServiceCollection appServices,
4750
IStartupLoader startupLoader,
48-
IConfiguration config)
51+
IConfiguration config,
52+
bool captureStartupErrors)
4953
{
5054
if (appServices == null)
5155
{
@@ -65,6 +69,7 @@ public HostingEngine(
6569
_config = config;
6670
_applicationServiceCollection = appServices;
6771
_startupLoader = startupLoader;
72+
_captureStartupErrors = captureStartupErrors;
6873
_applicationLifetime = new ApplicationLifetime();
6974
}
7075

@@ -79,8 +84,6 @@ public IServiceProvider ApplicationServices
7984

8085
public virtual IApplication Start()
8186
{
82-
EnsureApplicationServices();
83-
8487
var application = BuildApplication();
8588

8689
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
@@ -175,48 +178,97 @@ private void EnsureStartup()
175178

176179
private RequestDelegate BuildApplication()
177180
{
178-
if (ServerFactory == null)
181+
try
179182
{
180-
// Blow up if we don't have a server set at this point
181-
if (ServerFactoryLocation == null)
183+
EnsureApplicationServices();
184+
EnsureServer();
185+
186+
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
187+
var builder = builderFactory.CreateBuilder(_serverInstance);
188+
builder.ApplicationServices = _applicationServices;
189+
190+
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
191+
var configure = Startup.ConfigureDelegate;
192+
foreach (var filter in startupFilters)
182193
{
183-
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()");
194+
configure = filter.Configure(configure);
184195
}
185196

186-
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
187-
}
188-
189-
_serverInstance = ServerFactory.Initialize(_config);
190-
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
191-
var builder = builderFactory.CreateBuilder(_serverInstance);
192-
builder.ApplicationServices = _applicationServices;
197+
configure(builder);
193198

194-
var addresses = builder.ServerFeatures?.Get<IServerAddressesFeature>()?.Addresses;
195-
if (addresses != null && !addresses.IsReadOnly)
199+
return builder.Build();
200+
}
201+
catch (Exception ex)
196202
{
197-
var port = _config[ServerPort];
198-
if (!string.IsNullOrEmpty(port))
203+
if (!_captureStartupErrors)
199204
{
200-
addresses.Add("http://localhost:" + port);
205+
throw;
201206
}
202207

203-
// Provide a default address if there aren't any configured.
204-
if (addresses.Count == 0)
208+
// EnsureApplicationServices may have failed due to a missing or throwing Startup class.
209+
if (_applicationServices == null)
205210
{
206-
addresses.Add("http://localhost:5000");
211+
_applicationServices = _applicationServiceCollection.BuildServiceProvider();
207212
}
213+
214+
EnsureServer();
215+
216+
// Write errors to standard out so they can be retrieved when not in development mode.
217+
Console.Out.WriteLine("Application startup exception: " + ex.ToString());
218+
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
219+
logger.LogError("Application startup exception", ex);
220+
221+
// Generate an HTML error page.
222+
var runtimeEnv = _applicationServices.GetRequiredService<IRuntimeEnvironment>();
223+
var hostingEnv = _applicationServices.GetRequiredService<IHostingEnvironment>();
224+
var showDetailedErrors = hostingEnv.IsDevelopment()
225+
|| string.Equals("true", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase)
226+
|| string.Equals("1", _config[DetailedErrors], StringComparison.OrdinalIgnoreCase);
227+
var errorBytes = StartupExceptionPage.GenerateErrorHtml(showDetailedErrors, runtimeEnv, ex);
228+
229+
return context =>
230+
{
231+
context.Response.StatusCode = 500;
232+
context.Response.Headers["Cache-Control"] = "private, max-age=0";
233+
context.Response.ContentType = "text/html; charset=utf-8";
234+
context.Response.ContentLength = errorBytes.Length;
235+
return context.Response.Body.WriteAsync(errorBytes, 0, errorBytes.Length);
236+
};
208237
}
238+
}
209239

210-
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
211-
var configure = Startup.ConfigureDelegate;
212-
foreach (var filter in startupFilters)
240+
private void EnsureServer()
241+
{
242+
if (ServerFactory == null)
213243
{
214-
configure = filter.Configure(configure);
244+
// Blow up if we don't have a server set at this point
245+
if (ServerFactoryLocation == null)
246+
{
247+
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()");
248+
}
249+
250+
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
215251
}
216252

217-
configure(builder);
253+
if (_serverInstance == null)
254+
{
255+
_serverInstance = ServerFactory.Initialize(_config);
256+
var addresses = _serverInstance?.Get<IServerAddressesFeature>()?.Addresses;
257+
if (addresses != null && !addresses.IsReadOnly)
258+
{
259+
var port = _config[ServerPort];
260+
if (!string.IsNullOrEmpty(port))
261+
{
262+
addresses.Add("http://localhost:" + port);
263+
}
218264

219-
return builder.Build();
265+
// Provide a default address if there aren't any configured.
266+
if (addresses.Count == 0)
267+
{
268+
addresses.Add("http://localhost:5000");
269+
}
270+
}
271+
}
220272
}
221273

222274
private string GetRequestIdentifier(HttpContext httpContext)

src/Microsoft.AspNet.Hosting/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void Main(string[] args)
3636
builder.AddCommandLine(args);
3737
var config = builder.Build();
3838

39-
var host = new WebHostBuilder(_serviceProvider, config).Build();
39+
var host = new WebHostBuilder(_serviceProvider, config, captureStartupErrors: true).Build();
4040
using (var app = host.Start())
4141
{
4242
var hostingEnv = app.Services.GetRequiredService<IHostingEnvironment>();

0 commit comments

Comments
 (0)