Search code examples
c#asp.netasp.net-web-apiasp.net-web-api2dotnet-httpclient

Post byte array to Web API server using HttpClient


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").


Solution

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