Adding HttpResponseException to WebApi Shim.

This commit is contained in:
Harsh Gupta 2014-10-11 02:04:19 -07:00
parent d915994f0b
commit ebf64ce4e3
11 changed files with 362 additions and 2 deletions

View File

@ -0,0 +1,36 @@
// 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.Net;
using System.Net.Http;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class HttpResponseException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpResponseException"/> class.
/// </summary>
/// <param name="statusCode">The status code of the response.</param>
public HttpResponseException(HttpStatusCode statusCode)
: this(new HttpResponseMessage(statusCode))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpResponseException"/> class.
/// </summary>
/// <param name="response">The response message.</param>
public HttpResponseException([NotNull] HttpResponseMessage response)
: base(Resources.HttpResponseExceptionMessage)
{
Response = response;
}
/// <summary>
/// Gets the <see cref="HttpResponseMessage"/> to return to the client.
/// </summary>
public HttpResponseMessage Response { get; private set; }
}
}

View File

@ -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.Net.Http;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
/// <summary>
/// An action filter which sets <see cref="ActionExecutedContext.Result"/> to an <see cref="ObjectResult"/>
/// if the exception type is <see cref="HttpResponseException"/>.
/// This filter runs immediately after the action.
/// </summary>
public class HttpResponseExceptionActionFilter : IActionFilter, IOrderedFilter
{
// Return a high number by default so that it runs closest to the action.
public int Order { get; set; } = int.MaxValue - 10;
public void OnActionExecuting([NotNull] ActionExecutingContext context)
{
}
public void OnActionExecuted([NotNull] ActionExecutedContext context)
{
var httpResponseException = context.Exception as HttpResponseException;
if (httpResponseException != null)
{
var request = context.HttpContext.GetHttpRequestMessage();
var response = httpResponseException.Response;
if (response != null && response.RequestMessage == null)
{
response.RequestMessage = request;
}
var objectResult = new ObjectResult(response)
{
DeclaredType = typeof(HttpResponseMessage)
};
context.Result = objectResult;
// Its marked as handled as in webapi because an HttpResponseException
// was considered as a 'success' response.
context.ExceptionHandled = true;
}
}
}
}

View File

@ -138,6 +138,22 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
return string.Format(CultureInfo.CurrentCulture, GetString("MaxHttpCollectionKeyLimitReached"), p0, p1);
}
/// <summary>
/// Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
/// </summary>
internal static string HttpResponseExceptionMessage
{
get { return GetString("HttpResponseExceptionMessage"); }
}
/// <summary>
/// Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
/// </summary>
internal static string FormatHttpResponseExceptionMessage()
{
return GetString("HttpResponseExceptionMessage");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -141,4 +141,7 @@
<data name="MaxHttpCollectionKeyLimitReached" xml:space="preserve">
<value>The number of keys in a NameValueCollection has exceeded the limit of '{0}'. You can adjust it by modifying the MaxHttpCollectionKeys property on the '{1}' class.</value>
</data>
<data name="HttpResponseExceptionMessage" xml:space="preserve">
<value>Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.</value>
</data>
</root>

View File

@ -25,6 +25,9 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
options.ApplicationModelConventions.Add(new WebApiRoutesGlobalModelConvention(area: DefaultAreaName));
// Add an action filter for handling the HttpResponseException.
options.Filters.Add(new HttpResponseExceptionActionFilter());
// Add a model binder to be able to bind HttpRequestMessage
options.ModelBinders.Insert(0, new HttpRequestMessageModelBinder());

View File

@ -83,6 +83,78 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(expected, formatters);
}
[Fact]
public async Task ActionThrowsHttpResponseException_WithStatusCode()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/api/Blog/HttpResponseException/ThrowsHttpResponseExceptionWithHttpStatusCode");
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(string.Empty, content);
}
[Fact]
public async Task ActionThrowsHttpResponseException_WithResponse()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/api/Blog/HttpResponseException"+
"/ThrowsHttpResponseExceptionWithHttpResponseMessage?message=send some message");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal("send some message", content);
}
[Fact]
public async Task ActionThrowsHttpResponseException_EnsureGlobalHttpresponseExceptionActionFilter_IsInvoked()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/api/Blog/HttpResponseException/ThrowsHttpResponseExceptionEnsureGlobalFilterRunsLast");
// Assert
// Ensure we do not get a no content result.
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(string.Empty, content);
}
[Fact]
public async Task ActionThrowsHttpResponseException_EnsureGlobalFilterConvention_IsApplied()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/api/Blog/"+
"HttpResponseException/ThrowsHttpResponseExceptionInjectAFilterToHandleHttpResponseException");
// Assert
// Ensure we do get a no content result.
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(string.Empty, content);
}
[Fact]
public async Task ApiController_CanValidateCustomObjectWithPrefix_Fails()
{

View File

@ -0,0 +1,75 @@
// 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.PipelineCore;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class HttpResponseExceptionActionFilterTest
{
[Fact]
public void OnActionExecuting_IsNoOp()
{
// Arrange
var filter = new HttpResponseExceptionActionFilter();
var context = new ActionExecutingContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
actionDescriptor: Mock.Of<ActionDescriptor>()),
filters: Mock.Of<IList<IFilter>>(),
actionArguments: new Dictionary<string, object>());
// Act
filter.OnActionExecuting(context);
// Assert
Assert.Null(context.Result);
}
[Fact]
public void OrderIsSetToMaxValue()
{
// Arrange
var filter = new HttpResponseExceptionActionFilter();
var expectedFilterOrder = int.MaxValue - 10;
// Act & Assert
Assert.Equal(expectedFilterOrder, filter.Order);
}
[Fact]
public void OnActionExecuted_HandlesExceptionAndReturnsObjectResult()
{
// Arrange
var filter = new HttpResponseExceptionActionFilter();
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
var context = new ActionExecutedContext(
new ActionContext(
httpContext,
new RouteData(),
actionDescriptor: Mock.Of<ActionDescriptor>()),
filters: null);
context.Exception = new HttpResponseException(HttpStatusCode.BadRequest);
// Act
filter.OnActionExecuted(context);
// Assert
Assert.True(context.ExceptionHandled);
var result = Assert.IsType<ObjectResult>(context.Result);
Assert.Equal(typeof(HttpResponseMessage), result.DeclaredType);
var response = Assert.IsType<HttpResponseMessage>(result.Value);
Assert.NotNull(response.RequestMessage);
Assert.Equal(context.HttpContext.GetHttpRequestMessage(), response.RequestMessage);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}
}

