1

I have a client using HttpClient.GetAsync to call into a Azure Function Http Trigger in .Net 5.

When I call the function using PostMan, I get my custom header data.

However, when I try to access my response object (HttpResponseMessage) that is returned from HttpClient.GetAsync, my header data empty.

I have my Content data and my Status Code. But my custom header data are missing.

Any insight would be appreciated since I have looking at this for hours.

Thanks for you help.

Edit: Here is the code where I am making the http call:

        public async Task<HttpResponseMessage> GetQuotesAsync(int? pageNo, int? pageSize, string searchText)
        {
            var requestUri = $"{RequestUri.Quotes}?pageNo={pageNo}&pageSize={pageSize}&searchText={searchText}";
            return await _httpClient.GetAsync(requestUri);
        }

Edit 8/8/2021: See my comment below. The issue has something to do with using Blazor Wasm Client.

Richard
  • 1,054
  • 1
  • 19
  • 36

4 Answers4

2

For anyone having problems after following the tips on this page, go back and check the configuration in the host.json file. you need the Access-Control-Expose-Headers set to * or they won't be send even if you add them. Note: I added the "extensions" node below and removed my logging settings for clarity.

host.json (sample file):

{
  "version": "2.0",
  "extensions": {
    "http": {
      "customHeaders": {
        "Access-Control-Expose-Headers": "*"
      }
    }
  }
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
1

This is because HttpResponseMessage's Headers property data type is HttpResponseHeaders but HttpResponseData's Headers property data type is HttpHeadersCollection. Since, they are different, HttpResponseHeaders could not bind to HttpHeadersCollection while calling HttpClient.GetAsync(as it returns HttpResponseMessage).

I could not find a way to read HttpHeadersCollection through HttpClient.

Harshita Singh
  • 4,590
  • 1
  • 10
  • 13
  • Thanks for the reply. That make sense. So, basically the deserialization is not working since the they are 2 different data types / names. How did you resolve this issue? What work around did you do? – Richard Aug 05 '21 at 15:06
  • I could no find any way to read those headers in this case but I am researching and will share the solution if I find. – Harshita Singh Aug 06 '21 at 07:47
  • Thanks for letting me know. For me, I am returning the TotalCount of items in the header. So, I am going to make a change to get that value as a separate call. Not ideal but I already have the method available. Do you think when .Net 6 comes out this issue will resolved? Do you think the httpClient for .Net 6 will use HttpResponseData? – Richard Aug 06 '21 at 13:04
  • @HarshitaSingh see https://stackoverflow.com/a/71080532/9276081 – Bandook Feb 11 '22 at 13:26
1

As long as your Azure function code is emitting the header value, you should be able to read that in your client code from the Headers collection of HttpResponseMessage. Nothing in your azure function (which is your remote endpoint you are calling) makes it any different. Remember, your client code has no idea how your remote endpoint is implemented. Today it is azure functions, tomorrow it may be a full blown aspnet core web api or a REST endpoint written in Node.js. Your client code does not care. All it cares is whether the Http response it received has your expected header.

Asumming you have an azure function like this where you are adding a header called total-count to the response.

[Function("quotes")]
public static async Task<HttpResponseData> RunAsync(
             [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
             FunctionContext executionContext)
{
    var logger = executionContext.GetLogger("Quotes");
    logger.LogInformation("C# HTTP trigger function processed a request for Quotes.");

    var quotes = new List<Quote>
    {
        new Quote { Text = "Hello", ViewCount = 100},
        new Quote { Text = "Azure Functions", ViewCount = 200}
    };

    var response = req.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("total-count", quotes.Count.ToString());
    await response.WriteAsJsonAsync(quotes);

    return response;
}

Your existing client code should work as long as you read the Headers property.

public static async Task<HttpResponseMessage> GetQuotesAsync()
{
    var requestUri = "https://shkr-playground.azurewebsites.net/api/quotes";
    return await _httpClient.GetAsync(requestUri);
}

Now your GetQuotesAsync method can be called somewhere else where you will use the return value of it (HttpResponseMessage instance) and read the headers. In the below example, I am reading that value and adding to a string variable. HttpResponseMessage implements IDisposable. So I am using a using construct to implicitly call the Dispose method.

var msg = "Total count from response headers:";

using (var httpResponseMsg = await GetQuotesAsync())
{
    if (httpResponseMsg.Headers.TryGetValues("total-count", out var values))
    {
        msg += values.FirstOrDefault(); 
    }
}
// TODO: use "msg" variable as needed.

The types which Azure function uses for dealing with response headers is more of an implementation concern of azure functions. It has no impact on your client code where you are using HttpClient and HttpResponseMessage. Your client code is simply dealing with standard http call response (response headers and body)

Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thank you so much for responding. I understood everything you said and I agree with you. It should just work. I have an existing REST API and Blazor Wasm Client which works. What I am trying to do is swap out my REST API for the Azure Function. I would of though I could do that but I can't get the header data. I get the Content (Quotes) and the Status Code. I tried your example above. I set up an Azure Function coping/pasting your code. Then I updated my Blazor Wasm Client. Just like your above. But, I can't get the header data. – Richard Aug 08 '21 at 11:29
  • 1
    The Header collection is completely empty. No header values exist. Even the standard ones. When I try it with Postman, I get everything. You were able to get this to work? What are you using as a Client? Mine is Blazor Wasm. Could that have something to do with it? Any other ideas? If you do please let me know. I have a few ideas for work around but I don't like any of them. I really want to get this to work the right way. Thanks!!!!!!!!!!!!!! – Richard Aug 08 '21 at 11:34
  • The problem has something to do with the Blazor Wasm Client. I created a ASP.Net MVC and a Blazor Server client, both worked. I tried my existing Blazor Wasm Client and a new Blazor Wasm client. Neither worked. Thanks for your help. At least you helped me narrow down the issue. I am going to search to see if I can find a resolution. If you have any ideas, let me know. – Richard Aug 08 '21 at 20:20
  • I wrote a second azure functions and that was my client code which is calling my quotes api(which is my first azure function). Looks like client code on other platforms are working for you. In blazor, you are using the HttpClient and HttpResponseMessage. So it should work similar to how it works in a console app since it is also same C# code. – Shyju Aug 08 '21 at 20:36
  • I agree with you it should work, but it doesn't. I created both a Blazor Server and Blazor Wasm. Used the same code. The Blazor Server worked. The Blazor wasm did not. It looks as though for when I set up my API to work with my Blazor Wasm I had to add WithExposedHeaders("*") in my CORS policy in my Startup.cs. Looking to see now how I can set up the set that up in my Azure Function. Any ideas? I currently have "CORS":"*" in my local.settings.json. – Richard Aug 08 '21 at 20:58
  • @Shyju see https://stackoverflow.com/a/71080532/9276081 – Bandook Feb 11 '22 at 13:26
0

The issue is not with Blazor WASM, rather if that header has been exposed on your API Side. In your azure function, add the following -

Note: Postman will still show the headers even if you don't expose the headers like below. That's because, Postman doesn't care about CORS headers. CORS is just a browser concept and not a strong security mechanism. It allows you to restrict which other web apps may use your backend resources and that's all.

First create a Startup File to inject the HttpContextAccessor

Package Required: Microsoft.Azure.Functions.Extensions

[assembly: FunctionsStartup(typeof(FuncAppName.Startup))]

namespace FuncAppName
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped<HttpContextAccessor>();
        }
    }
}

Next, inject it into your main Function -

using Microsoft.AspNetCore.Http;
namespace FuncAppName
{
    public class SomeFunction
    {
        private readonly HttpContext _httpContext;
        public SomeFunction(HttpContextAccessor contextAccessor)
        {
             _httpContext = contextAccessor.HttpContext;
        }

        [FunctionName("SomeFunc")]
        public override Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "post" }, Route = "run")] HttpRequest req)
        {
            var response = "Some Response"
            _httpContext.Response.Headers.Add("my-custom-header", "some-custom-value");
            _httpContext.Response.Headers.Add("my-other-header", "some-other-value");
            _httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-custom-header, my-other-header");
            return new OkObjectResult(response)
        }

If you want to allow all headers you can use wildcard (I think, not tested) -

_httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "*");

You still need to add your web-app url to the azure platform CORS. You can add * wildcard, more info here - https://iotespresso.com/allowing-all-cross-origin-requests-azure-functions/

to enable CORS for Local Apps during development - https://stackoverflow.com/a/60109518/9276081

Now to access those headers in your Blazor WASM, as an e.g. you can -

protected override async Task OnInitializedAsync()
{
    var content = JsonContent.Create(new { query = "" });
    using (var client = new HttpClient())
    {
        var result = await client.PostAsync("https://func-app-name.azurewebsites.net/api/run", content);
        var headers = result.Headers.ToList();
    }
}
Bandook
  • 658
  • 6
  • 21