6.4自定义内容转换器

IObjectContentConverterFactory 默认的内容转换器工厂会返回 ObjectContentConverter 实例,该实例利用 HttpResponseMessage.Content 的 ReadFromJsonAsync 方法,结合 System.Text.Json 序列化库,将 HttpR

自定义对象内容转换器(如序列化)#

IObjectContentConverterFactory 默认的内容转换器工厂会返回 ObjectContentConverter 实例,该实例利用 HttpResponseMessage.ContentReadFromJsonAsync 方法,结合 System.Text.Json 序列化库,将 HttpRequestMessage 对象转换为目标接收类型。然而,这种转换方式在某些特殊类型(例如 DataTable)上可能会遇到反序列化失败的问题。为了应对这种情况,您可以自定义 ObjectContentConverter,以便选择或更换为更适合您需求的 JSON 序列化工具(例如 Newtonsoft.Json)。

自定义时需同时提供泛型和非泛型版本:

cs
// 非泛型版本public class CustomObjectContentConverter : ObjectContentConverter{    /// <inheritdoc />    public override async Task<object?> ReadAsync(Type resultType, HttpContentConverterContext context, CancellationToken cancellationToken = default)    {        // 获取 HttpResponseMessage 实例        var httpResponseMessage = context.ResponseMessage;        // 解析 HttpClient 客户端对应的 JSON 序列化上下文信息        var jsonSerializationContext =            HttpRemoteUtility.ResolveJsonSerializationContext(resultType, httpResponseMessage, ServiceProvider);        // 获取 JSON 反序列化的值(若需使用 Newtonsoft.Json 进行序列化或反序列化操作,请将以下代码替换为 Newtonsoft.Json 库的相应方法调用) ✅✅✅        var deserializedValue = await httpResponseMessage.Content.ReadFromJsonAsync(jsonSerializationContext.ResultType,            jsonSerializationContext.JsonSerializerOptions, cancellationToken);        // 获取转换的目标类型值        return jsonSerializationContext.GetResultValue(deserializedValue, httpResponseMessage);    }}// 泛型版本public class CustomObjectContentConverter<TResult> : CustomObjectContentConverter, IHttpContentConverter<TResult>{    /// <inheritdoc />    public virtual TResult? Read(HttpContentConverterContext context, CancellationToken cancellationToken = default) =>        (TResult?)base.Read(typeof(TResult), context, cancellationToken);    /// <inheritdoc />    public virtual async Task<TResult?> ReadAsync(HttpContentConverterContext context, CancellationToken cancellationToken = default) =>        (TResult?)await base.ReadAsync(typeof(TResult), context, cancellationToken);}

接着,创建自定义的 IObjectContentConverterFactory 实现:

cs
public sealed class CustomObjectContentConverterFactory : IObjectContentConverterFactory{    /// <inheritdoc />    public IHttpContentConverter<TResult> GetConverter<TResult>(HttpContentConverterContext context)    {        // 检查 HTTP 响应的内容类型是否为 XML 媒体类型        if (context.ResponseMessage.IsXmlContent())        {            return new XmlObjectContentConverter<TResult>();        }        return new CustomObjectContentConverter<TResult>();    }    /// <inheritdoc />    public IHttpContentConverter GetConverter(Type resultType, HttpContentConverterContext context)    {        // 检查 HTTP 响应的内容类型是否为 XML 媒体类型        if (context.ResponseMessage.IsXmlContent())        {            return new XmlObjectContentConverter();        }        return new CustomObjectContentConverter();    }}

最后,在 Startup.csProgram.cs 文件中,配置并注册 HttpRemote 服务,以替换默认的对象内容转换器工厂功能:

cs
services.AddHttpRemote(builder =>{    builder.UseObjectContentConverterFactory<CustomObjectContentConverterFactory>();});

这样,通过自定义的 ObjectContentConverterIObjectContentConverterFactory 工厂,可以确保在反序列化时使用指定的 JSON 选项,避免潜在的反序列化问题。

自定义内容转换器#

在特定场景下,当框架内置的 IHttpContentConverter 内容转换器无法满足需求时,可以通过自定义 IHttpContentConverter 内容转换器来解决。

例如,为 Span<char> 类型添加内容转换器,可以通过实现 IHttpContentConverter 接口来实现自定义需求。

cs
public class SpanCharContentConverter : HttpContentConverterBase<Span<char>>{    /// <inheritdoc />    public override byte[]? Read(HttpContentConverterContext context, CancellationToken cancellationToken = default) =>        AsyncUtility.RunSync(() => ReadAsync(context, cancellationToken));    /// <inheritdoc />    public override async Task<Span<char>?> ReadAsync(HttpContentConverterContext context, CancellationToken cancellationToken = default)    {        var str = await context.ResponseMessage.Content.ReadAsStringAsync(cancellationToken);        return str.AsSpan();    }}

接下来,可以通过以下两种方式应用自定义内容转换器器:

