Как правильно обработать Refit возвращаемые значения?
Я пишу некоторые API, используя Refit, который творит чудеса, и у меня возникают проблемы с поиском хорошего (как в "чистом", "правильном") способа выполнения произвольной обработки возвращаемых данных.
В качестве примера рассмотрим этот код:
public interface ISomeService
{
[Get("/someurl/{thing}.json")]
Task<Data> GetThingAsync([AliasAs("thing")] string thing);
}
Многие API-интерфейсы REST, которые я видел, имеют неудачную привычку упаковывать реальные данные (как в "полезных" данных) вглубь ответа 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>
эквивалент, который вызывается в результате заданного API Refit, и вносит некоторые изменения в возвращаемые данные, прежде чем представить их пользователю.
3 ответа
Я обнаружил, что достаточно чистым решением этой проблемы является использование методов расширения для расширения сервисов 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);
}
Я могу просто определить такой метод расширения и использовать его вместо API, предоставляемого службой Refit:
#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, я на самом деле использую метод расширения, который может позаботиться обо всей дополнительной десериализации для меня.
Резюме
Вы можете передать пользовательские JsonConverters в Refit, чтобы изменить способ сериализации или десериализации различных типов.
подробность
Класс RefitSettings предоставляет параметры настройки, включая параметры сериализатора JSON.
Помните, что класс RefitSettings несколько изменился за последние несколько выпусков. Вы должны обратиться к соответствующей документации для вашей версии Refit.
Из последних примеров Refit
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
}
)});
Вот базовый пример пользовательского JsonConverter из документации JSON.Net.
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 для вложенной структуры данных, которую вы хотите десериализовать.
Если вы используете C# 8.0 или выше, у вас есть возможность использовать подход, рекомендованный веб-сайтом Refit:
- Напишите некоторый код преобразования в приватном методе интерфейса.
- Измените имя вашего метода Get на что-то другое, например
_Get()
или жеGetInternal()
и сделай этоinternal
поэтому он не виден вызывающему коду. - Создайте новый общедоступный метод, который имеет исходный
Get()
имя, а затем дайте ему тело, которое применяет преобразование перед возвратом.