diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
index 45b8c0a1f1..3d9c2bfd0f 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs
@@ -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;
+
/// Gets the action context.
/// The setter is intended for unit testing purposes only.
[Activate]
@@ -40,6 +43,25 @@ namespace System.Web.Http
}
}
+ /// Gets or sets the HTTP request message.
+ /// The setter is intended for unit testing purposes only.
+ public HttpRequestMessage Request
+ {
+ get
+ {
+ if (_request == null && ActionContext != null)
+ {
+ _request = ActionContext.HttpContext.GetHttpRequestMessage();
+ }
+
+ return _request;
+ }
+ set
+ {
+ _request = value;
+ }
+ }
+
/// Gets a factory used to generate URLs to other APIs.
/// The setter is intended for unit testing purposes only.
[Activate]
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs
new file mode 100644
index 0000000000..19f7d90591
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs
new file mode 100644
index 0000000000..4a96fd3859
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs
@@ -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();
+ if (feature == null)
+ {
+ feature = new HttpRequestMessageFeature(httpContext);
+ httpContext.SetFeature(feature);
+ }
+
+ return feature.HttpRequestMessage;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs
new file mode 100644
index 0000000000..9f6b19dc3b
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.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.Net.Http;
+
+namespace Microsoft.AspNet.Mvc.WebApiCompatShim
+{
+ public interface IHttpRequestMessageFeature
+ {
+ HttpRequestMessage HttpRequestMessage { get; set; }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
index c15ebb13f7..a53c0b695b 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs
@@ -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
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs
new file mode 100644
index 0000000000..09912e7504
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs
@@ -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(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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs b/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs
new file mode 100644
index 0000000000..174b0d7fc6
--- /dev/null
+++ b/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs
@@ -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 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();
+ }
+ }
+}
\ No newline at end of file