ASP.NET Core Web API 自定义格式化器深度解析
2025-07-06 04:02:52作者:幸俭卉
什么是自定义格式化器
在 ASP.NET Core Web API 中,格式化器负责处理请求和响应数据的序列化与反序列化。系统内置了 JSON 和 XML 格式化器,但当我们需要支持其他数据格式时,就需要创建自定义格式化器。
何时需要使用自定义格式化器
以下场景适合使用自定义格式化器:
- 需要支持系统未内置的数据格式(如 vCard、Protobuf 等)
- 需要对现有格式进行特殊处理
- 需要实现特定业务场景下的数据转换
创建自定义格式化器的步骤
1. 选择基类
根据处理的数据类型选择合适的基类:
- 文本数据:继承
TextInputFormatter
或TextOutputFormatter
- 二进制数据:继承
InputFormatter
或OutputFormatter
2. 配置支持的媒体类型和编码
在构造函数中指定支持的媒体类型和编码:
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
3. 实现核心方法
对于输出格式化器:
CanWriteType
:确定格式化器能否处理特定类型WriteResponseBodyAsync
:实现实际的序列化逻辑
对于输入格式化器:
CanReadType
:确定格式化器能否处理特定类型ReadRequestBodyAsync
:实现实际的反序列化逻辑
实战示例:vCard 格式化器
输出格式化器实现
public class VcardOutputFormatter : TextOutputFormatter
{
// 构造函数配置支持的媒体类型和编码
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
// 确定能否处理特定类型
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
// 实现实际的序列化逻辑
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
var contact = context.Object as Contact;
FormatVcard(buffer, contact, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine("END:VCARD");
logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
}
}
输入格式化器实现
public class VcardInputFormatter : TextInputFormatter
{
// 构造函数配置支持的媒体类型和编码
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
// 确定能否处理特定类型
protected override bool CanReadType(Type type)
{
return type == typeof(Contact);
}
// 实现实际的反序列化逻辑
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();
using var reader = new StreamReader(httpContext.Request.Body, effectiveEncoding);
string nameLine = null;
try
{
await ReadUntilCondition(reader, (line) => line.StartsWith("FN:"));
nameLine = await reader.ReadLineAsync();
var contact = new Contact
{
FirstName = nameLine.Substring(3).Split(' ')[0],
LastName = nameLine.Substring(3).Split(' ')[1]
};
logger.LogInformation("Read contact {FirstName} {LastName}",
contact.FirstName, contact.LastName);
return await InputFormatterResult.SuccessAsync(contact);
}
catch
{
logger.LogError("Read failed");
return await InputFormatterResult.FailureAsync();
}
}
private async Task ReadUntilCondition(TextReader reader, Func<string, bool> condition)
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (condition(line)) break;
}
}
}
注册自定义格式化器
在服务配置中注册自定义格式化器:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
测试自定义格式化器
测试输出格式化器
发送 GET 请求到 /api/contacts
,并设置 Accept 头为 text/vcard
,将收到类似以下格式的响应:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
测试输入格式化器
发送 POST 请求到 /api/contacts
:
- 设置 Content-Type 头为
text/vcard
- 请求体包含 vCard 格式的数据
高级主题
处理派生类型
当处理可能返回派生类型的操作时,可以重写 CanWriteResult
方法:
public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context.Object is Student)
{
return base.CanWriteResult(context);
}
return false;
}
性能考虑
- 对于高吞吐量场景,考虑使用缓冲和池化技术
- 避免在格式化器中进行复杂的计算
- 考虑使用异步操作处理大型数据流
最佳实践
- 明确职责:格式化器应只关注数据的序列化和反序列化,不包含业务逻辑
- 错误处理:提供清晰的错误信息和日志记录
- 性能优化:对于文本格式化器,使用
StringBuilder
而不是字符串连接 - 编码支持:明确支持多种编码以增强兼容性
- 依赖注入:通过上下文获取服务,而不是构造函数注入
通过本文的详细讲解,您应该已经掌握了在 ASP.NET Core Web API 中创建和使用自定义格式化器的完整流程。自定义格式化器是扩展 Web API 功能的有力工具,合理使用可以大大增强 API 的灵活性和适用性。