company_banner

Удобный REST для Xamarin-приложений

    Разработчикам на Xamarin доступен богатый выбор компонентов для работы с сетью, и в сегодняшней нашей статье мы рассмотрим набор модулей, которые также могут быть использованы в PCL-проектах на Xamarin.Forms.

    Все статьи из колонки можно найти и прочитать по ссылке #xamarincolumn, или в конце материала под катом.



    Refit для удобного описания клиента REST API


    Самым популярным в настоящее время протоколом для общения мобильных приложений с сервером является REST в связке с Json. Поэтому наше сегодняшнее знакомство начнем с библиотеки Refit.

    Refit позволяет описать спецификации для работы с REST-сервисом в виде простого Interface с понятным набором входных и выходных параметров, включая возможность манипулировать HTTP-заголовками для отдельных запросов. Для примера возьмем демо-API сервиса httpbin.org:

        [Headers("Accept: application/json")]
        public interface IHttpbinApi
        {
            [Get("/basic-auth/{username}/{password}")]
            Task<AuthResult> BasicAuth(string username, string password, [Header("Authorization")] string authToken, CancellationToken ctx);
    
            [Get("/cache")]
            Task<HttpResponseMessage> CheckIfModified([Header("If-Modified-Since")] string lastUpdateAtString, CancellationToken ctx);
    
            [Post("/post")]
            Task<HttpResponseMessage> FormPost([Body(BodySerializationMethod.UrlEncoded)] FormData data, CancellationToken ctx);
        }
    

    После описания данного интерфейса, он подается на вход для Refit:

                var client = new HttpClient(new NativeMessageHandler())
                {
                    BaseAddress = new Uri("http://httpbin.org")
                };
                _httpbinApiService = RestService.For<IHttpbinApi>(client);
    

    Сами данные можно при необходимости (конвертации camel case или snake eyes, преобразование из множества в строковые значения) можно расширить аттрибутами из библиотеки Json.net, так как именно она используется в Refit:

          public class AuthResult
        {
            [JsonProperty("authenticated")]
            public bool IsAuthenticated { get; set; }
    
            [JsonProperty("user")]
            public string Login { get; set; }
        }
    

    В Refit в качестве выходного значения можно получить уже преобразованные объекты DTO или HttpResponseMessage. Последний позволяет получить информацию о запросе и ответе, что может быть полезно при отладке. При желании также может использоваться ModernHttpClient при создании HttpClient. В целом, Refit — достаточно удобный и универсальный инструмент для Xamarin-разработчиков в том числе.

    Примечание 1: для установки Refit 3.0 в PCL-проект Xamarin.Forms потребуется перевести проект на .NET Standard.

    Примечание 2: в документации Refit нет упоминания на использование CancelationToken для отмены активных операций, но данный механизм работает и описан в тикете.

    Polly


    При разработке мобильных приложений часто приходится учитывать фактор нестабильности сигнала в сотовых сетях, поэтому при выполнении сетевых запросов часто возникает необходимости делать повторные попытки. Это позволяет не отвлекать лишний раз пользователя просьбой повторить запрос.

    Интересный подход по использованию Refit и Polly описал Rob Gibbens в своем блоге (дополнительно там показан пример приоретизацией сетевых запросов с помощью Fusillade).

    Вот так мы совершаем запрос:

    protected async Task<RequestResult> MakeRequest<T>(Func<CancellationToken, Task<T>> loadingFunction, CancellationToken cancellationToken)
            {
                Exception exception = null;
                var result = default(T);
    
                try
                {
                    result = await Policy.Handle<WebException>().Or<HttpRequestException>()
                        .WaitAndRetryAsync(3, i => TimeSpan.FromMilliseconds(300), (ex, span) => exception = ex)
                        .ExecuteAsync(loadingFunction, cancellationToken);
                }
                catch (Exception e)
                {
    // Сюда приходят ошибки вроде отсутствия интернет-соединения или неправильной работы DNS
                    exception = e;
                }
    //TODO: Обработать исключения или передать их дальше            
                return result;
            }
    

    Вместо loadingFunction необходимо передать ваш код обращения к Refit:

       var authToken = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
       return await MakeRequest(ct => _httpbinApiService.BasicAuth(username, password, authToken, ct), cancellationToken);
    

    Итак, мы интегрировали Refit, Polly и ModernHttpClient.

    Кэш


    И в завершении статьи можно рассмотреть использование кэша при работе с сетью. Xamarin-разработчику доступны все возможности целевых платформ, поэтому для реализации кэша можно использовать различные СУБД. Одним из самых популярных кэшеров выступает Akavache, работающий поверх SQLite.

        var cache = BlobCache.LocalMachine;
        var cachedObjects = cache.GetAndFetchLatest("objects", GetRemoteObjectAsync,
            offset =>
            {
                TimeSpan elapsed = DateTimeOffset.Now - offset;
                return elapsed > new TimeSpan(hours: 0, minutes: 30, seconds: 0);
            });
    

    Также можно использовать для реализации кэша очень удобную мобильную СУБД Realm. Ниже представлен пример кэшера на базе Realm:

     public static class LocalCache
        {
            private class CachedObject : RealmObject
            {
                [PrimaryKey]
                public string Key { get; set; }
                public string Value { get; set; }
                public DateTimeOffset UpdatedAt { get; set; }
            }
    
    
            private static readonly RealmConfiguration Configuration = new RealmConfiguration("cache.realm", true);
            private static Realm Db => Realm.GetInstance(Configuration);
    
    
            public static async Task WriteToCache<T>(string key, T data, DateTimeOffset timeStamp)
            {
                if (String.IsNullOrEmpty(key) || data == null || timeStamp == DateTimeOffset.MinValue) return;
                
                var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
                if (currentValue == null)
                    await Db.WriteAsync(db =>
                    {
                        var newValue = db.CreateObject<CachedObject>();
                        newValue.Key = key;
                        newValue.UpdatedAt = timeStamp;
                        newValue.Value = JsonConvert.SerializeObject(data);
                    });
                else
                    using (var transaction = Db.BeginWrite())
                    {
                        currentValue.Value = JsonConvert.SerializeObject(data);
                        currentValue.UpdatedAt = timeStamp;
                        transaction.Commit();
                    }
            }
    
            public static DateTimeOffset CacheLastUpdated(string key)
            {
                if (String.IsNullOrEmpty(key)) return DateTimeOffset.MinValue;
                
                var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
                return currentValue?.UpdatedAt ?? DateTimeOffset.MinValue;
            }
    
            public static void RemoveCache(string key)
            {
                if (String.IsNullOrEmpty(key)) return;
                
                var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
                if (currentValue == null) return;
    
    
                using (var transaction = Db.BeginWrite())
                {
                    Db.Remove(currentValue);
                    transaction.Commit();
                }
            }
    
            public static T GetFromCache<T>(string key)
            {
                if (String.IsNullOrEmpty(key)) return default(T);
                
                var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
                return currentValue?.Value == null ? default(T) : JsonConvert.DeserializeObject<T>(currentValue.Value);
            }
    
            public static void ClearCache()
            {
                Realm.DeleteRealm(Configuration);
            }
        }
    

    Заключение


    Итак, сегодня мы рассмотрели использование Refit, Json.net, ModernHttpClient, Polly и Realm при интеграции с REST API. В следующей статье мы рассмотрим вопросы интеграции Xamarin с внешними сервисами и Azure.

    Об авторах



    Вячеслав Черников — руководитель отдела разработки компании Binwell. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone.

    Другие статьи автора:


    Полезные ссылки


    Microsoft 355,10
    Microsoft — мировой лидер в области ПО и ИТ-услуг
    Поделиться публикацией
    Похожие публикации
    Комментарии 7
    • 0
      Не совсем по теме, но хотелось бы задать вопрос специалистам Xamarin. Недавно хотел попробовать эту кроссплатформенную среду разработки, но столкнулся с тем, что по факту кроссплатформенностью и не пахнет, для каждой целевой ОС надо делать всё отдельно в том числе интерфейс, действительно кроссплатформенной остаётся только бизнеслогика, которая не всегда является самой сложной частью приложения.
      Подскажите, может я не в ту сторону смотрел?
      • +3

        Посмотрите в сторону Xamarin.Forms — благодаря ему можно интерфейс описывать сразу для всех поддерживаемых платформ. Конечно, если приложение достаточно сложное, то многие вещи придется описывать отдельно для каждой платформы, но количество кроссплатформенного кода в первой статье цикла было про это https://habrahabr.ru/company/microsoft/blog/281897/


        Если не использовать Xamarin.Forms, то кроссплатформенной идет бизнес-логика приложения. Благодаря библиотекам типо MVVM-cross можно уменьшить количество платформенного кода описании UI и прочих вещей.

        • 0
          Спасибо, с интерфейсом прояснили, а что с работой с камерой, GPS, с файловой системой, аудио?
          • +1

            Как написал S_A — для многих вещей есть уже готовые плагины, которые позволяют обращаться к ним из кроссплатформенного кода. Хотя некоторые вещи могут быть не покрыты плагинами (сразу и не вспомню таких) или реализованы не совсем так, как требуется, тогда можно реализовать самому. Доступ ко всем системным API имеется.

          • +1
            есть плагины для xamarin.forms. с последнего пререлиза xaml в xamarin.forms также поддерживает и нативные компоненты. так что чем дальше в лес, тем меньше нативного кода. молодцы вообще они.
        • 0
          Мне кажется в статье не хватает RestSharp.
          • +1
            У RestSharp традиционные проблемы с PCL-проектами, поэтому для Xamarin.Forms он не очень хорошо подходит. Да и Refit на наш взгляд более удобное и простое решение. Но это все IMHO, а так RestSharp — да, хороший инструмент.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое
          Интересные публикации