From 6390bad0d3fbac3be7c623c2650a033acb7cfd6a Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 17:46:18 -0800 Subject: [PATCH] Adding a pattern for returning 'unhandled' exception information via middleware. This should be used where posssible instead of throwing an exception and catching in a test, as that only works in memory. --- .../ActivatorTests.cs | 16 +++-- .../AntiForgeryTests.cs | 45 ++++++++---- .../ExceptionInfo.cs | 16 +++++ .../FiltersTest.cs | 34 ++++++--- .../HttpResponseMessageExceptions.cs | 48 +++++++++++++ .../InlineConstraintTests.cs | 14 ++-- .../ModelBindingTests.cs | 69 ++++++++++++------- .../RoutingTests.cs | 6 +- .../WebApiCompatShimActionSelectionTest.cs | 8 ++- .../XmlSerializerInputFormatterTests.cs | 9 ++- test/WebSites/ActivatorWebSite/Startup.cs | 3 + test/WebSites/AntiForgeryWebSite/Startup.cs | 2 + test/WebSites/FiltersWebSite/Startup.cs | 2 + .../InlineConstraintsWebSite/Startup.cs | 5 +- .../BuilderExtensions.cs | 6 ++ .../ErrorReporterMiddleware.cs | 51 ++++++++++++++ test/WebSites/ModelBindingWebSite/Startup.cs | 2 + test/WebSites/RoutingWebSite/Startup.cs | 2 + .../WebApiCompatShimWebSite/Startup.cs | 2 + test/WebSites/XmlSerializerWebSite/Startup.cs | 2 + 20 files changed, 268 insertions(+), 74 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs create mode 100644 test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs index 4d23790d48..fd3e27da20 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs @@ -21,12 +21,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); + var expectedMessage = "No service for type 'ActivatorWebSite.CannotBeActivatedController+FakeType' " + "has been registered."; // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.GetAsync("http://localhost/CannotBeActivated/Index")); - Assert.Equal(expectedMessage, ex.Message); + var response = await client.GetAsync("http://localhost/CannotBeActivated/Index"); + + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } [Fact] @@ -162,9 +166,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "has been registered."; // Act & Assert - var ex = await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent")); - Assert.Equal(expectedMessage, ex.Message); + var response = await client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent"); + + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index 806a1575c2..4a19cbe97a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -100,9 +100,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery token could not be decrypted.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery token could not be decrypted.", exception.ExceptionMessage); } [Fact] @@ -127,9 +130,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery token could not be decrypted.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery token could not be decrypted.", exception.ExceptionMessage); } [Fact] @@ -162,9 +168,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery cookie token and form field token do not match.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery cookie token and form field token do not match.", exception.ExceptionMessage); } [Fact] @@ -189,9 +198,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); } [Fact] @@ -214,10 +226,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); Assert.Equal("The required anti-forgery form field \"__RequestVerificationToken\" is not present.", - ex.Message); + exception.ExceptionMessage); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs new file mode 100644 index 0000000000..1542984292 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + /// + /// Information about an exception that occured on the server side of a functional + /// test. + /// + public class ExceptionInfo + { + public string ExceptionMessage { get; set; } + + public string ExceptionType { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs index bf4ba11293..b25743957e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs @@ -124,8 +124,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var url = "http://localhost/RandomNumber/GetAuthorizedRandomNumber"; - // Act & Assert - await Assert.ThrowsAsync(() => client.GetAsync(url)); + // Act + var response = await client.GetAsync(url); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } [Fact] @@ -152,8 +156,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var url = "http://localhost/RandomNumber/GetHalfOfModifiedRandomNumber?randomNumber=3"; - // Act & Assert - await Assert.ThrowsAsync(() => client.GetAsync(url)); + // Act + var response = await client.GetAsync(url); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } [Fact] @@ -465,9 +473,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/Home/ThrowingResultFilter")); + // Act + var response = await client.GetAsync("http://localhost/Home/ThrowingResultFilter"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidProgramException).FullName, exception.ExceptionType); } // Action Filter throws. @@ -496,9 +507,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/Home/ThrowingAuthorizationFilter")); + // Act + var response = await client.GetAsync("http://localhost/Home/ThrowingAuthorizationFilter"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidProgramException).FullName, exception.ExceptionType); } // Exception Filter throws. diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs new file mode 100644 index 0000000000..f2d87daba5 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs @@ -0,0 +1,48 @@ +// 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.Collections.Generic; +using System.Net; +using System.Net.Http; +using Microsoft.AspNet.Mvc.TestConfiguration; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public static class HttpResponseMessageExceptions + { + public static ExceptionInfo GetServerException(this HttpResponseMessage response) + { + if (response.StatusCode != HttpStatusCode.InternalServerError) + { + throw new AssertActualExpectedException( + HttpStatusCode.InternalServerError, + response.StatusCode, + "A server-side exception should be returned as a 500."); + } + + var headers = response.Headers; + + IEnumerable exceptionMessageHeader; + IEnumerable exceptionTypeHeader; + if (!headers.TryGetValues(ErrorReporterMiddleware.ExceptionMessageHeader, out exceptionMessageHeader)) + { + throw new XunitException( + "No value for the '" + ErrorReporterMiddleware.ExceptionMessageHeader + "' header."); + } + + if (!headers.TryGetValues(ErrorReporterMiddleware.ExceptionTypeHeader, out exceptionTypeHeader)) + { + throw new XunitException( + "No value for the '" + ErrorReporterMiddleware.ExceptionTypeHeader + "' header."); + } + + return new ExceptionInfo() + { + ExceptionMessage = Assert.Single(exceptionMessageHeader), + ExceptionType = Assert.Single(exceptionTypeHeader), + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs index 5dc8d1961f..1f95804482 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs @@ -42,15 +42,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/area-withoutexists/Users") - ); + // Act + var response = await client.GetAsync("http://localhost/area-withoutexists/Users"); + // Assert + var exception = response.GetServerException(); Assert.Equal("The view 'Index' was not found." + - " The following locations were searched:\r\n/Areas/Users/Views/Home/Index.cshtml\r\n" + - "/Areas/Users/Views/Shared/Index.cshtml\r\n/Views/Shared/Index.cshtml.", - ex.Message); + " The following locations were searched:__/Areas/Users/Views/Home/Index.cshtml__" + + "/Areas/Users/Views/Shared/Index.cshtml__/Views/Shared/Index.cshtml.", + exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 0fefaa5a9f..952d278ad1 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -128,12 +128,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -143,12 +146,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -158,12 +164,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -173,12 +182,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -930,13 +942,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); Expression> expression = model => model.Address.Country; + var expected = string.Format( + "The passed expression of expression node type '{0}' is invalid." + + " Only simple member access expressions for model properties are supported.", + expression.Body.NodeType); + // Act - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123")); - Assert.Equal(string.Format("The passed expression of expression node type '{0}' is invalid." + - " Only simple member access expressions for model properties are supported.", - expression.Body.NodeType), - ex.Message); + var response = await client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expected, exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs index 27182925da..623e961b3d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs @@ -925,11 +925,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var expectedMessage = "The supplied route name 'DuplicateRoute' is ambiguous and matched more than one route."; // Act - var ex = await Assert.ThrowsAsync(async () => - await client.GetAsync(url)); + var response = await client.GetAsync(url); // Assert - Assert.Equal(expectedMessage, ex.Message); + var exception = response.GetServerException(); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs index 4731677348..a0ea027f79 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs @@ -577,8 +577,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var request = new HttpRequestMessage(new HttpMethod("POST"), "http://localhost/api/Admin/Test?name=mario"); - // Act & Assert - await Assert.ThrowsAsync(async () => await client.SendAsync(request)); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(AmbiguousActionException).FullName, exception.ExceptionType); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs index 7d2840796d..d4d48c6177 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs @@ -50,9 +50,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "" + sampleInputInt.ToString() + ""; var content = new StringContent(input, Encoding.UTF8, "application/xml"); - // Act & Assert - await Assert.ThrowsAsync( - async () => await client.PostAsync("http://localhost/Home/Index", content)); + // Act + var response = await client.PostAsync("http://localhost/Home/Index", content); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } } } \ No newline at end of file diff --git a/test/WebSites/ActivatorWebSite/Startup.cs b/test/WebSites/ActivatorWebSite/Startup.cs index dfc3a68168..c48637d7f9 100644 --- a/test/WebSites/ActivatorWebSite/Startup.cs +++ b/test/WebSites/ActivatorWebSite/Startup.cs @@ -22,6 +22,9 @@ namespace ActivatorWebSite services.AddScoped(); }); + // Used to report exceptions that MVC doesn't handle + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => { diff --git a/test/WebSites/AntiForgeryWebSite/Startup.cs b/test/WebSites/AntiForgeryWebSite/Startup.cs index f54fd9b107..508c1e4378 100644 --- a/test/WebSites/AntiForgeryWebSite/Startup.cs +++ b/test/WebSites/AntiForgeryWebSite/Startup.cs @@ -17,6 +17,8 @@ namespace AntiForgeryWebSite services.AddMvc(configuration); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("ActionAsMethod", "{controller}/{action}", diff --git a/test/WebSites/FiltersWebSite/Startup.cs b/test/WebSites/FiltersWebSite/Startup.cs index e546ed9162..6449d67012 100644 --- a/test/WebSites/FiltersWebSite/Startup.cs +++ b/test/WebSites/FiltersWebSite/Startup.cs @@ -28,6 +28,8 @@ namespace FiltersWebSite }); }); + app.UseErrorReporter(); + app.UseMvc(); } } diff --git a/test/WebSites/InlineConstraintsWebSite/Startup.cs b/test/WebSites/InlineConstraintsWebSite/Startup.cs index 9f455c0d28..698deb6c3c 100644 --- a/test/WebSites/InlineConstraintsWebSite/Startup.cs +++ b/test/WebSites/InlineConstraintsWebSite/Startup.cs @@ -1,7 +1,6 @@ // 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.Builder; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; @@ -11,8 +10,6 @@ namespace InlineConstraints { public class Startup { - public Action RouteCollectionProvider { get; set; } - public void Configure(IApplicationBuilder app) { var configuration = app.GetTestConfiguration(); @@ -22,6 +19,8 @@ namespace InlineConstraints services.AddMvc(configuration); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("StoreId", diff --git a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs index 8813249a29..2b253d444c 100644 --- a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs +++ b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.TestConfiguration; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; @@ -18,5 +19,10 @@ namespace Microsoft.AspNet.Builder return configuration; } + + public static IApplicationBuilder UseErrorReporter(this IApplicationBuilder app) + { + return app.Use(next => new ErrorReporterMiddleware(next).Invoke); + } } } \ No newline at end of file diff --git a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs new file mode 100644 index 0000000000..fecc5835d6 --- /dev/null +++ b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs @@ -0,0 +1,51 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.TestConfiguration +{ + /// + /// A middleware that reports errors via header values. Useful for tests that want to verify + /// an exception that goes unhandled by the MVC part of the stack. + /// + public class ErrorReporterMiddleware + { + public static readonly string ExceptionMessageHeader = "ExceptionMessage"; + public static readonly string ExceptionTypeHeader = "ExceptionType"; + + private readonly RequestDelegate _next; + + public ErrorReporterMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception exception) + { + if (context.Response.HeadersSent) + { + throw; + } + else + { + context.Response.StatusCode = 500; + + var escapedMessage = exception.Message.Replace('\r', '_').Replace('\n', '_'); + + context.Response.Headers.Add(ExceptionTypeHeader, new string[] { exception.GetType().FullName }); + context.Response.Headers.Add(ExceptionMessageHeader, new string[] { escapedMessage }); + } + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs index ead23b1274..b3c8067032 100644 --- a/test/WebSites/ModelBindingWebSite/Startup.cs +++ b/test/WebSites/ModelBindingWebSite/Startup.cs @@ -30,6 +30,8 @@ namespace ModelBindingWebSite services.AddSingleton(); }); + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => { diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index c353939d84..099b773f8b 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -20,6 +20,8 @@ namespace RoutingWebSite services.AddScoped(); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("areaRoute", diff --git a/test/WebSites/WebApiCompatShimWebSite/Startup.cs b/test/WebSites/WebApiCompatShimWebSite/Startup.cs index 7b912327ec..485e8d9c18 100644 --- a/test/WebSites/WebApiCompatShimWebSite/Startup.cs +++ b/test/WebSites/WebApiCompatShimWebSite/Startup.cs @@ -19,6 +19,8 @@ namespace WebApiCompatShimWebSite services.AddWebApiConventions(); }); + app.UseErrorReporter(); + app.UseMvc(routes => { // This route can't access any of our webapi controllers diff --git a/test/WebSites/XmlSerializerWebSite/Startup.cs b/test/WebSites/XmlSerializerWebSite/Startup.cs index 6a044444d4..bd63f6cc0d 100644 --- a/test/WebSites/XmlSerializerWebSite/Startup.cs +++ b/test/WebSites/XmlSerializerWebSite/Startup.cs @@ -27,6 +27,8 @@ namespace XmlSerializerWebSite }); }); + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => {