11

I have an issue with HttpClient.PostAsJsonAsync()

In addition to "application/json" in the "Content-Type" header the method also adds "charset=utf-8"

so the header looks like this:

Content-Type: application/json; charset=utf-8

While ASP.NET WebAPI doesn't have any issue with this header, i've found that other WebAPIs I work against as a client don't accept request with this header, unless it's only application/json.

Is there anyway to remove the "charset=utf-8" from Content-Type when using PostAsJsonAsync(), or should I use another method?

SOLUTION: Credits to Yishai!

using System.Net.Http.Headers;

public class NoCharSetJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
   public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
   {
       base.SetDefaultContentHeaders(type, headers, mediaType);
       headers.ContentType.CharSet = "";
   }
}

public static class HttpClientExtensions
{
    public static async Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken)
    {
        return await client.PostAsync(requestUri, value, new NoCharSetJsonMediaTypeFormatter(), cancellationToken);
    }

    public static async Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value)
    {
        return await client.PostAsync(requestUri, value, new NoCharSetJsonMediaTypeFormatter());
    }
}
Yishai Galatzer
  • 8,791
  • 2
  • 32
  • 41
oronbz
  • 2,219
  • 2
  • 12
  • 17
  • more better solution: https://stackoverflow.com/questions/40273638/how-do-i-not-exclude-charset-in-content-type-when-using-httpclient – Moe Howard May 10 '18 at 21:33

4 Answers4

6

You can derive from JsonMediaTypeFormatter and override SetDefaultContentHeaders.

Call base.SetDefaultContentHeaders() and then clear headers.ContentType.CharSet

then write your own extension method based on the following code:

public static Task<HttpResponseMessage> PostAsJsonAsync<T>(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken)
{
    return client.PostAsync(requestUri, value, 
            new JsonMediaTypeFormatter(), cancellationToken);
}

In essence something like:

public static Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value, CancellatioNToken cancellationToken)
{
    return client.PostAsync(requestUri, value, 
          new NoCharSetJsonMediaTypeFormatter(), cancellationToken);
}
Jens Meinecke
  • 2,904
  • 17
  • 20
Yishai Galatzer
  • 8,791
  • 2
  • 32
  • 41
  • It works! I can't believe such awkward work has to be done for that but it actually works! Thank you very much – oronbz Apr 19 '14 at 10:08
  • @oronbz Servers should accept the charset. What you are having to do should never be an issue. Having said that, it is much easier to deal directly with HTTP when you just use derived HttpContent classes instead of depending on formatters. – Darrel Miller Apr 19 '14 at 13:06
  • 1
    @DarrelMiller Thanks for your comment. **"should"** is a meaningless word when you are the client and not the server. I've also tackled an API where they was expecting **"Accept"** header in a **POST** request which totally makes no sense and HttpClient doesn't allow inserting Accept headers to POST so I've had to use the **TryAddHeaderWithoutValidation()** to solve it. Could you please provide an example of your recommendation? – oronbz Apr 19 '14 at 15:31
  • @oronbz The "should" comment was a justification as to why there is not an easy path to solve your problem. I was not dismissing the fact that it is a real problem that needs to be dealt with. See my other answer as an suggestion to how you can deal with HTTP payloads and have a finer degree of control over them. – Darrel Miller Apr 19 '14 at 17:29
5

For more direct control over the payload you send, you can create derived HttpContent classes instead of letting your object be passed to an ObjectContent class which then delegates streaming to a Formatter class.

A JsonContent class that supports both reading and writing looks like this,

public class JsonContent : HttpContent
{
    private readonly Stream _inboundStream;
    private readonly JToken _value;

    public JsonContent(JToken value)
    {
        _value = value;
        Headers.ContentType = new MediaTypeHeaderValue("application/json");
    }

    public JsonContent(Stream inboundStream)
    {
        _inboundStream = inboundStream;
    }

    public async Task<JToken> ReadAsJTokenAsync()
    {
        return _value ?? JToken.Parse(await ReadAsStringAsync());
    }

    protected async override Task<Stream> CreateContentReadStreamAsync()
    {
        return _inboundStream;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        if (_value != null)
        {
            var jw = new JsonTextWriter(new StreamWriter(stream)) {Formatting = Formatting.Indented};
            _value.WriteTo(jw);
            jw.Flush();
        } else if (_inboundStream != null)
        {
            return _inboundStream.CopyToAsync(stream);
        }
        return Task.FromResult<object>(null);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1;
        return false;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _inboundStream.Dispose();   
        }
        base.Dispose(disposing);
    }
}

Once you have this class you can then do,

var content = new JsonContent(myObject);
_httpClient.PostAsync(uri,content);

If you need to change any of the content Headers you can do that manually before sending the request. And if you need to mess with any of the request headers then you use the SendAsync overload,

var content = new JsonContent(myObject);
// Update Content headers here
var request = new HttpRequestMessage {RequestUri = uri, Content = content };
// Update request headers here
_httpClient.SendAsync(request);

Derived content classes are easy to create for pretty much any media type or any source of data. I've created all kinds of classes derived from HttpContent. e.g. FileContent, EmbeddedResourceContent, CSVContent, XmlContent, ImageContent, HalContent, CollectionJsonContent, HomeContent, ProblemContent.

Personally, I've found it gives me much better control over my payloads.

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
1

The easiest approach that is working for me is passing new MediaTypeHeaderValue as parameter:

using var client = new HttpClient();

var data = JsonContent.Create(new
{
    data = "local"
}, new MediaTypeHeaderValue("application/json"));

var response = await client.PutAsync("api/test", data);
dieselcz
  • 433
  • 3
  • 9
-1

I like Darrel's answer more than the accepted one, but it was still too complex for me. I used this:

public class ContentTypeSpecificStringContent : StringContent
{
    /// <summary>
    /// Ensure content type is reset after base class mucks it up.
    /// </summary>
    /// <param name="content">Content to send</param>
    /// <param name="encoding">Encoding to use</param>
    /// <param name="contentType">Content type to use</param>
    public ContentTypeSpecificStringContent(string content, Encoding encoding, string contentType)
        : base(content, encoding, contentType)
    {
        Headers.ContentType = new MediaTypeHeaderValue(contentType);
    }
}

Needless to say you could adapt it for whichever base class suits your needs. Hope that helps somebody.