ASP.NET Core媒体类型:自定义格式支持

ASP.NET Core媒体类型:自定义格式支持

【免费下载链接】aspnetcore dotnet/aspnetcore: 是一个 ASP.NET Core 应用程序开发框架的官方 GitHub 仓库,它包含了 ASP.NET Core 的核心源代码和技术文档。适合用于 ASP.NET Core 应用程序开发,特别是对于那些需要深入了解 ASP.NET Core 框架实现和技术的场景。特点是 ASP.NET Core 官方仓库、核心源代码、技术文档。 【免费下载链接】aspnetcore 项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

在现代Web开发中,处理多种数据格式是API设计的核心需求。ASP.NET Core提供了强大的媒体类型(Media Type)处理机制,允许开发者轻松实现自定义格式支持。本文将深入探讨ASP.NET Core中的媒体类型系统,并展示如何创建自定义输入和输出格式化器。

媒体类型基础概念

媒体类型(Media Type),也称为MIME类型(Multipurpose Internet Mail Extensions),是标识数据格式的标准方式。在HTTP协议中,通过Content-TypeAccept头来协商客户端和服务器之间的数据交换格式。

媒体类型结构

mermaid

常见媒体类型示例:

  • application/json - JSON格式数据
  • application/xml - XML格式数据
  • text/plain; charset=utf-8 - 纯文本,UTF-8编码
  • application/vnd.api+json - 符合JSON API规范的格式

ASP.NET Core格式化器架构

ASP.NET Core使用格式化器(Formatter)来处理请求和响应的序列化与反序列化。系统内置了多种格式化器,同时也支持自定义实现。

核心接口和基类

// 输入格式化器接口
public interface IInputFormatter
{
    bool CanRead(InputFormatterContext context);
    Task<InputFormatterResult> ReadAsync(InputFormatterContext context);
}

// 输出格式化器接口  
public interface IOutputFormatter
{
    bool CanWriteResult(OutputFormatterCanWriteContext context);
    Task WriteAsync(OutputFormatterWriteContext context);
}

// 抽象基类
public abstract class InputFormatter : IInputFormatter, IApiRequestFormatMetadataProvider
public abstract class OutputFormatter : IOutputFormatter, IApiResponseTypeMetadataProvider

格式化器选择流程

mermaid

创建自定义输入格式化器

让我们创建一个处理CSV格式的自定义输入格式化器。

CSV输入格式化器实现

using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
using System.Text;

public class CsvInputFormatter : TextInputFormatter
{
    public CsvInputFormatter()
    {
        SupportedMediaTypes.Add("text/csv");
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanReadType(Type type)
    {
        return type.IsAssignableTo(typeof(IEnumerable<object>));
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context, Encoding encoding)
    {
        var httpContext = context.HttpContext;
        using var reader = new StreamReader(httpContext.Request.Body, encoding);
        
        var content = await reader.ReadToEndAsync();
        var modelType = context.ModelType.GetGenericArguments()[0];
        
        var items = ParseCsv(content, modelType);
        return await InputFormatterResult.SuccessAsync(items);
    }

    private object ParseCsv(string csvContent, Type modelType)
    {
        var lines = csvContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
        if (lines.Length < 2) return Array.CreateInstance(modelType, 0);
        
        var headers = lines[0].Split(',').Select(h => h.Trim()).ToArray();
        var listType = typeof(List<>).MakeGenericType(modelType);
        var list = Activator.CreateInstance(listType);
        
        var addMethod = listType.GetMethod("Add");
        
        for (int i = 1; i < lines.Length; i++)
        {
            var values = lines[i].Split(',');
            var instance = Activator.CreateInstance(modelType);
            
            for (int j = 0; j < Math.Min(headers.Length, values.Length); j++)
            {
                var property = modelType.GetProperty(headers[j]);
                if (property != null && property.CanWrite)
                {
                    var value = Convert.ChangeType(values[j].Trim(), property.PropertyType, CultureInfo.InvariantCulture);
                    property.SetValue(instance, value);
                }
            }
            
            addMethod?.Invoke(list, new[] { instance });
        }
        
        return list;
    }
}

创建自定义输出格式化器

现在创建一个对应的CSV输出格式化器。

CSV输出格式化器实现

using Microsoft.AspNetCore.Mvc.Formatters;
using System.Globalization;
using System.Text;

public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add("text/csv");
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanWriteType(Type type)
    {
        return type.IsAssignableTo(typeof(IEnumerable<object>));
    }

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context, Encoding encoding)
    {
        var response = context.HttpContext.Response;
        var objects = context.Object as IEnumerable<object>;
        
        if (objects == null || !objects.Any())
        {
            await response.WriteAsync("", encoding);
            return;
        }
        
        var firstObject = objects.First();
        var properties = firstObject.GetType().GetProperties();
        var header = string.Join(",", properties.Select(p => p.Name));
        
        var csvBuilder = new StringBuilder();
        csvBuilder.AppendLine(header);
        
        foreach (var obj in objects)
        {
            var values = properties.Select(p => 
                Convert.ToString(p.GetValue(obj), CultureInfo.InvariantCulture) ?? "");
            csvBuilder.AppendLine(string.Join(",", values));
        }
        
        await response.WriteAsync(csvBuilder.ToString(), encoding);
    }
}

