C#解析Multipart/form-data C#如何在不依赖框架的情况下处理文件上传数据

来源:这里教程网 时间:2026-02-21 17:41:37 作者:

怎么手动解析
multipart/form-data
的原始字节流

不能靠

HttpRequest.Form
IFormCollection
,就得从原始
Request.Body
读字节,再按 RFC 7578 规则拆解。核心是先提取
boundary
,再用它切分出每个 part。

实操建议:

Content-Type
请求头里用正则提取
boundary=...
,注意可能带双引号,比如
boundary="----WebKitFormBoundaryabc123"
构造分隔符行:开头两个破折号 + boundary + 可选的两个破折号(结尾 part 会多两个),即
--{boundary}
--{boundary}--
Stream.ReadAsync
分块读,别一次性
ToArray()
—— 大文件直接 OOM
每个 part 的头部以
\r\n\r\n
结尾,前面是 headers(如
Content-Disposition
Content-Type
),后面是 body;需逐行解析 header,区分大小写不敏感

Content-Disposition
字段怎么安全提取文件名和字段名

这个 header 是识别字段类型的关键,但格式松散、编码混乱,容易误判。

常见错误现象:

直接
.Split(';')
然后找
filename=
,结果遇到
filename*=UTF-8''xxx.jpg
就崩了
忽略引号包裹的值,把
name="file"; filename="a b.txt"
错拆成
a
b.txt
没处理 RFC 5987 编码(
filename*=
),中文文件名变成乱码

实操建议:

用正则匹配
name="([^"]*)"
filename="([^"]*)"
,优先取带引号的完整值
若遇到
filename\*=(.+?)''(.+)
,用
WebUtility.UrlDecode
解码右侧,并按指定 charset 转字符串(默认 UTF-8)
字段名为空或只有空格?跳过,不是文件上传项

怎么区分普通字段和文件字段并分别处理

仅靠

Content-Disposition
不够,必须结合是否有
filename
、是否有
Content-Type
、body 是否二进制来综合判断。

使用场景差异:

普通字段:无
filename
,body 是纯文本(可能含
\r\n
),可直接用
Encoding.UTF8.GetString()
文件字段:有
filename
,且通常带
Content-Type
(如
image/png
),body 应按原始字节保存,**绝不能用字符串解码**
边界风险:part body 末尾自带
\r\n
,别把它当内容写进文件

性能影响:

对小字段,内存中暂存没问题;对大文件,应边读边写入磁盘或流式处理,避免缓冲区膨胀 不要反复
Seek(0)
重读 stream —— 原始
RequestBody
通常不可回溯,需自己缓存或用
MemoryStream
中转

为什么
ReadLine()
在 multipart 解析中基本不能用

因为 HTTP body 是二进制流,不是纯文本,

ReadLine()
依赖
\r\n
切行,但文件内容本身可能含任意字节,包括
\r\n
,会导致提前截断或解析错位。

容易踩的坑:

StreamReader.ReadLine()
解析 header —— 看似可行,但一旦某个 part 的
Content-Type
text/plain
且内容含
\r\n
,后续解析全乱
假设所有换行都是
\r\n
,忽略部分客户端发
\n
(虽不合规,但真实存在)
没考虑 boundary 出现在文件内容里的可能性(极低,但理论上需扫描 escape)

实操建议:

header 解析用
Span<byte>.IndexOf</byte>
\r\n\r\n
,而不是按行读
body 提取用
Stream.Position
+
Length
计算偏移,配合
boundary
字节序列做滑动窗口查找
边界检测必须用字节比较(
ReadOnlySpan<byte>.SequenceEqual</byte>
),别转 string 再比

最麻烦的其实是 boundary 和文件内容重叠的极端情况,实际中极少发生,但如果你的业务要存用户传的 zip 或 pdf,就得加校验逻辑——比如检查 boundary 是否出现在前 1KB 之后,否则拒绝上传。

相关推荐