Frage

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());
    }
}
War es hilfreich?

Lösung

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);
}

Andere Tipps

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.

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);

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top