使用HttpClient将字节数组发布到Web API服务器

50
我想将这些数据发布到Web API服务器:
public sealed class SomePostRequest
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

使用此代码用于服务器:

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

并且这个是为客户而准备的:

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

一切都正常运作(至少,在 PostIncomingData 中断点调试器停止了)。

由于有一个 byte 数组,我不想将其序列化为 JSON,并希望将其作为二进制数据发布以减少网络流量(类似于 application/octet-stream)。

如何实现这一点?

我尝试过使用 MultipartFormDataContent,但好像我无法理解它如何匹配控制器方法的签名。

例如,将内容替换为此内容:

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

导致错误415(“不支持的媒体类型”)。

5个回答

38

WebAPI v2.1及以上版本原生支持BSON(二进制JSON),甚至还包含了一个MediaTypeFormatter以支持它。这意味着您可以以二进制格式发送整个消息。

如果您想使用它,需要在WebApiConfig中进行设置:

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

现在,您可以在客户端使用相同的BsonMediaTypeFormatter来序列化请求:
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();
}

或者,您可以使用Json.NET将类序列化为BSON格式。然后,指定您想要使用“application/bson”作为您的“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();
    }
}

谢谢,它完美地工作了。实际上,您提供的链接包含更简单的客户端示例。 - Dennis
@Dennis 它使用 BsonMediaTypeFormatter 进行序列化。我猜你可以两种方式都有。我会在我的答案中添加另一个示例。 - Yuval Itzchakov
1
你的第二个代码示例缺少内容类型:var byteArrayContent = new ByteArrayContent(stream.ToArray()); byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/bson"); var result = await client.PostAsync( "api/SomeData/Incoming", byteArrayContent); - Minh Nguyen
你在控制器中是如何使用它的?使用你的第二个示例,我在服务器上得到了“UnsupportedMediaType”异常 - .Net5 web api。 - PandaWood

17

我将 字节数组 转换为 Base64 字符串 进行提交:

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

接收者可以通过以下方式将Base64字符串转换回字节数组

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

1
简单而有效 - Simon
10
我会说这很简单但不够有效,因为base64字符串会大约增加30%的大小。尽管如此,这是一个可行的快速解决方案。 - Ray Cheng
1
@RayCheng 我会说这很简单有效,但不够高效。 - Eike
它还假定您对API具有控制权,但您可能没有。 - John Lord
接收方法应该长什么样?postBody变量从哪里获取? - Zi Cold
显示剩余2条评论

7

我已经创建了这个通用的跨平台方法,使用 Json.NET 库来支持 BSON 格式,以便我们以后可以更轻松地重用它。在 Xamarin 平台上也可以正常运行。

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

帮助将数据序列化为BSON格式的方法如下:
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();
    }
}

然后您可以像这样使用Post方法:
var response = await PostBsonAsync<SamplePostRequest>("api/SomeData/Incoming", requestData);

PostBson 的返回类型必须是 Task、Task<T> 或 void。 - Juan Zamora
2
@JuanZamora 你是对的,我刚刚用了await关键字更新了代码。 - Minh Nguyen
1
这很好,但是 HttpClient 应该在应用程序中只被实例化一次 - 在高负载下,这段代码可能会耗尽可用的套接字数量 - 请参阅 Microsoft 文档 - 适用于任何版本的 .NET - https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0 - PandaWood

2

提醒一下,对于将 Protobuf 序列化到请求正文的帖子

        LoginRequest loginRequest = new LoginRequest()
        {
            Code = "UserId",
            Password = "myPass",
            CMToken = "eIFt4lYTKGU:APA91bFZPe3XCDL2r1JUJuEQLlN3FoeFw9ULpw8ljEavNdo9Lc_-Qua4w9pTqdOFLTb92Kf03vyWBqkcvbBfYEno4NQIvp21kN9sldDt40eUOdy0NgMRXf2Asjp6FhOD1Kmubx1Hq7pc",
        };
        byte[] rawBytes = ProtoBufSerializer.ProtoSerialize<LoginRequest>(loginRequest);

        var client = new HttpClient();
        client.BaseAddress = new Uri("http://localhost:9000/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/x-protobuf"));

        //var bSonData = HttpExtensions.SerializeBson<T>(data);
        var byteArrayContent = new ByteArrayContent(rawBytes);
        byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-protobuf");

        var result = client.PostAsync("Api/Login", byteArrayContent).Result;

        Console.WriteLine(result.IsSuccessStatusCode);

如何消费? - user1034912

0

我想像之前使用WebClient一样以真正的二进制方式发送它,而不是将其变成多部分。

受到这个问题的启发,我用以下方法使其工作:

HttpClient InternalHttpClient = new HttpClient();
HttpContent BinaryContent = new ByteArrayContent(new byte[] { 1, 2, 3 });
byte[] ReceivedData = new byte[0];

using (HttpResponseMessage ResponseMessage = InternalHttpClient.PostAsync("apiurl/binarycomms.aspx", BinaryContent).Result)
{
    using (HttpContent ResponseBytes = ResponseMessage.Content)
    {
        ReceivedData = ResponseBytes.ReadAsByteArrayAsync().Result;
    }
}

在服务器端,代码也完全是二进制的:

protected void Page_Load(object sender, EventArgs e)
{
    Page.Response.ContentType = "application/octet-stream";

    byte[] Challenge = Page.Request.BinaryRead(Request.TotalBytes);

    Page.Response.BinaryWrite(new byte[] { 10, 20, 30 });
}

您可以轻松地添加压缩功能以使带宽使用量更小。

非常乐意听取评论,如果我漏掉了什么或者这与主题无关,请告诉我,但是对我来说它的效果非常好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接