Skip to content

Commit df4ffb8

Browse files
committed
增加UrlEncodedContent类型,提高HttpApiRequestMessage.AddFormFieldAsync性能
1 parent 62d6de1 commit df4ffb8

File tree

3 files changed

+137
-59
lines changed

3 files changed

+137
-59
lines changed

WebApiClient/HttpApiRequestMessage.cs

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -145,69 +145,23 @@ public async Task AddFormFieldAsync(string name, string value)
145145
public async Task AddFormFieldAsync(IEnumerable<KeyValuePair<string, string>> keyValues)
146146
{
147147
this.EnsureNotGetOrHead();
148+
this.EnsureMediaTypeEqual(UrlEncodedContent.MediaType);
148149

149150
if (keyValues == null)
150151
{
151152
return;
152153
}
153154

154-
var formBody = default(byte[]);
155-
const string mediaType = "application/x-www-form-urlencoded";
156-
157-
if (this.Content != null)
155+
var formContent = this.Content as UrlEncodedContent;
156+
if (formContent == null)
158157
{
159-
this.EnsureMediaTypeEqual(mediaType);
160-
formBody = await this.Content.ReadAsByteArrayAsync();
161-
this.Content.Dispose();
158+
formContent = new UrlEncodedContent(this.Content);
162159
}
163160

164-
var bytesContent = MergeFields(formBody, keyValues);
165-
var byteArrayContent = new ByteArrayContent(bytesContent);
166-
byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
167-
this.Content = byteArrayContent;
161+
await formContent.AddFormFieldAsync(keyValues);
162+
this.Content = formContent;
168163
}
169164

