Fix for issue #1279 - Add .Request property to ApiController

This change adds a .Request property to the ApiController class that can
be used to access an HttpRequestMessage wrapping the HttpContext.

The HttpRequestMessage is stored in an http feature to make it accessible
to model binders and other infrastructure.
This commit is contained in:
Ryan Nowak 2014-10-08 14:52:04 -07:00
parent 9b11c1d90f
commit e51e0e1d52
7 changed files with 313 additions and 0 deletions

View File

@ -1,6 +1,7 @@
// 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;
using System.Security.Principal;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
@ -13,6 +14,8 @@ namespace System.Web.Http
[UseWebApiOverloading]
public abstract class ApiController : IDisposable
{
private HttpRequestMessage _request;
/// <summary>Gets the action context.</summary>
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
[Activate]
@ -40,6 +43,25 @@ namespace System.Web.Http
}
}
/// <summary>Gets or sets the HTTP request message.</summary>
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
public HttpRequestMessage Request
{
get
{
if (_request == null && ActionContext != null)
{
_request = ActionContext.HttpContext.GetHttpRequestMessage();
}
return _request;
}
set
{
_request = value;
}
}
/// <summary>Gets a factory used to generate URLs to other APIs.</summary>
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
[Activate]

View File

@ -0,0 +1,70 @@
// 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.Diagnostics.Contracts;
using System.Net.Http;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class HttpRequestMessageFeature : IHttpRequestMessageFeature
{
private readonly HttpContext _httpContext;
private HttpRequestMessage _httpRequestMessage;
public HttpRequestMessageFeature([NotNull] HttpContext httpContext)
{
_httpContext = httpContext;
}
public HttpRequestMessage HttpRequestMessage
{
get
{
if (_httpRequestMessage == null)
{
_httpRequestMessage = CreateHttpRequestMessage(_httpContext);
}
return _httpRequestMessage;
}
set
{
_httpRequestMessage = value;
}
}
private static HttpRequestMessage CreateHttpRequestMessage(HttpContext httpContext)
{
var httpRequest = httpContext.Request;
var uriString =
httpRequest.Scheme + "://" +
httpRequest.Host +
httpRequest.PathBase +
httpRequest.Path +
httpRequest.QueryString;
var message = new HttpRequestMessage(new HttpMethod(httpRequest.Method), uriString);
// This allows us to pass the message through APIs defined in legacy code and then
// operate on the HttpContext inside.
message.Properties[nameof(HttpContext)] = httpContext;
message.Content = new StreamContent(httpRequest.Body);
foreach (var header in httpRequest.Headers)
{
// Every header should be able to fit into one of the two header collections.
// Try message.Headers first since that accepts more of them.
if (!message.Headers.TryAddWithoutValidation(header.Key, header.Value))
{
var added = message.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
Contract.Assert(added);
}
}
return message;
}
}
}

View File

@ -0,0 +1,23 @@
// 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;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public static class HttpRequestMessageHttpContextExtensions
{
public static HttpRequestMessage GetHttpRequestMessage(this HttpContext httpContext)
{
var feature = httpContext.GetFeature<IHttpRequestMessageFeature>();
if (feature == null)
{
feature = new HttpRequestMessageFeature(httpContext);
httpContext.SetFeature(feature);
}
return feature.HttpRequestMessage;
}
}
}

View File

@ -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.Net.Http;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public interface IHttpRequestMessageFeature
{
HttpRequestMessage HttpRequestMessage { get; set; }
}
}

View File

@ -4,6 +4,7 @@
#if ASPNET50
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
@ -78,6 +79,28 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expected, formatters);
}
[Fact]
public async Task ApiController_RequestProperty()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
var expected =
"POST http://localhost/api/Blog/HttpRequestMessage/EchoProperty localhost " +
"13 Hello, world!";
// Act
var response = await client.PostAsync(
"http://localhost/api/Blog/HttpRequestMessage/EchoProperty",
new StringContent("Hello, world!"));
var content = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expected, content);
}
}
}
#endif

View File

@ -0,0 +1,134 @@
// 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.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.PipelineCore;
using Xunit;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
public class HttpRequestMessageFeatureTest
{
[Fact]
public void HttpRequestMessage_CombinesUri()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "GET";
context.Request.Scheme = "http";
context.Request.Host = new HostString("contoso.com");
context.Request.PathBase = new PathString("/app");
context.Request.Path = new PathString("/api/Products");
context.Request.QueryString = new QueryString("?orderId=3");
// Act
var request = feature.HttpRequestMessage;
// Assert
Assert.Equal("http://contoso.com/app/api/Products?orderId=3", request.RequestUri.AbsoluteUri);
}
[Fact]
public void HttpRequestMessage_CopiesRequestMethod()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "OPTIONS";
// Act
var request = feature.HttpRequestMessage;
// Assert
Assert.Equal(new HttpMethod("OPTIONS"), request.Method);
}
[Fact]
public void HttpRequestMessage_CopiesHeader()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "OPTIONS";
context.Request.Headers.Add("Host", new string[] { "contoso.com" });
// Act
var request = feature.HttpRequestMessage;
// Assert
Assert.Equal("contoso.com", request.Headers.Host);
}
[Fact]
public void HttpRequestMessage_CopiesContentHeader()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "OPTIONS";
context.Request.Headers.Add("Content-Type", new string[] { "text/plain" });
// Act
var request = feature.HttpRequestMessage;
// Assert
Assert.Equal("text/plain", request.Content.Headers.ContentType.ToString());
}
[Fact]
public async Task HttpRequestMessage_WrapsBodyContent()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "OPTIONS";
var bytes = Encoding.UTF8.GetBytes("Hello, world!");
context.Request.Body = new MemoryStream(bytes);
context.Request.Body.Seek(0, SeekOrigin.Begin);
// Act
var request = feature.HttpRequestMessage;
// Assert
var streamContent = Assert.IsType<StreamContent>(request.Content);
var content = await request.Content.ReadAsStringAsync();
Assert.Equal("Hello, world!", content);
}
[Fact]
public void HttpRequestMessage_CachesMessage()
{
// Arrange
var context = new DefaultHttpContext();
var feature = new HttpRequestMessageFeature(context);
context.Request.Method = "GET";
context.Request.Scheme = "http";
context.Request.Host = new HostString("contoso.com");
// Act
var request1 = feature.HttpRequestMessage;
context.Request.Path = new PathString("/api/Products");
var request2 = feature.HttpRequestMessage;
// Assert
Assert.Same(request1, request2);
Assert.Equal("/", request2.RequestUri.AbsolutePath);
}
}
}

View File

@ -0,0 +1,29 @@
// 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.Threading.Tasks;
using System.Web.Http;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
namespace WebApiCompatShimWebSite
{
public class HttpRequestMessageController : ApiController
{
public async Task<IActionResult> EchoProperty()
{
var request = Request;
var message = string.Format(
"{0} {1} {2} {3} {4}",
request.Method,
request.RequestUri.AbsoluteUri,
request.Headers.Host,
request.Content.Headers.ContentLength,
await request.Content.ReadAsStringAsync());
await Context.Response.WriteAsync(message);
return new EmptyResult();
}
}
}