注册自定义格式化器

在Startup.cs或Program.cs中注册自定义格式化器:

// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    // 添加自定义输入格式化器
    options.InputFormatters.Add(new CsvInputFormatter());
    
    // 添加自定义输出格式化器
    options.OutputFormatters.Add(new CsvOutputFormatter());
    
    // 配置格式化器映射
    options.FormatterMappings.SetMediaTypeMappingForFormat(
        "csv", MediaTypeHeaderValue.Parse("text/csv"));
});

var app = builder.Build();

使用FormatFilter进行内容协商

ASP.NET Core提供了[FormatFilter]特性,允许通过URL参数指定响应格式。

配置和使用FormatFilter

// 在控制器中使用
[ApiController]
[Route("api/[controller]")]
[FormatFilter]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [Route("")]
    public IEnumerable<Product> GetProducts()
    {
        return _productService.GetAllProducts();
    }
    
    [HttpGet]
    [Route("{id}.{format?}")]
    public Product GetProduct(int id)
    {
        return _productService.GetProductById(id);
    }
}

访问示例:

  • /api/products - 默认格式(通常是JSON)
  • /api/products.csv - CSV格式
  • /api/products/1.json - 单个产品的JSON格式
  • /api/products/1.csv - 单个产品的CSV格式

高级自定义格式场景

1. 支持多种版本格式

public class VersionedCsvOutputFormatter : TextOutputFormatter
{
    public VersionedCsvOutputFormatter()
    {
        SupportedMediaTypes.Add("text/csv; version=1.0");
        SupportedMediaTypes.Add("text/csv; version=2.0");
        SupportedEncodings.Add(Encoding.UTF8);
    }

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context, Encoding encoding)
    {
        var version = context.HttpContext.Request.Headers["Accept"]
            .FirstOrDefault()?.Split(';')
            .FirstOrDefault(p => p.Trim().StartsWith("version="))
            ?.Split('=')[1]?.Trim();
        
        // 根据版本号实现不同的序列化逻辑
        if (version == "2.0")
        {
            await WriteV2Format(context, encoding);
        }
        else
        {
            await WriteV1Format(context, encoding);
        }
    }
    
    private async Task WriteV1Format(OutputFormatterWriteContext context, Encoding encoding) { /* ... */ }
    private async Task WriteV2Format(OutputFormatterWriteContext context, Encoding encoding) { /* ... */ }
}

2. 基于配置的动态格式化器

public class ConfigurableOutputFormatter : IOutputFormatter
{
    private readonly IConfiguration _configuration;

    public ConfigurableOutputFormatter(IConfiguration configuration)
    {
        _configuration = configuration;
        SupportedMediaTypes = new MediaTypeCollection
        {
            "application/vnd.configurable+json"
        };
    }

    public MediaTypeCollection SupportedMediaTypes { get; }

    public bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        return context.ObjectType != null && 
               SupportedMediaTypes.Any(mt => 
                   context.HttpContext.Request.Headers.Accept.Contains(mt));
    }

    public async Task WriteAsync(OutputFormatterWriteContext context)
    {
        var config = _configuration.GetSection("Formatting:Custom");
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = config["NamingPolicy"] == "CamelCase" 
                ? JsonNamingPolicy.CamelCase 
                : null,
            WriteIndented = config.GetValue<bool>("PrettyPrint")
        };
        
        await JsonSerializer.SerializeAsync(
            context.HttpContext.Response.Body, 
            context.Object, 
            options);
    }
}

性能优化和最佳实践

1. 格式化器缓存策略

public class CachedOutputFormatter : IOutputFormatter
{
    private readonly ConcurrentDictionary<Type, byte[]> _cache = new();
    
