Post byte array to Web API server using HttpClient

88,920

Solution 1

WebAPI v2.1 and beyond supports BSON (Binary JSON) out of the box, and even has a MediaTypeFormatter included for it. This means you can post your entire message in binary format.

If you want to use it, you'll need to set it in WebApiConfig:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Formatters.Add(new BsonMediaTypeFormatter());
    }
}

Now, you an use the same BsonMediaTypeFormatter at the client side to serialize your request:

public async Task SendRequestAsync()
{
    var client = new HttpClient
    {
        BaseAddress = new Uri("http://www.yourserviceaddress.com");
    };

    // Set the Accept header for BSON.
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/bson"));

    var request = new SomePostRequest
    {
        Id = 20,
        Content = new byte[] { 2, 5, 7, 10 }
    };

    // POST using the BSON formatter.
    MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
    var result = await client.PostAsync("api/SomeData/Incoming", request, bsonFormatter);

    result.EnsureSuccessStatusCode();
}

Or, you can use Json.NET to serialize your class to BSON. Then, specify you want to use "application/bson" as your "Content-Type":

public async Task SendRequestAsync()
{   
    using (var stream = new MemoryStream())
    using (var bson = new BsonWriter(stream))
    {
        var jsonSerializer = new JsonSerializer();

        var request = new SomePostRequest
        {
            Id = 20,
            Content = new byte[] { 2, 5, 7, 10 }
        };

        jsonSerializer.Serialize(bson, request);

        var client = new HttpClient
        {
            BaseAddress = new Uri("http://www.yourservicelocation.com")
        };

        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/bson"));

        var byteArrayContent = new ByteArrayContent(stream.ToArray());
        byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson");

        var result = await client.PostAsync(
                "api/SomeData/Incoming", byteArrayContent);

        result.EnsureSuccessStatusCode();
    }
}

Solution 2

I convert Byte Array into Base64 String to post:

await client.PostAsJsonAsync( apiUrl,  
    new  {
        message = "",
        content = Convert.ToBase64String(yourByteArray),
    }
);

and receiver can convert the Base64 String back to Byte Array by:

string base64Str = (string)postBody.content;
byte[] fileBytes = Convert.FromBase64String(base64Str);

Solution 3

I have created this generic and cross platform method to support the BSON format using the Json.NET library so we can reuse it easier later. It works fine in Xamarin platform as well.

public static async HttpResponseMessage PostBsonAsync<T>(string url, T data)
{
    using (var client = new HttpClient())
    {
        //Specifiy 'Accept' header As BSON: to ask server to return data as BSON format
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/bson"));

        //Specify 'Content-Type' header: to tell server which format of the data will be posted
        //Post data will be as Bson format                
        var bSonData = HttpExtensions.SerializeBson<T>(data);
        var byteArrayContent = new ByteArrayContent(bSonData);
        byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson");

        var response = await client.PostAsync(url, byteArrayContent);

        response.EnsureSuccessStatusCode();

        return response;
    }
}

The method to help to serialise data to BSON format:

public static byte[] SerializeBson<T>(T obj)
{
    using (MemoryStream ms = new MemoryStream())
    {
        using (BsonWriter writer = new BsonWriter(ms))
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(writer, obj);
        }

        return ms.ToArray();
    }
}

Then you can use the Post method like this:

var response = await PostBsonAsync<SamplePostRequest>("api/SomeData/Incoming", requestData);
Share:
88,920
Dennis
Author by

Dennis

C# developer. Nowadays I mostly design/develop enterprise-level services and REST/SOAP APIs. From time to time still use XAML + MVVM.

Updated on October 30, 2020

Comments

  • Dennis
    Dennis over 3 years

    I want to post this data to Web API server:

    public sealed class SomePostRequest
    {
        public int Id { get; set; }
        public byte[] Content { get; set; }
    }
    

    Using this code for server:

    [Route("Incoming")]
    [ValidateModel]
    public async Task<IHttpActionResult> PostIncomingData(SomePostRequest requestData)
    {
        // POST logic here
    }
    

    and this - for client:

    var client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:25001/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));
    
    var content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        { "id", "1" },
        { "content", "123" }
    });
    
    var result = await client.PostAsync("api/SomeData/Incoming", content);
    result.EnsureSuccessStatusCode();
    

    everything works fine (at least, debugger stops at breakpoint in PostIncomingData).

    Since there is a byte array, I don't want to serialize it as JSON, and want to post it as binary data to decrease network traffic (something like application/octet-stream).

    How this can be achieved?

    I've tried to play with MultipartFormDataContent, but looks like I just can't understand, how MultipartFormDataContent will match signature of controller's method.

    E.g., replacing content to this:

    var content = new MultipartFormDataContent();
    content.Add(new FormUrlEncodedContent(new Dictionary<string, string> { { "id", "1" } }));
    
    var binaryContent = new ByteArrayContent(new byte[] { 1, 2, 3 });
    binaryContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    content.Add(binaryContent, "content");
    
    var result = await client.PostAsync("api/SomeData/Incoming", content);
    result.EnsureSuccessStatusCode();
    

    leads to error 415 ("Unsupported media type").

  • Dennis
    Dennis over 8 years
    Thanks, it works perfectly. Actually, the link you've provided contains more simple example for the client side.
  • Yuval Itzchakov
    Yuval Itzchakov over 8 years
    @Dennis It uses the BsonMediaTypeFormatter to serialize. I guess you can have it either way. I'll add another example to my answer.
  • Minh Nguyen
    Minh Nguyen about 8 years
    Your second code sample missing content type: var byteArrayContent = new ByteArrayContent(stream.ToArray()); byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson"); var result = await client.PostAsync( "api/SomeData/Incoming", byteArrayContent);
  • Juan Zamora
    Juan Zamora over 6 years
    return type for PostBson must be Task, Task<T> or void
  • Minh Nguyen
    Minh Nguyen over 6 years
    @JuanZamora you are right I just updated the code with await keyword
  • Simon
    Simon over 4 years
    Simple and effective
  • Ray Cheng
    Ray Cheng over 4 years
    I would say it's simple but not effective since the base64 string will be about 30% larger. Nevertheless, a working quick solution.
  • user1034912
    user1034912 over 2 years
    How to consume??
  • Eike
    Eike over 2 years
    @RayCheng I'd say it's simple and effective but not efficient.
  • John Lord
    John Lord over 2 years
    it also assumes you have control over the api, which you probably don't
  • Zi Cold
    Zi Cold over 2 years
    How should reciever method look like? Where postBody variable is taken from?
  • yu yang Jian
    yu yang Jian over 2 years
  • Igor Mironenko
    Igor Mironenko about 2 years
    This is good but HttpClient is intended to be instantiated once per application - this code will likely exhaust the number of sockets available under heavy loads - see Microsoft docs - any version of .NET - docs.microsoft.com/en-us/dotnet/api/…
  • Igor Mironenko
    Igor Mironenko about 2 years
    How are you consuming this in the controller...? using your 2nd example there, I get UnsupportedMediaType exception on the server - .Net5 web api