如何在 ASP.NET 核心控制器中接收 `multipart/mixed`
How to receive `multipart/mixed` in an ASP.NET Core controller
旧版系统会发给我:
POST /xml HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 321
Content-Type: multipart/mixed; boundary=------------------------a9dd0ab37a224967
--------------------------a9dd0ab37a224967
Content-Disposition: attachment; name="part1"
Content-Type: text/xml
<foo>bar</foo>
--------------------------a9dd0ab37a224967
Content-Disposition: attachment; name="part2"
Content-Type: application/json
{'foo': 'bar'}
--------------------------a9dd0ab37a224967--
我需要解释为原始的第一部分XElement
;对于第二部分,我想要通常的模型绑定。
我试试这个:
class Part2 {
public string foo { get; set; }
}
[HttpPost]
[Route("/xml")]
public string Post1([FromBody] XElement part1, [FromBody] Part2 part2 )
{
return part1.ToString() + ", " + part2.foo;
}
但是ASP.NET不允许超过一个参数用[FromBody]
修饰。
如何让我的 ASP.NET 核心服务接收内容类型为 multipart/mixed
的 http 请求?
注意 [FromBody]
在这里不起作用 因为它专门设计用于接受简单的表单数据(例如 "multipart/form-data"
类型)适合通过POST
请求方法处理。
您可能会寻找类似 Batch Processing in OData 的内容。考虑到这一点,通过 RFC 2045/2046 发送数据并不是 Web 应用程序通信的典型方式。实际上,这是为电子邮件通信而设计的。这就是为什么您看到 Asp.net 实施信息不佳的原因。
在 之后,另一个答案可能是将原始文本(xml 格式)发送到控制器,然后尝试解析并执行其他操作。尽量保持客户端-服务器通信简单。在这里,没有理由使用 MultipartMixed 方法使它变得复杂,而现代和大型项目使用典型的方法。
没有内置机制来处理这种类型的 post 数据(multipart/mixed
几乎有无限的可能性,并且很难在一般意义上绑定到它),但是,您可以使用 MultipartReader
对象自己轻松解析数据。
我将假设所有传入的数据都具有 attachment
的配置,并且只有 JSON 和 XML 内容类型是有效的。但这应该是开放式的,您可以根据需要进行修改。
看看这个静态助手:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Text;
namespace YourNamespace.Utilities
{
public static class MutipartMixedHelper
{
public static async IAsyncEnumerable<ParsedSection> ParseMultipartMixedRequestAsync(HttpRequest request)
{
// Extract, sanitize and validate boundry
var boundary = HeaderUtilities.RemoveQuotes(
MediaTypeHeaderValue.Parse(request.ContentType).Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary) ||
(boundary.Length > new FormOptions().MultipartBoundaryLengthLimit))
{
throw new InvalidDataException("boundry is shot");
}
// Create a new reader based on that boundry
var reader = new MultipartReader(boundary, request.Body);
// Start reading sections from the MultipartReader until there are no more
MultipartSection section;
while ((section = await reader.ReadNextSectionAsync()) != null)
{
// parse the content type
var contentType = new ContentType(section.ContentType);
// create a new ParsedSecion and start filling in the details
var parsedSection = new ParsedSection
{
IsJson = contentType.MediaType.Equals("application/json",
StringComparison.OrdinalIgnoreCase),
IsXml = contentType.MediaType.Equals("text/xml",
StringComparison.OrdinalIgnoreCase),
Encoding = Encoding.GetEncoding(contentType.CharSet)
};
// Must be XML or JSON
if (!parsedSection.IsXml && !parsedSection.IsJson)
{
throw new InvalidDataException("only handling json/xml");
}
// Parse the content disosition
if (ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition) &&
contentDisposition.DispositionType.Equals("attachment"))
{
// save the name
parsedSection.Name = contentDisposition.Name.Value;
// Create a new StreamReader using the proper encoding and
// leave the underlying stream open
using (var streamReader = new StreamReader(
section.Body, parsedSection.Encoding, leaveOpen: true))
{
parsedSection.Data = await streamReader.ReadToEndAsync();
yield return parsedSection;
}
}
}
}
}
public sealed class ParsedSection
{
public bool IsJson { get; set; }
public bool IsXml { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public Encoding Encoding { get; set; }
}
}
您可以从端点调用此方法,如下所示:
[HttpPost, Route("TestMultipartMixedPost")]
public async Task<IActionResult> TestMe()
{
await foreach (var parsedSection in MutipartMixedHelper
.ParseMultipartMixedRequestAsync(Request))
{
Debug.WriteLine($"Name: {parsedSection.Name}");
Debug.WriteLine($"Encoding: {parsedSection.Encoding.EncodingName}");
Debug.WriteLine($"IsJson: {parsedSection.IsJson}");
Debug.WriteLine($"IsXml: {parsedSection.IsXml}");
Debug.WriteLine($"Data: {parsedSection.Data}");
Debug.WriteLine("-----------------------");
}
return Ok();
}
您的端点将输出:
Name: part1
Encoding: Unicode (UTF-8)
IsJson: False
IsXml: True
Data: <foo>bar</foo>
-----------------------
Name: part2
Encoding: Unicode (UTF-8)
IsJson: True
IsXml: False
Data: {"foo": "bar"}
-----------------------
这时候就需要根据返回对象的属性进行反序列化了。
旧版系统会发给我:
POST /xml HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 321
Content-Type: multipart/mixed; boundary=------------------------a9dd0ab37a224967
--------------------------a9dd0ab37a224967
Content-Disposition: attachment; name="part1"
Content-Type: text/xml
<foo>bar</foo>
--------------------------a9dd0ab37a224967
Content-Disposition: attachment; name="part2"
Content-Type: application/json
{'foo': 'bar'}
--------------------------a9dd0ab37a224967--
我需要解释为原始的第一部分XElement
;对于第二部分,我想要通常的模型绑定。
我试试这个:
class Part2 {
public string foo { get; set; }
}
[HttpPost]
[Route("/xml")]
public string Post1([FromBody] XElement part1, [FromBody] Part2 part2 )
{
return part1.ToString() + ", " + part2.foo;
}
但是ASP.NET不允许超过一个参数用[FromBody]
修饰。
如何让我的 ASP.NET 核心服务接收内容类型为 multipart/mixed
的 http 请求?
注意 [FromBody]
在这里不起作用 因为它专门设计用于接受简单的表单数据(例如 "multipart/form-data"
类型)适合通过POST
请求方法处理。
您可能会寻找类似 Batch Processing in OData 的内容。考虑到这一点,通过 RFC 2045/2046 发送数据并不是 Web 应用程序通信的典型方式。实际上,这是为电子邮件通信而设计的。这就是为什么您看到 Asp.net 实施信息不佳的原因。
在
没有内置机制来处理这种类型的 post 数据(multipart/mixed
几乎有无限的可能性,并且很难在一般意义上绑定到它),但是,您可以使用 MultipartReader
对象自己轻松解析数据。
我将假设所有传入的数据都具有 attachment
的配置,并且只有 JSON 和 XML 内容类型是有效的。但这应该是开放式的,您可以根据需要进行修改。
看看这个静态助手:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Text;
namespace YourNamespace.Utilities
{
public static class MutipartMixedHelper
{
public static async IAsyncEnumerable<ParsedSection> ParseMultipartMixedRequestAsync(HttpRequest request)
{
// Extract, sanitize and validate boundry
var boundary = HeaderUtilities.RemoveQuotes(
MediaTypeHeaderValue.Parse(request.ContentType).Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary) ||
(boundary.Length > new FormOptions().MultipartBoundaryLengthLimit))
{
throw new InvalidDataException("boundry is shot");
}
// Create a new reader based on that boundry
var reader = new MultipartReader(boundary, request.Body);
// Start reading sections from the MultipartReader until there are no more
MultipartSection section;
while ((section = await reader.ReadNextSectionAsync()) != null)
{
// parse the content type
var contentType = new ContentType(section.ContentType);
// create a new ParsedSecion and start filling in the details
var parsedSection = new ParsedSection
{
IsJson = contentType.MediaType.Equals("application/json",
StringComparison.OrdinalIgnoreCase),
IsXml = contentType.MediaType.Equals("text/xml",
StringComparison.OrdinalIgnoreCase),
Encoding = Encoding.GetEncoding(contentType.CharSet)
};
// Must be XML or JSON
if (!parsedSection.IsXml && !parsedSection.IsJson)
{
throw new InvalidDataException("only handling json/xml");
}
// Parse the content disosition
if (ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition) &&
contentDisposition.DispositionType.Equals("attachment"))
{
// save the name
parsedSection.Name = contentDisposition.Name.Value;
// Create a new StreamReader using the proper encoding and
// leave the underlying stream open
using (var streamReader = new StreamReader(
section.Body, parsedSection.Encoding, leaveOpen: true))
{
parsedSection.Data = await streamReader.ReadToEndAsync();
yield return parsedSection;
}
}
}
}
}
public sealed class ParsedSection
{
public bool IsJson { get; set; }
public bool IsXml { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public Encoding Encoding { get; set; }
}
}
您可以从端点调用此方法,如下所示:
[HttpPost, Route("TestMultipartMixedPost")]
public async Task<IActionResult> TestMe()
{
await foreach (var parsedSection in MutipartMixedHelper
.ParseMultipartMixedRequestAsync(Request))
{
Debug.WriteLine($"Name: {parsedSection.Name}");
Debug.WriteLine($"Encoding: {parsedSection.Encoding.EncodingName}");
Debug.WriteLine($"IsJson: {parsedSection.IsJson}");
Debug.WriteLine($"IsXml: {parsedSection.IsXml}");
Debug.WriteLine($"Data: {parsedSection.Data}");
Debug.WriteLine("-----------------------");
}
return Ok();
}
您的端点将输出:
Name: part1
Encoding: Unicode (UTF-8)
IsJson: False
IsXml: True
Data: <foo>bar</foo>
-----------------------
Name: part2
Encoding: Unicode (UTF-8)
IsJson: True
IsXml: False
Data: {"foo": "bar"}
-----------------------
这时候就需要根据返回对象的属性进行反序列化了。