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:
parent
9b11c1d90f
commit
e51e0e1d52
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue