How to change the HTTP Request Content Type for FLURL Client?

10,055

Solution 1

The comments and another post I found (will add reference when I find it again) have pointed me to the right direction. The solution for my problem looks like:

        var jobInJson = JsonConvert.SerializeObject(job);
        var json = new StringContent(jobInJson, Encoding.UTF8);
        json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; odata=verbose");

        var flurClient = GetBaseUrlForOperations("Jobs");

        return await flurClient.PostAsync(json).ReceiveJson<Job>();

Edit: Found the related SO question: Azure encoding job via REST Fails

Solution 2

This answer is outdated. Upgrade to latest version (2.0 or above) and the problem goes away.

It turns out the real issue has to do with how the System.Net.Http APIs validate headers. It makes a distinction between request-level headers and content-level headers, which I've always found a bit odd since raw HTTP makes no such distinction (except perhaps in multipart scenarios). Flurl's WithHeader adds headers to the HttpRequestMessage object but is failing validation for Content-Type, which it expects to be added to the HttpContent object.

Those APIs do allow you to skip validation, and although Flurl doesn't expose it directly, you can get under the hood pretty easily, without breaking the fluent chain:

return await GetBaseUrlForGetOperations("Jobs")
    .ConfigureHttpClient(c => c.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json;odata=verbose"))
    .PostJsonAsync(new { ... })
    .ReceiveJson<Job>();

This is probably the best way to do what you need and still take advantage of Flurl's goodness, i.e. not have to directly deal with serialization, HttpContent objects, etc.

I'm strongly considering changing Flurl's AddHeader(s) implementations to use TryAddWithoutValidation based on this issue.

Solution 3

Am I allowed to post 3 answers to the same question? :)

Upgrade. Flurl.Http 2.0 includes the following enhancements to headers:

  1. WithHeader(s) now uses TryAddWithoutValidation under the hood. With that change alone, the OP's code will work as originally posted.

  2. Headers are now set at the request level, which solves another known issue.

  3. When using SetHeaders with object notation, underscores in property names will be converted to hyphens in the header names, since hyphens in headers are very common, underscores are not, and hyphens are not allowed in C# identifiers.

This will be useful in your case:

.WithHeaders(new {
    x_ms_version = "2.11",
    Accept = "application/json"
});

Solution 4

public static class Utils
{
    public static IFlurlClient GetBaseUrlForOperations(string resource)
    {
        var _apiUrl = "https://api.mobile.azure.com/v0.1/apps/";

        var url = _apiUrl
            .AppendPathSegment("Red-Space")
            .AppendPathSegment("HD")
            .AppendPathSegment("push")
            .AppendPathSegment("notifications")
            .WithHeader("Accept", "application/json")
            .WithHeader("X-API-Token", "myapitocken");

            return url;
    }

    public static async Task Invia()
    {
        FlurlClient _client;
        PushMessage pushMessage = new PushMessage();
        pushMessage.notification_content = new NotificationContent();

        try
        {
            var flurClient = Utils.GetBaseUrlForOperations("risorsa");
            // News news = (News)contentService.GetById(node.Id);
            //pushMessage.notification_target.type = "";
            pushMessage.notification_content.name = "A2";
            // pushMessage.notification_content.title = node.GetValue("TitoloNews").ToString();
            pushMessage.notification_content.title = "Titolo";
            pushMessage.notification_content.body = "Contenuto";
            var jobInJson = JsonConvert.SerializeObject(pushMessage);
            var json = new StringContent(jobInJson, Encoding.UTF8);
            json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
            dynamic data2 = await flurClient.PostAsync(json).ReceiveJson();
            var expandoDic = (IDictionary<string, object>)data2;
            var name = expandoDic["notification_id"];
            Console.WriteLine(name);
        }
        catch (FlurlHttpTimeoutException ex)
        {
            Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
        }
        catch (FlurlHttpException ex)
        {
            Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
            if (ex.Call.Response != null)
                Console.WriteLine("Failed with response code " + ex.Call.Response.StatusCode);
            else
                Console.WriteLine("Totally failed before getting a response! " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
        }
    }
}

public class NotificationTarget
{
    public string type { get; set; }
}

public class CustomData {}

public class NotificationContent
{
    public string name { get; set; }
    public string title { get; set; }
    public string body { get; set; }
    public CustomData custom_data { get; set; }
}

public class PushMessage
{
    public NotificationTarget notification_target { get; set; }
    public NotificationContent notification_content { get; set; }
}
Share:
10,055
mJay
Author by

mJay

Updated on June 05, 2022

Comments

  • mJay
    mJay almost 2 years

    I am using flurl to submit HTTP request and this is very useful. Now I need to change the "Content-Type" header for some of the requests to "application/json;odata=verbose"

        public async Task<Job> AddJob()
        {
    
            var flurlClient = GetBaseUrlForGetOperations("Jobs").WithHeader("Content-Type", "application/json;odata=verbose");
            return await flurlClient.PostJsonAsync(new
            {
                //Some parameters here which are not the problem since tested with Postman
    
            }).ReceiveJson<Job>();
        }
    
        private IFlurlClient GetBaseUrlForOperations(string resource)
        {
            var url = _azureApiUrl
                .AppendPathSegment("api")
                .AppendPathSegment(resource)
                .WithOAuthBearerToken(AzureAuthentication.AccessToken)
                .WithHeader("x-ms-version", "2.11")
                .WithHeader("Accept", "application/json");
            return url;
        }
    

    You can see how I tried to add the header above (.WithHeader("Content-Type", "application/json;odata=verbose"))

    Unfortunately this gives me following error:

    "InvalidOperationException: Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects."

    I also tried flurl's "ConfigureHttpClient" method but could not find how/where to set the content type header.

  • mJay
    mJay almost 7 years
    I am using the Azure Media Services API. As described here docs.microsoft.com/en-us/rest/api/media/operations/job setting the accept header as you described should be enough. But it works only if I set the Content-Type header to the same
  • Todd Menier
    Todd Menier almost 7 years
    That'll work. I posted an alternative answer that you might like even better.
  • Robert Wagenaar
    Robert Wagenaar over 6 years
    thanks! very happy with this, thankfully, I can use flurl now with an Api that requires a header that system.net.http considers invalid
  • Sascha
    Sascha over 5 years
    Hi Todd, could you please add a hint / edit your answer from July 14th 2017. This one here is pretty easy to miss. Thanks :-)
  • Todd Menier
    Todd Menier over 5 years
    I did. Did you miss the part in bold? ;)