View File

@ -0,0 +1,44 @@
// 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.Net;
using System.Net.Http;
using Microsoft.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class HttpResponseExceptionTest
{
[Fact]
[ReplaceCulture]
public void Constructor_SetsResponseProperty()
{
// Arrange and Act
var response = new HttpResponseMessage();
var exception = new HttpResponseException(response);
// Assert
Assert.Same(response, exception.Response);
Assert.Equal("Processing of the HTTP request resulted in an exception."+
" Please see the HTTP response returned by the 'Response' "+
"property of this exception for details.",
exception.Message);
}
[Fact]
[ReplaceCulture]
public void Constructor_SetsResponsePropertyWithGivenStatusCode()
{
// Arrange and Act
var exception = new HttpResponseException(HttpStatusCode.BadGateway);
// Assert
Assert.Equal(HttpStatusCode.BadGateway, exception.Response.StatusCode);
Assert.Equal("Processing of the HTTP request resulted in an exception."+
" Please see the HTTP response returned by the 'Response' "+
"property of this exception for details.",
exception.Message);
}
}
}

View File

@ -5,6 +5,7 @@
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*",
"Moq": "4.2.1312.1622",
"Xunit.KRunner": "1.0.0-*"
},

View File

@ -0,0 +1,62 @@
// 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.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.WebApiCompatShim;
namespace WebApiCompatShimWebSite
{
public class HttpResponseExceptionController : ApiController
{
[HttpGet]
public object ThrowsHttpResponseExceptionWithHttpStatusCode()
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
[HttpGet]
public object ThrowsHttpResponseExceptionWithHttpResponseMessage(string message)
{
var httpResponse = new HttpResponseMessage();
httpResponse.Content = new StringContent(message);
throw new HttpResponseException(httpResponse);
}
[TestActionFilter]
[HttpGet]
public object ThrowsHttpResponseExceptionEnsureGlobalFilterRunsLast()
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
// Runs before the HttpResponseExceptionActionFilter's OnActionExecuted.
[TestActionFilter(Order = int.MaxValue)]
[HttpGet]
public object ThrowsHttpResponseExceptionInjectAFilterToHandleHttpResponseException()
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
}
public class TestActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (!context.ExceptionHandled)
{
var httpResponseException = context.Exception as HttpResponseException;
if (httpResponseException != null)
{
context.Result = new NoContentResult();
context.ExceptionHandled = true;
// Null it out so that next filter do not handle it.
context.Exception = null;
}
}
}
}
}

View File

@ -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.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
@ -16,10 +17,9 @@ namespace WebApiCompatShimWebSite
app.UseServices(services =>
{
services.AddMvc(configuration);
services.AddWebApiConventions();
});
app.UseMvc(routes =>
{
// This route can't access any of our webapi controllers