如何正确地后处理改装返回值?

时间:2019-01-14 14:03:15

标签: c# .net .net-core .net-standard refit

我正在使用Refit编写一些API,这很有用,并且在找出一种好的方法(如“干净”,“适当”)时遇到了一些麻烦,返回的数据。

作为示例,请考虑以下代码:

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

现在,我看到的许多REST API都有一个不幸的习惯,就是将实际数据(如“有用的”数据中)打包到JSON响应中。说,实际的JSON具有以下结构:

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

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

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

我的意思是,这两个外部模型实际上只是容器,不需要向用户公开。或者说Data属性是一个模型,它具有另一个属性IEnumerable,那么我可能只想直接将它返回给用户即可。

我不知道如何做到这一点,而不必为每个服务编写无用的包装器类,其中每个服务类也必须明显地重复接口等中的所有XML注释,从而导致更多无用的代码在周围漂浮。

我只想有一个简单的,可选的Func<T, TResult>等效项,该等效项在给定的Refit API的结果上被调用,并在将返回的数据呈现给用户之前对其进行一些修改。

2 个答案:

答案 0 :(得分:2)

摘要

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

详细信息

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

请注意,在过去的几个发行版中,RefitSettings类已有所更改。您应该查阅适合您的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。

答案 1 :(得分:2)

我发现解决此问题的一个足够干净的解决方案是使用扩展方法来扩展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时,我实际上就在使用扩展方法,该方法可以为我解决所有其他的反序列化。