  • 单次请求设置
cs
HttpRequestBuilder.Post("https://furion.net/")    .AddHttpContentConverters(() => [ new SpanCharContentConverter() ]);
  • 全局配置

Startup.csProgram.cs 文件中,配置并注册 HttpRemote 服务,以启用自定义内容转换器功能:

cs
services.AddHttpRemote(builder =>{    builder.AddHttpContentConverters(() => [ new SpanCharContentConverter() ]);});

以下示例展示了如何在发送 HTTP 远程请求时使用 SpanCharContentConverter 内容转换器:

使用 IHttpRemoteService 方式:

cs
// 单次请求添加var span = await httpRemoteService.SendAsAsync<Span<char>>(HttpRequestBuilder.Post("https://furion.net/")    .AddHttpContentConverters(() => [ new SpanCharContentConverter() ]));// 全局配置var span = await httpRemoteService.GetAsAsync<Span<char>>("https://furion.net/");

使用 IHttpContentConverterFactory 方式:

cs
public class YourService(IHttpContentConverterFactory httpContentConverterFactory)  // .NET8+ 支持主构造函数注入{    public async Task<Span<char>?> GetSpanAsync()    {        var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://furion.net/");        using var httpClient = new HttpClient();        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);        // 调用 ReadAsync 方法将 HttpResponseMessage 对象转换为目标类型实例        var context = new HttpContentConverterContext(httpResponseMessage);        return await httpContentConverterFactory.GetConverter<Span<char>>(context).ReadAsync(context);    }}

自定义泛型内容转换器#

除了具体类型,框架同样支持泛型内容的转换。例如,定义如下泛型转换器:

cs
public class YourGenericClassContentConverter<T> : HttpContentConverterBase<YourGenericClass<T?>>{    /// <inheritdoc />    public override YourGenericClass<T?>? Read(HttpContentConverterContext context, CancellationToken cancellationToken = default)    {        // 实现同步转换逻辑    }    /// <inheritdoc />    public override Task<YourGenericClass<T?>?> ReadAsync(HttpContentConverterContext context, CancellationToken cancellationToken = default)    {        // 实现异步转换逻辑    }}

接下来在 Startup.csProgram.cs 文件中,配置并注册 HttpRemote 服务,以启用自定义泛型内容转换器功能:

cs
services.AddHttpRemote(builder =>{    builder.AddGenericHttpContentConverters(() => [ new(typeof(YourGenericClass<>), typeArgs => (IHttpContentConverter)Activator.CreateInstance(typeof(YourGenericClassContentConverter<>).MakeGenericType(typeArgs[0]))!) ]);});

以下示例展示了如何在发送 HTTP 远程请求时使用 YourGenericClassContentConverter 内容转换器:

使用 IHttpRemoteService 方式:

cs
var str = await httpRemoteService.GetAsAsync<YourGenericClass<string>>("https://furion.net/");

使用 IHttpContentConverterFactory 方式:

cs
public class YourService(IHttpContentConverterFactory httpContentConverterFactory)  // .NET8+ 支持主构造函数注入{    public async Task<YourGenericClass<string>?> GetStringAsync()    {        var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, "https://furion.net/");        using var httpClient = new HttpClient();        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);        // 调用 ReadAsync 方法将 HttpResponseMessage 对象转换为目标类型实例        var context = new HttpContentConverterContext(httpResponseMessage);        return await httpContentConverterFactory.GetConverter<YourGenericClass<string>>(context).ReadAsync(context);    }}