How to return a HttpResponseMessage in a .net Core MVC Controller without Serializing it as JSON?

5.1k Views Asked by At

I'm researching a web proxy pattern that makes use of ASP.Net Core MVC project type. I'd like to basically pass a HttpRequestMessage to a httpClient in the controller which then makes a request to a remote website (like https://www.abc.fake) and then returns the response exactly as it came back from the client (body and headers). Example code:

    [Microsoft.AspNetCore.Mvc.Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    //[HttpGet]
    public async Task<HttpResponseMessage> Get()
    {
        var httpClient = new HttpClient();
        
         var resp = await httpClient.GetAsync("https://www.abc.fake");

        return resp;
        
    }
}

The problem is every time I do this I just get a JSON serialized version of the response message. It's not actually sending back the HTML I get from the remote website. This is what I'm seeing:

{"version":{"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1},"content":{"headers":[{"Key":"Content-Type","Value":["text/html; charset=utf-8"]},{"Key":"Content-Length","Value":["1036119"]},{"Key":"Expires","Value":["Wed, 23 Sep 2020 21:44:35 GMT"]},{"Key":"Last-Modified","Value":["Wed, 23 Sep 2020 21:44:35 GMT"]}]},"statusCode":200,"reasonPhrase":"OK","headers":[{"Key":"Connection","Value":["keep-alive"]},{"Key":"Vary","Value":["Accept-Encoding","Accept-Encoding"]},{"Key":"Date","Value":["Wed, 23 Sep 2020 21:41:54 GMT"]},{"Key":"Server","Value":["nginx/1.16.1"]},{"Key":"Via","Value":["1.1 varnish-v4","1.1 e8afb729a4bc6f5676d32307ea14bdae.cloudfront.fake (CloudFront)"]},{"Key":"Accept-Ranges","Value":["bytes"]},{"Key":"Cache-Control","Value":["must-revalidate, max-age=0"]},{"Key":"Set-Cookie","Value":["SWID=0C8B6C96-3F05-43D5-C3D1-2676E1C15F8C; path=/; Expires=Sun, 23 Sep 2040 21:41:54 GMT; domain=abc.fake;"]},{"Key":"X-Cache","Value":["Miss from cloudfront"]},{"Key":"X-Amz-Cf-Pop","Value":["HIO50-C1"]},{"Key":"X-Amz-Cf-Id","Value":["yKz-d9KhZdb-5qdDpppD0jeFqYHfFQA4Z1RT98Nk31eaH7kB_FXisQ=="]}],"trailingHeaders":[],"requestMessage":{"version":{"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1},"content":null,"method":{"method":"GET"},"requestUri":"https://abc.fake/","headers":[{"Key":"Request-Id","Value":["|8e9d36f9-4b9e69ca8ec31ee9.1."]}],"properties":{}},"isSuccessStatusCode":true}
3

There are 3 best solutions below

1
On

After following the link to AspNetCore.Proxy nuget package that @tym32167 referred me to; I can verify that does what I wanted. For anyone that wants to do this, it's basically this simple:

[Route("{*everything}")]
public async Task Get()
{
    var host = Request.Host.Host;
    string path = Request.Path;
    string queryString = null;
    if (Request.QueryString != null)
    {
        var queryStringBuilder = new StringBuilder();
        var isFirstParameter = true;
        foreach (var parameter in Request.Query)
        {
            var leadingCharacter = isFirstParameter ? "?" : "&";
            queryStringBuilder.Append($"{leadingCharacter}{parameter.Key}={parameter.Value}");
            isFirstParameter = false;
        }
        queryString = queryStringBuilder.ToString();
    }
    var requestUrl = $"{Request.Scheme}://{host}{path}{queryString}";
    var b = HttpProxyOptionsBuilder.Instance.New();
    b.WithHttpClientName("default");
    b.WithShouldAddForwardedHeaders(true);
    var options = b.Build();         
    await this.HttpProxyAsync(requestUrl, options);
}
4
On

Try something like this

public async Task<IActionResult> Get()
{
     HttpClient httpClient = new HttpClient();
     var resp = await httpClient.GetAsync("https://www.abc.fake");
     if (!resp.IsSuccessStatusCode)
         return BadRequest();

     return resp.Content;
    
}
0
On

I'm a bit late to this party, but it's worth mentioning that this mapping was very common in the early days for interop between existing Web API codebases transitioning to ASP.NET Core. It's no longer maintained, but there was a Web API Shim library written by the ASP.NET team to support the transition. Within that repo, there are 2 key files you can grab:

To wire it up, you just need to add the output formatter like this:

mvcOptions.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());

Using it is the same as it was back in the Web API model like this:

[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    readonly IHttpClientFactory factory;

    public WeatherForecastController(IHttpClientFactory factory) => this.factory = factory;

    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken cancellationToken)
    {
        using var client = factory.CreateClient("WeatherChannel");
        var response = await client.GetAsync("https://weather.com/", cancellationToken);
        return new ResponseMessageResult(response);
    }
}

Since you mentioned you are creating a proxy, you can also build a HttpRequestMesage from the incoming HttpRequest if you need to forward it on to the HttpClient. The code is a bit more involved, but you can get everything you need from the WebApiCompatShim source.