如何正确 post-process Refit return 值?

How to properly post-process Refit return values?

我正在使用 Refit 编写一些 APIs,效果非常好,但我在找出一个好的方面遇到了一些麻烦(如 "clean"、"proper") 对 returned 数据执行一些任意处理的方法。

例如,请考虑以下代码:

public interface ISomeService
{
    [Get("/someurl/{thing}.json")]
    Task<Data> GetThingAsync([AliasAs("thing")] string thing);
}

现在,我见过的许多 REST APIs 都有将实际数据(如 "useful" 数据)打包到 JSON 响应中的不幸习惯。比如说,实际的 JSON 有这样的结构:

{
    "a" = {
        "b" = {
            "data" = {
...
}

现在,通常我只映射所有必要的模型,这将允许 Refit 正确反序列化响应。这虽然使 API 使用起来有点笨拙,因为每次我使用它时我都必须做类似的事情:

var response = await SomeService.GetThingAsync("foo");
var data = response.A.B.Data;

我的意思是那两个外部模型实际上只是容器,不需要暴露给用户。或者,假设 Data 属性 是一个有另一个 属性 的模型,它是一个 IEnumerable,我很可能只想直接 return 到用户。

我不知道如何在不必为每个服务编写无用的包装器 类 的情况下执行此操作,其中每个服务还必须明显重复接口中的所有 XML 注释等., 导致更多无用的代码四处飘荡。

我只想有一些简单的、可选的 Func<T, TResult> 等价物,它在给定 Refit API 的结果上被调用,并在 returned 上做一些修改在将数据呈现给用户之前。

总结

您可以将自定义 JsonConverters 传递给 Refit 以修改其序列化或反序列化各种类型的方式。

详情

RefitSettings class 提供自定义选项,包括 JSON 序列化程序设置。

请注意,RefitSettings class 在过去的几个版本中发生了一些变化。您应该查阅适用于您的 Refit 版本的相应文档。

来自 Refit 的最新 examples

var myConverters = new List<JsonConverter>();
myConverters += new myCustomADotBConverter();

var myApi = RestService.For<IMyApi>("https://api.example.com",
    new RefitSettings {
        ContentSerializer = new JsonContentSerializer( 
            new JsonSerializerSettings {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                Converters = myConverters
        }
    )});

这是来自 JSON.Net docs 的自定义 JsonConverter 的基本示例。

public class VersionConverter : JsonConverter<Version>
{
    public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }

    public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        string s = (string)reader.Value;

        return new Version(s);
    }
}

public class NuGetPackage
{
    public string PackageId { get; set; }
    public Version Version { get; set; }
}

该示例 JsonConverter 旨在序列化或反序列化 JSON 负载的“版本”字段,如下所示:

{
  "PackageId": "Newtonsoft.Json",
  "Version": "10.0.4"
}

您必须为要反序列化的嵌套数据结构编写自己的自定义 JsonConverter。

我发现解决这个问题的一个足够干净的解决方案是使用扩展方法来扩展 Refit 服务。例如,假设我有一个像这样的 JSON 映射:

public class Response
{
    [JsonProperty("container")]
    public DataContainer Container { get; set; }
}

public class DataContainer
{
    [JsonProperty("data")]
    public Data Data { get; set; }
}

public class Data
{
    [JsonProperty("ids")]
    public IList<string> Ids { get; set; }
}

然后我有一个这样的 Refit API:

public interface ISomeService
{
    [Get("/someurl/{thing}.json")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("use extension " + nameof(ISomeService) + "." + nameof(SomeServiceExtensions.GetThingAsync))]
    Task<Response> _GetThingAsync(string thing);
}

我可以像这样定义一个扩展方法,并使用这个方法代替 Refit 服务公开的 API:

#pragma warning disable 612, 618

public static class SomeServiceExtensions
{
    public static Task<Data> GetThingAsync(this ISomeService service, string thing)
    {
        var response = await service._GetThingAsync(thing);
        return response.Container.Data.Ids;
    }
}

这样,每当我调用 GetThingAsync API 时,我实际上是在使用可以为我处理所有额外反序列化的扩展方法。

如果您使用的是 C# 8.0 或更高版本,您可以选择采用 Refit's website 推荐的方法:

  1. 在接口的私有方法中写一些转换代码
  2. 将您的 Get 方法的名称更改为不同的名称,例如 _Get()GetInternal() 并将其设为 internal 以便调用代码看不到它。
  3. 创建一个具有原始 Get() 名称的新 public 方法,然后在返回前为其提供一个应用转换的主体。