diff --git a/DiagnosticsPages.sln b/DiagnosticsPages.sln
index 9691d84257..1850737a64 100644
--- a/DiagnosticsPages.sln
+++ b/DiagnosticsPages.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.22130.0
+VisualStudioVersion = 14.0.22129.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{509A6F36-AD80-4A18-B5B1-717D38DFF29D}"
EndProject
@@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2AF90579-B
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Diagnostics.Tests", "test\Microsoft.AspNet.Diagnostics.Tests\Microsoft.AspNet.Diagnostics.Tests.kproj", "{994351B4-7B2A-4139-8B72-72C5BB5CC618}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorHandlerSample", "samples\ErrorHandlerSample\ErrorHandlerSample.kproj", "{427CDB36-78B0-4583-9EBC-7F283DE60355}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -96,6 +98,16 @@ Global
{994351B4-7B2A-4139-8B72-72C5BB5CC618}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{994351B4-7B2A-4139-8B72-72C5BB5CC618}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{994351B4-7B2A-4139-8B72-72C5BB5CC618}.Release|x86.ActiveCfg = Release|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Any CPU.Build.0 = Release|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -107,5 +119,6 @@ Global
{4D4A785A-ECB9-4916-A88F-0FD306EE3B74} = {509A6F36-AD80-4A18-B5B1-717D38DFF29D}
{CD62A191-39F5-4C86-BC1D-7731085120F5} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
{994351B4-7B2A-4139-8B72-72C5BB5CC618} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
+ {427CDB36-78B0-4583-9EBC-7F283DE60355} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
EndGlobalSection
EndGlobal
diff --git a/samples/ErrorHandlerSample/ErrorHandlerSample.kproj b/samples/ErrorHandlerSample/ErrorHandlerSample.kproj
new file mode 100644
index 0000000000..b863c76805
--- /dev/null
+++ b/samples/ErrorHandlerSample/ErrorHandlerSample.kproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 427cdb36-78b0-4583-9ebc-7f283de60355
+ Web
+ ErrorHandlerSample
+
+
+ 2.0
+ 54230
+
+
+
\ No newline at end of file
diff --git a/samples/ErrorHandlerSample/Startup.cs b/samples/ErrorHandlerSample/Startup.cs
new file mode 100644
index 0000000000..7ba297860a
--- /dev/null
+++ b/samples/ErrorHandlerSample/Startup.cs
@@ -0,0 +1,56 @@
+using System;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.Diagnostics;
+using Microsoft.AspNet.Http;
+
+namespace ErrorHandlerSample
+{
+ public class Startup
+ {
+ public void Configure(IApplicationBuilder app)
+ {
+ // Configure the error handler to show an error page.
+ app.UseErrorHandler(errorApp =>
+ {
+ // Normally you'd use MVC or similar to render a nice page.
+ errorApp.Run(async context =>
+ {
+ context.Response.StatusCode = 500;
+ context.Response.ContentType = "text/html";
+ await context.Response.WriteAsync("
\r\n");
+ await context.Response.WriteAsync("We're sorry, we encountered an un-expected issue with your application.
\r\n");
+
+ var error = context.GetFeature();
+ if (error != null)
+ {
+ // This error would not normally be exposed to the client
+ await context.Response.WriteAsync("
Error: " + System.Net.WebUtility.HtmlEncode(error.Error.Message) + "
\r\n");
+ }
+ await context.Response.WriteAsync("
Home
\r\n");
+ await context.Response.WriteAsync("\r\n");
+ await context.Response.WriteAsync(new string(' ', 512)); // Padding for IE
+ });
+ });
+
+ // We could also configure it to re-execute the request on the normal pipeline with a different path.
+ // app.UseErrorHandler("/error.html");
+
+ // The broken section of our application.
+ app.Map("/throw", throwApp =>
+ {
+ throwApp.Run(context => { throw new Exception("Application Exception"); });
+ });
+
+ app.UseStaticFiles();
+
+ // The home page.
+ app.Run(async context =>
+ {
+ context.Response.ContentType = "text/html";
+ await context.Response.WriteAsync("Welcome to the sample
\r\n");
+ await context.Response.WriteAsync("Click here to throw an exception: throw\r\n");
+ await context.Response.WriteAsync("\r\n");
+ });
+ }
+ }
+}
diff --git a/samples/ErrorHandlerSample/project.json b/samples/ErrorHandlerSample/project.json
new file mode 100644
index 0000000000..a620f4479d
--- /dev/null
+++ b/samples/ErrorHandlerSample/project.json
@@ -0,0 +1,18 @@
+{
+ "webroot": "wwwroot",
+ "exclude": "wwwroot/**/*.*",
+ "dependencies": {
+ "Microsoft.AspNet.Diagnostics": "1.0.0-*",
+ "Microsoft.AspNet.Server.IIS": "1.0.0-*",
+ "Microsoft.AspNet.Server.WebListener": "1.0.0-*",
+ "Microsoft.AspNet.StaticFiles": "1.0.0-*"
+ },
+ "commands": {
+ /* Change the port number when you are self hosting this application */
+ "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000"
+ },
+ "frameworks": {
+ "aspnet50" : { },
+ "aspnetcore50" : { }
+ }
+}
diff --git a/samples/ErrorHandlerSample/wwwroot/error.html b/samples/ErrorHandlerSample/wwwroot/error.html
new file mode 100644
index 0000000000..b9989e017f
--- /dev/null
+++ b/samples/ErrorHandlerSample/wwwroot/error.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+ You've reached the static error page.
+ Home
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/ErrorHandlerExtensions.cs b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerExtensions.cs
new file mode 100644
index 0000000000..22bbe8512b
--- /dev/null
+++ b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerExtensions.cs
@@ -0,0 +1,47 @@
+// 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 Microsoft.AspNet.Diagnostics;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Builder
+{
+ public static class ErrorHandlerExtensions
+ {
+ ///
+ /// Adds a middleware to the pipeline that will catch exceptions, log them, reset the request path, and re-execute the request.
+ /// The request will not be re-executed if the response has already started.
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder app, string errorHandlingPath)
+ {
+ var options = new ErrorHandlerOptions()
+ {
+ ErrorHandlingPath = new PathString(errorHandlingPath)
+ };
+ return app.UseMiddleware(options);
+ }
+
+ ///
+ /// Adds a middleware to the pipeline that will catch exceptions, log them, and re-execute the request in an alternate pipeline.
+ /// The request will not be re-executed if the response has already started.
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder app, Action configure)
+ {
+ var subAppBuilder = app.New();
+ configure(subAppBuilder);
+ var errorPipeline = subAppBuilder.Build();
+ var options = new ErrorHandlerOptions()
+ {
+ ErrorHandler = errorPipeline
+ };
+ return app.UseMiddleware(options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/ErrorHandlerFeature.cs b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerFeature.cs
new file mode 100644
index 0000000000..bbac79bdc4
--- /dev/null
+++ b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerFeature.cs
@@ -0,0 +1,12 @@
+// 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;
+
+namespace Microsoft.AspNet.Diagnostics
+{
+ public class ErrorHandlerFeature : IErrorHandlerFeature
+ {
+ public Exception Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs
new file mode 100644
index 0000000000..ab6790aadf
--- /dev/null
+++ b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs
@@ -0,0 +1,80 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.Http;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Diagnostics
+{
+ public class ErrorHandlerMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ErrorHandlerOptions _options;
+ private readonly ILogger _logger;
+
+ public ErrorHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, ErrorHandlerOptions options)
+ {
+ _next = next;
+ _options = options;
+ _logger = loggerFactory.Create();
+ if (_options.ErrorHandler == null)
+ {
+ _options.ErrorHandler = _next;
+ }
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ var responseStarted = false;
+ try
+ {
+ context.Response.OnSendingHeaders(state => responseStarted = true, null);
+ await _next(context);
+ }
+ catch (Exception ex)
+ {
+ _logger.WriteError("An unhandled exception has occurred: " + ex.Message, ex);
+ // We can't do anything if the response has already started, just abort.
+ if (responseStarted)
+ {
+ _logger.WriteWarning("The response has already started, the error handler will not be executed.");
+ throw;
+ }
+
+ PathString originalPath = context.Request.Path;
+ if (_options.ErrorHandlingPath.HasValue)
+ {
+ context.Request.Path = _options.ErrorHandlingPath;
+ }
+ try
+ {
+ var errorHandlerFeature = new ErrorHandlerFeature()
+ {
+ Error = ex,
+ };
+ context.SetFeature(errorHandlerFeature);
+ context.Response.StatusCode = 500;
+ context.Response.Headers.Clear();
+ // TODO: Try clearing any buffered data. The buffering feature/middleware has not been designed yet.
+ await _options.ErrorHandler(context);
+ // TODO: Optional re-throw? We'll re-throw the original exception by default if the error handler throws.
+ return;
+ }
+ catch (Exception ex2)
+ {
+ // Suppress secondary exceptions, re-throw the original.
+ _logger.WriteError("An exception was thrown attempting to execute the error handler.", ex2);
+ }
+ finally
+ {
+ context.Request.Path = originalPath;
+ }
+
+ throw; // Re-throw the original if we couldn't handle it
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/ErrorHandlerOptions.cs b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerOptions.cs
new file mode 100644
index 0000000000..706127a5d5
--- /dev/null
+++ b/src/Microsoft.AspNet.Diagnostics/ErrorHandlerOptions.cs
@@ -0,0 +1,15 @@
+// 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.Builder;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.Diagnostics
+{
+ public class ErrorHandlerOptions
+ {
+ public PathString ErrorHandlingPath { get; set; }
+
+ public RequestDelegate ErrorHandler { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/IErrorHandlerFeature.cs b/src/Microsoft.AspNet.Diagnostics/IErrorHandlerFeature.cs
new file mode 100644
index 0000000000..b53949b2a9
--- /dev/null
+++ b/src/Microsoft.AspNet.Diagnostics/IErrorHandlerFeature.cs
@@ -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 System;
+using Microsoft.Framework.Runtime;
+
+namespace Microsoft.AspNet.Diagnostics
+{
+ [AssemblyNeutral]
+ public interface IErrorHandlerFeature
+ {
+ Exception Error { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Diagnostics/project.json b/src/Microsoft.AspNet.Diagnostics/project.json
index 49907f1e8f..7f2747a35f 100644
--- a/src/Microsoft.AspNet.Diagnostics/project.json
+++ b/src/Microsoft.AspNet.Diagnostics/project.json
@@ -4,6 +4,8 @@
"Microsoft.AspNet.FeatureModel": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
+ "Microsoft.AspNet.RequestContainer": "1.0.0-*",
+ "Microsoft.Framework.Logging": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": { "version": "1.0.0-*", "type": "build" }
},
"frameworks": {