170-
/// <summary>
171-
/// 合并内容
172-
/// </summary>
173-
/// <param name="formBody"></param>
174-
/// <param name="keyValues"></param>
175-
/// <returns></returns>
176-
private static byte[] MergeFields(byte[] formBody, IEnumerable<KeyValuePair<string, string>> keyValues)
177-
{
178-
var encoding = Encoding.UTF8;
179-
var kvs = from kv in keyValues select string.Format("{0}={1}", kv.Key, HttpUtility.UrlEncode(kv.Value, encoding));
180-
var stringContent = string.Join("&", kvs);
181-
182-
if (formBody != null && formBody.Length > 0)
183-
{
184-
stringContent = "&" + stringContent;
185-
}
186-
187-
var byteConent = encoding.GetBytes(stringContent);
188-
return MergeBytes(formBody, byteConent);
189-
}
190-
191-
/// <summary>
192-
/// 合并字节组
193-
/// </summary>
194-
/// <param name="formBody"></param>
195-
/// <param name="byteConent"></param>
196-
/// <returns></returns>
197-
private static byte[] MergeBytes(byte[] formBody, byte[] byteConent)
198-
{
199-
if (formBody == null || formBody.Length == 0 || byteConent == null)
200-
{
201-
return byteConent;
202-
}
203-
204-
var bytes = new byte[formBody.Length + byteConent.Length];
205-
formBody.CopyTo(bytes, 0);
206-
byteConent.CopyTo(bytes, formBody.Length);
207-
return bytes;
208-
}
209-
210-
211165
/// <summary>
212166
/// 添加文件内容到已有的Content
213167
/// 要求content-type为multipart/form-data
@@ -353,10 +307,7 @@ private bool SetCookie(string cookieValues, bool useUrlEncode)
353307
/// <returns></returns>
354308
private MultipartContent CastOrCreateMultipartContent()
355309
{
356-
if (this.Content != null)
357-
{
358-
this.EnsureMediaTypeEqual("multipart/form-data");
359-
}
310+
this.EnsureMediaTypeEqual("multipart/form-data");
360311

361312
var httpContent = this.Content as MultipartContent;
362313
if (httpContent == null)
@@ -378,13 +329,12 @@ private MultipartContent CastOrCreateMultipartContent()
378329
/// <exception cref="NotSupportedException"></exception>
379330
private void EnsureMediaTypeEqual(string newMediaType)
380331
{
381-
var contentType = this.Content.Headers.ContentType;
382-
if (contentType == null)
332+
var existsMediaType = this.Content?.Headers.ContentType?.MediaType;
333+
if (string.IsNullOrEmpty(existsMediaType) == true)
383334
{
384335
return;
385336
}
386337

387-
var existsMediaType = contentType.MediaType;
388338
if (string.Equals(existsMediaType, newMediaType, StringComparison.OrdinalIgnoreCase) == false)
389339
{
390340
var message = string.Format("Content-Type必须保持为{0}", existsMediaType);
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace WebApiClient
11+
{
12+
/// <summary>
13+
/// 表示键值对表单内容
14+
/// </summary>
15+
class UrlEncodedContent : HttpContent
16+
{
17+
/// <summary>
18+
/// 获取对应的ContentType
19+
/// </summary>
20+
public static string MediaType => "application/x-www-form-urlencoded";
21+
22+
/// <summary>
23+
/// 用于保存表单内容
24+
/// </summary>
25+
private readonly MemoryStream stream = new MemoryStream();
26+
27+
/// <summary>
28+
/// 键值对表单内容
29+
/// </summary>
30+
/// <param name="content">原始表单</param>
31+
/// <param name="disposeContent">是否要释放原始表单</param>
32+
public UrlEncodedContent(HttpContent content, bool disposeContent = true)
33+
{
34+
if (content != null)
35+
{
36+
content.CopyToAsync(this.stream);
37+
if (disposeContent == true)
38+
{
39+
content.Dispose();
40+
}
41+
}
42+
this.Headers.ContentType = new MediaTypeHeaderValue(MediaType);
43+
}
44+
45+
/// <summary>
46+
/// 添加字段到内存流
47+
/// </summary>
48+
/// <param name="keyValues">键值对</param>
49+
/// <returns></returns>
50+
public async Task AddFormFieldAsync(IEnumerable<KeyValuePair<string, string>> keyValues)
51+
{
52+
if (keyValues == null)
53+
{
54+
return;
55+
}
56+
57+
var bytes = this.EncodedKeyValues(keyValues);
58+
await this.stream.WriteAsync(bytes, 0, bytes.Length);
59+
}
60+
61+
/// <summary>
62+
/// 计算键值对的字节组
63+
/// </summary>
64+
/// <param name="keyValues">键值对</param>
65+
/// <returns></returns>
66+
private byte[] EncodedKeyValues(IEnumerable<KeyValuePair<string, string>> keyValues)
67+
{
68+
var encoding = Encoding.UTF8;
69+
var parameters =
70+
from kv in keyValues
71+
let value = HttpUtility.UrlEncode(kv.Value, encoding)
72+
select $"{kv.Key }={value}";
73+
74+
var parameterString = string.Join("&", parameters);
75+
if (this.stream.Length > 0)
76+
{
77+
parameterString = "&" + parameterString;
78+
}
79+
return encoding.GetBytes(parameterString);
80+
}
81+
82+
/// <summary>
83+
/// 计算内容长度
84+
/// </summary>
85+
/// <param name="length"></param>
86+
/// <returns></returns>
87+
protected override bool TryComputeLength(out long length)
88+
{
89+
length = this.stream.Length;
90+
return true;
91+
}
92+
93+
/// <summary>
94+
/// 创建只读流
95+
/// </summary>
96+
/// <returns></returns>
97+
protected override Task<Stream> CreateContentReadStreamAsync()
98+
{
99+
var buffer = this.stream.ToArray();
100+
var readStream = new MemoryStream(buffer, 0, buffer.Length, writable: false);
101+
return Task.FromResult<Stream>(readStream);
102+
}
103+
104+
/// <summary>
105+
/// 序列化到目标流中
106+
/// </summary>
107+
/// <param name="stream">目标流</param>
108+
/// <param name="context">上下文</param>
109+
/// <returns></returns>
110+
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
111+
{
112+
var position = this.stream.Position;
113+
this.stream.Position = 0;
114+
await this.stream.CopyToAsync(stream);
115+
this.stream.Position = position;
116+
}
117+
118+
/// <summary>
119+
/// 释放资源
120+
/// </summary>
121+
/// <param name="disposing"></param>
122+
protected override void Dispose(bool disposing)
123+
{
124+
this.stream.Dispose();
125+
base.Dispose(disposing);
126+
}
127+
}
128+
}

WebApiClient/WebApiClient.csproj

216 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)