    public async Task WriteAsync(OutputFormatterWriteContext context)
    {
        var type = context.Object.GetType();
        if (!_cache.TryGetValue(type, out var cachedData))
        {
            // 序列化并缓存
            using var stream = new MemoryStream();
            await SerializeToStream(context.Object, stream);
            cachedData = stream.ToArray();
            _cache[type] = cachedData;
        }
        
        await context.HttpContext.Response.Body.WriteAsync(cachedData);
    }
    
    private async Task SerializeToStream(object obj, Stream stream)
    {
        // 自定义序列化逻辑
    }
}

2. 异步处理最佳实践

public class AsyncAwareInputFormatter : InputFormatter
{
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context)
    {
        try
        {
            // 使用异步流处理
            await using var stream = new MemoryStream();
            await context.HttpContext.Request.Body.CopyToAsync(stream);
            stream.Position = 0;
            
            // 异步解析
            return await ParseAsync(stream, context.ModelType);
        }
        catch (Exception ex)
        {
            context.ModelState.AddModelError(string.Empty, $"解析失败: {ex.Message}");
            return InputFormatterResult.Failure();
        }
    }
}

测试自定义格式化器

单元测试示例

public class CsvFormatterTests
{
    [Fact]
    public async Task CsvInputFormatter_CanRead_CsvContentType()
    {
        // Arrange
        var formatter = new CsvInputFormatter();
        var context = new InputFormatterContext(
            new DefaultHttpContext(),
            "test",
            new ModelStateDictionary(),
            new EmptyModelMetadataProvider().GetMetadataForType(typeof(List<Product>)),
            (stream, encoding) => new StreamReader(stream, encoding));
        
        context.HttpContext.Request.ContentType = "text/csv";
        
        // Act
        var canRead = formatter.CanRead(context);
        
        // Assert
        Assert.True(canRead);
    }
    
    [Fact]
    public async Task CsvOutputFormatter_Writes_CorrectFormat()
    {
        // Arrange
        var formatter = new CsvOutputFormatter();
        var products = new List<Product>
        {
            new Product { Id = 1, Name = "Product A", Price = 10.99m },
            new Product { Id = 2, Name = "Product B", Price = 20.50m }
        };
        
        var httpContext = new DefaultHttpContext();
        httpContext.Response.Body = new MemoryStream();
        
        var context = new OutputFormatterWriteContext(
            httpContext, 
            (stream, encoding) => new StreamWriter(stream, encoding),
            typeof(List<Product>),
            products);
        
        // Act
        await formatter.WriteAsync(context);
        httpContext.Response.Body.Position = 0;
        var result = new StreamReader(httpContext.Response.Body).ReadToEnd();
        
        // Assert
        Assert.Contains("Id,Name,Price", result);
        Assert.Contains("1,Product A,10.99", result);
    }
}

故障排除和调试

常见问题及解决方案

问题原因解决方案
格式化器不被调用媒体类型不匹配检查SupportedMediaTypes配置
序列化异常类型不支持实现CanReadType/CanWriteType方法
编码问题字符集配置错误设置SupportedEncodings属性
性能问题重复序列化实现缓存机制

调试技巧

// 添加诊断日志
public class DiagnosticOutputFormatter : IOutputFormatter
{
    private readonly ILogger<DiagnosticOutputFormatter> _logger;
    
    public DiagnosticOutputFormatter(ILogger<DiagnosticOutputFormatter> logger)
    {
        _logger = logger;
    }
    
    public async Task WriteAsync(OutputFormatterWriteContext context)
    {
        _logger.LogInformation("格式化类型: {Type}", context.ObjectType);
        _logger.LogInformation("请求内容类型: {ContentType}", 
            context.HttpContext.Request.Headers.Accept);
        
        // 实际格式化逻辑
    }
}

总结

ASP.NET Core的媒体类型系统提供了强大的扩展能力,通过自定义格式化器可以轻松支持各种数据格式。关键要点包括:

【免费下载链接】aspnetcore dotnet/aspnetcore: 是一个 ASP.NET Core 应用程序开发框架的官方 GitHub 仓库,它包含了 ASP.NET Core 的核心源代码和技术文档。适合用于 ASP.NET Core 应用程序开发,特别是对于那些需要深入了解 ASP.NET Core 框架实现和技术的场景。特点是 ASP.NET Core 官方仓库、核心源代码、技术文档。 【免费下载链接】aspnetcore 项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值