Поделиться через


Использование клиентской библиотеки мобильных приложений Azure версии 4.2.0 для .NET

Заметка

Этот продукт отставлен. Сведения о замене проектов с помощью .NET 8 или более поздней версии см. вбиблиотеке Community Toolkit Datasync.

В этом руководстве показано, как выполнять распространенные сценарии с помощью клиентской библиотеки .NET для мобильных приложений Azure. Используйте клиентская библиотека .NET в приложениях Windows (WPF, UWP) или Xamarin (Native или Forms). Если вы не знакомы с мобильными приложениями Azure, сначала ознакомьтесь с кратким руководством по для Xamarin.Forms.

Предупреждение

В этой статье рассматриваются сведения о версии библиотеки версии 4.2.0, которая заменена библиотекой версии 5.0.0. Актуальные сведения см. в статье последней версии

Поддерживаемые платформы

Клиентская библиотека .NET поддерживает .NET Standard 2.0 и следующие платформы:

  • Xamarin.Android от уровня API 19 до уровня API 30.
  • Xamarin.iOS версии 8.0–14.3.
  • Универсальная платформа Windows создает 16299 и более поздних версий.
  • Любое приложение .NET Standard 2.0.

Проверка подлинности "поток сервера" использует WebView для представленного пользовательского интерфейса и может быть недоступна на каждой платформе. Если он недоступен, необходимо предоставить проверку подлинности client-flow. Эта клиентская библиотека не подходит для контрольных или форм-факторов Интернета вещей при использовании проверки подлинности.

Настройка и предварительные требования

Предполагается, что вы уже создали и опубликовали внутренний проект мобильных приложений Azure, который включает по крайней мере одну таблицу. В коде, используемом в этом разделе, таблица называется TodoItem и содержит строковые Id, а поля Text и логический столбец Complete. Эта таблица аналогична таблице, созданной при выполнении краткого руководства.

Соответствующий тип на стороне клиента в C# является этим классом:

public class TodoItem
{
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }
}

jsonPropertyAttribute используется для определения сопоставления PropertyName между клиентским полем и полем таблицы.

Чтобы узнать, как создавать таблицы в серверной части мобильных приложений, см. раздел пакета SDK для .NET Server раздела пакета SDK Node.js Server SDK.

Установка пакета sdk для управляемого клиента

Щелкните проект правой кнопкой мыши, нажмите клавишу Управление пакетами NuGet, найдите пакет Microsoft.Azure.Mobile.Client, а затем нажмите Установить. Для автономных возможностей также установите пакет Microsoft.Azure.Mobile.Client.SQLiteStore.

Создание клиента мобильных приложений Azure

Следующий код создает объект MobileServiceClient, используемый для доступа к серверной части мобильного приложения.

var client = new MobileServiceClient("MOBILE_APP_URL");

В приведенном выше коде замените MOBILE_APP_URL URL-адрес серверной части службы приложений. Объект MobileServiceClient должен быть одним.

Работа с таблицами

В следующем разделе описано, как выполнять поиск и извлечение записей и изменять данные в таблице. Рассматриваются следующие разделы:

Создание ссылки на таблицу

Весь код, который обращается к данным или изменяет данные в серверной таблице, вызывает функции в объекте MobileServiceTable. Получите ссылку на таблицу, вызвав метод GetTable следующим образом:

IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();

Возвращаемый объект использует типизованную модель сериализации. Также поддерживается нетипичная модель сериализации. В следующем примере создается ссылка на нетипизованную таблицу:

// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");

В нетипизированных запросах необходимо указать базовую строку запроса OData.

Запрос данных из мобильного приложения

В этом разделе описывается, как выдавать запросы к серверной части мобильного приложения, которая включает следующие функции:

Заметка

Размер страниц на основе сервера применяется, чтобы предотвратить возврат всех строк. Разбиение на страницах сохраняет запросы по умолчанию для больших наборов данных от негативного влияния на службу. Чтобы вернуть более 50 строк, используйте метод Skip и Take, как описано в разделе Возвращать данные на страницах.

Фильтрация возвращаемых данных

В следующем коде показано, как фильтровать данные, включая предложение Where в запросе. Он возвращает все элементы из todoTable, свойство Complete которого равно false. Функция Где функция применяет предикат фильтрации строк к запросу к таблице.

// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .ToListAsync();

Вы можете просмотреть URI запроса, отправленного в серверную часть, с помощью программного обеспечения проверки сообщений, например средств разработчика браузера или Fiddler. Если вы посмотрите URI запроса, обратите внимание, что строка запроса изменяется:

GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1

Этот запрос OData преобразуется в SQL-запрос с помощью пакета SDK для сервера:

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0

Функция, передаваемая методу Where, может иметь произвольное количество условий.

// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
    .ToListAsync();

Этот пример будет преобразован в SQL-запрос с помощью пакета SDK для сервера:

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0
          AND ISNULL(text, 0) = 0

Этот запрос также можно разделить на несколько предложений:

List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .Where(todoItem => todoItem.Text != null)
    .ToListAsync();

Два метода эквивалентны и могут использоваться взаимозаменяемо. Бывший вариант — объединение нескольких предикатов в одном запросе — более компактный и рекомендуемый.

Предложение Where поддерживает операции, которые будут преобразованы в подмножество OData. К операциям относятся:

  • Реляционные операторы (==, !=, <, <=, >, >=),
  • Арифметические операторы (+, -, /, *, %)
  • Точность чисел (Math.Floor, Math.Ceiling),
  • Строковые функции (Length, Substring, Replace, IndexOf, StartsWith, EndsWith)
  • Свойства даты (Year, Month, Day, Hour, Minute, Second)
  • Доступ к свойствам объекта и
  • Выражения, объединяющие любую из этих операций.

При рассмотрении поддержки пакета SDK для сервера можно рассмотреть документации по OData версии 3.

Сортировка возвращаемых данных

В следующем коде показано, как сортировать данные, включая OrderBy или функцию OrderByDescending в запросе. Он возвращает элементы из todoTable сортировки по Text полю.

// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
                .OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();

// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
                .OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();

Возврат данных на страницах

По умолчанию серверная часть возвращает только первые 50 строк. Вы можете увеличить количество возвращаемых строк, вызвав метод Take. Используйте Take вместе с методом Skip, чтобы запросить определенную "страницу" общего набора данных, возвращаемого запросом. Следующий запрос при выполнении возвращает первые три элемента таблицы.

// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();

Следующий измененный запрос пропускает первые три результата и возвращает следующие три результата. Этот запрос создает вторую "страницу" данных, где размер страницы составляет три элемента.

// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();

Метод IncludeTotalCount запрашивает общее количество всех возвращенных записей, игнорируя любое предложение по страницам и ограничение, указанное:

query = query.IncludeTotalCount();

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

Заметка

Чтобы переопределить ограничение в 50 строк в серверной части мобильного приложения, необходимо также применить EnableQueryAttribute к общедоступному методу GET и указать поведение разбиения по страницам. При применении к методу следующие значения задают максимальные возвращаемые строки в 1000:

[EnableQuery(MaxTop=1000)]

Выбор определенных столбцов

Вы можете указать, какой набор свойств следует включить в результаты, добавив в запрос предложение Select. Например, в следующем коде показано, как выбрать только одно поле, а также как выбрать и отформатировать несколько полей:

// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
                .Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();

// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
                .Select(todoItem => string.Format("{0} -- {1}",
                    todoItem.Text.PadRight(30), todoItem.Complete ?
                    "Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();

Все описанные до сих пор функции являются аддитивным, поэтому мы можем сохранить их цепочку. Каждый прицеленный вызов влияет на большее количество запросов. Еще один пример:

MobileServiceTableQuery<TodoItem> query = todoTable
                .Where(todoItem => todoItem.Complete == false)
                .Select(todoItem => todoItem.Text)
                .Skip(3).
                .Take(3);
List<string> items = await query.ToListAsync();

Поиск данных по идентификатору

Функцию LookupAsync можно использовать для поиска объектов из базы данных с определенным идентификатором.

// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");

Выполнение нетипизированных запросов

При выполнении запроса с помощью нетипизированного объекта таблицы необходимо явно указать строку запроса OData, вызвав ReadAsync, как показано в следующем примере:

// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");

Возвращаются значения JSON, которые можно использовать как контейнер свойств. Дополнительные сведения о и Newtonsoft Json см. на сайте JSON Newtonsoft.

Вставка данных

Все типы клиентов должны содержать элемент с именем id, который по умолчанию является строкой. Этот идентификатор требуется для выполнения операций CRUD и автономной синхронизации. В следующем коде показано, как использовать метод InsertAsync для вставки новых строк в таблицу. Параметр содержит данные, которые необходимо вставить в качестве объекта .NET.

await todoTable.InsertAsync(todoItem);

Если уникальное пользовательское значение идентификатора не входит в todoItem во время вставки, идентификатор GUID создается сервером. Вы можете получить созданный идентификатор, проверив объект после возврата вызова.

Чтобы вставить нетипизированные данные, можно воспользоваться преимуществами Json.NET:

JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);

Ниже приведен пример использования адреса электронной почты в качестве уникального идентификатора строки:

JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);

Работа со значениями идентификаторов

Мобильные приложения поддерживают уникальные настраиваемые строковые значения для столбца таблицы столбца. Строковое значение позволяет приложениям использовать пользовательские значения, такие как адреса электронной почты или имена пользователей для идентификатора. Идентификаторы строк предоставляют следующие преимущества:

  • Идентификаторы создаются без обхода в базу данных.
  • Записи проще объединить из разных таблиц или баз данных.
  • Значения идентификаторов могут лучше интегрироваться с логикой приложения.

Если строковое значение идентификатора не задано в вставленной записи, серверная часть мобильного приложения создает уникальное значение для идентификатора. Вы можете использовать метод Guid.NewGuid для создания собственных значений идентификаторов на клиенте или в серверной части.

JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));

Обновление данных

В следующем коде показано, как использовать метод UpdateAsync для обновления существующей записи с тем же идентификатором с новыми сведениями. Параметр содержит данные, которые необходимо обновить как объект .NET.

await todoTable.UpdateAsync(todoItem);

Чтобы обновить нетипизированные данные, можно воспользоваться преимуществами Newtonsoft JSON следующим образом:

JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);

При внесении обновления необходимо указать поле id. Серверная часть использует поле id для определения строки для обновления. Поле id можно получить из результата вызова InsertAsync. При попытке обновить элемент без предоставления значения id возникает ArgumentException.

Удаление данных

В следующем коде показано, как использовать метод DeleteAsync для удаления существующего экземпляра. Экземпляр определяется полем id в todoItem.

await todoTable.DeleteAsync(todoItem);

Чтобы удалить нетипизированные данные, можно воспользоваться преимуществами Json.NET следующим образом:

JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);

При выполнении запроса на удаление необходимо указать идентификатор. Другие свойства не передаются в службу или игнорируются в службе. Результат вызова DeleteAsync обычно null. Идентификатор для передачи можно получить из результата вызова InsertAsync. При попытке удалить элемент без указания поля id возникает MobileServiceInvalidOperationException.

Разрешение конфликтов и оптимистическое параллелизм

Два или более клиентов могут одновременно записывать изменения в один и тот же элемент. Без обнаружения конфликтов последняя запись перезаписывает все предыдущие обновления. элемент управления оптимистическим параллелизмом предполагает, что каждая транзакция может зафиксировать и поэтому не использует блокировку ресурсов. Перед фиксацией транзакции оптимистичный элемент управления параллелизмом проверяет, что другая транзакция не изменила данные. Если данные были изменены, откат транзакции фиксации выполняется.

Мобильные приложения поддерживают функцию управления оптимистическим параллелизмом, отслеживая изменения каждого элемента с помощью столбца системного свойства version, определенного для каждой таблицы в серверной части мобильного приложения. При каждом обновлении записи мобильные приложения задают свойство version для этой записи новым значением. Во время каждого запроса обновления свойство version записи, включенной в запрос, сравнивается с тем же свойством записи на сервере. Если версия, переданная с запросом, не соответствует серверной части, клиентская библиотека вызывает исключение MobileServicePreconditionFailedException<T>. Тип, включенный с исключением, — это запись из серверной части, содержащей версию записи серверов. Затем приложение может использовать эти сведения, чтобы решить, следует ли повторно выполнить запрос на обновление с правильным значением version из серверной части, чтобы зафиксировать изменения.

Определите столбец класса таблицы для системного свойства version, чтобы включить оптимистическое параллелизм. Например:

public class TodoItem
{
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }

    // *** Enable Optimistic Concurrency *** //
    [JsonProperty(PropertyName = "version")]
    public string Version { set; get; }
}

Приложения, использующие нетипизированные таблицы, обеспечивают оптимистическое параллелизм, задав флаг Version на SystemProperties таблицы следующим образом.

//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;

Помимо включения оптимистического параллелизма, необходимо также поймать исключение MobileServicePreconditionFailedException<T> в коде при вызове UpdateAsync. Устраните конфликт, применив правильный version к обновленной записи и вызову UpdateAsync с разрешенной записью. В следующем коде показано, как устранить конфликт записи после обнаружения:

private async void UpdateToDoItem(TodoItem item)
{
    MobileServicePreconditionFailedException<TodoItem> exception = null;

    try
    {
        //update at the remote table
        await todoTable.UpdateAsync(item);
    }
    catch (MobileServicePreconditionFailedException<TodoItem> writeException)
    {
        exception = writeException;
    }

    if (exception != null)
    {
        // Conflict detected, the item has changed since the last query
        // Resolve the conflict between the local and server item
        await ResolveConflict(item, exception.Item);
    }
}


private async Task ResolveConflict(TodoItem localItem, TodoItem serverItem)
{
    //Ask user to choose the resolution between versions
    MessageDialog msgDialog = new MessageDialog(
        String.Format("Server Text: \"{0}\" \nLocal Text: \"{1}\"\n",
        serverItem.Text, localItem.Text),
        "CONFLICT DETECTED - Select a resolution:");

    UICommand localBtn = new UICommand("Commit Local Text");
    UICommand ServerBtn = new UICommand("Leave Server Text");
    msgDialog.Commands.Add(localBtn);
    msgDialog.Commands.Add(ServerBtn);

    localBtn.Invoked = async (IUICommand command) =>
    {
        // To resolve the conflict, update the version of the item being committed. Otherwise, you will keep
        // catching a MobileServicePreConditionFailedException.
        localItem.Version = serverItem.Version;

        // Updating recursively here just in case another change happened while the user was making a decision
        UpdateToDoItem(localItem);
    };

    ServerBtn.Invoked = async (IUICommand command) =>
    {
        RefreshTodoItems();
    };

    await msgDialog.ShowAsync();
}

Дополнительные сведения см. в разделе Автономная синхронизация данных в мобильных приложениях Azure.

Привязка данных к пользовательскому интерфейсу Windows

В этом разделе показано, как отображать возвращаемые объекты данных с помощью элементов пользовательского интерфейса в приложении Windows. Следующий пример кода привязывается к источнику списка с запросом на неполные элементы. MobileServiceCollection создает коллекцию привязок с поддержкой мобильных приложений.

// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .ToCollectionAsync();

// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl  = items;

// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;

Некоторые элементы управления в управляемой среде выполнения поддерживают интерфейс, называемый ISupportIncrementalLoading. Этот интерфейс позволяет элементам управления запрашивать дополнительные данные при прокрутке пользователя. Существует встроенная поддержка этого интерфейса для универсальных приложений Windows с помощью MobileServiceIncrementalLoadingCollection, которая автоматически обрабатывает вызовы из элементов управления. Используйте MobileServiceIncrementalLoadingCollection в приложениях Windows следующим образом:

MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();

ListBox lb = new ListBox();
lb.ItemsSource = items;

Чтобы использовать новую коллекцию в приложениях Windows Phone 8 и Silverlight, используйте методы расширения ToCollection в IMobileServiceTableQuery<T> и IMobileServiceTable<T>. Чтобы загрузить данные, вызовите LoadMoreItemsAsync().

MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();

При использовании коллекции, созданной путем вызова ToCollectionAsync или ToCollection, вы получите коллекцию, которая может быть привязана к элементам управления пользовательского интерфейса. Эта коллекция учитывает разбиение по страницам. Так как коллекция загружает данные из сети, загрузка иногда завершается ошибкой. Чтобы обработать такие сбои, переопределите метод OnException на MobileServiceIncrementalLoadingCollection для обработки исключений, вызванных вызовами LoadMoreItemsAsync.

Рассмотрите, имеет ли таблица множество полей, но вы хотите отобразить некоторые из них в элементе управления. Вы можете использовать инструкции в предыдущем разделе "Выбрать определенные столбцы", чтобы выбрать определенные столбцы для отображения в пользовательском интерфейсе.

Изменение размера страницы

Мобильные приложения Azure возвращают не более 50 элементов на запрос по умолчанию. Размер страниц можно изменить, увеличив максимальный размер страницы как на клиенте, так и на сервере. Чтобы увеличить запрошенный размер страницы, укажите PullOptions при использовании PullAsync():

PullOptions pullOptions = new PullOptions
    {
        MaxPageSize = 100
    };

Если вы сделали PageSize равным или больше 100 на сервере, запрос возвращает до 100 элементов.

Работа с автономными таблицами

Автономные таблицы используют локальное хранилище SQLite для хранения данных для использования в автономном режиме. Все операции таблицы выполняются в локальном хранилище SQLite вместо удаленного хранилища сервера. Чтобы создать автономную таблицу, сначала подготовьте проект.

  • В Visual Studio щелкните правой кнопкой мыши решение >Управление пакетами NuGet для решения..., а затем найдите и установите Microsoft.Azure.Mobile.Client.SQLiteStore пакет NuGet для всех проектов в решении.
  • Для устройств Windows нажмите клавишу Ссылкидобавить ссылку...разверните папку расширения Windows , а затем включите соответствующий пакет SDK SQLite для Windows вместе с пакетом SDK Visual C++ 2013 для Windows SDK. Имена пакета SDK SQLite немного отличаются от каждой платформы Windows.

Перед созданием ссылки на таблицу необходимо подготовить локальное хранилище:

var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();

//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);

Инициализация хранилища обычно выполняется сразу после создания клиента. OfflineDbPath должен быть именем файла, подходящим для использования на всех поддерживаемых платформах. Если путь является полным путем (то есть начинается с косой черты), используется этот путь. Если путь не является полным, файл помещается в расположение для конкретной платформы.

  • Для устройств iOS и Android путь по умолчанию — это папка "Личные файлы".
  • Для устройств Windows путь по умолчанию — это папка AppData для конкретного приложения.

Ссылку на таблицу можно получить с помощью метода GetSyncTable<>:

var table = client.GetSyncTable<TodoItem>();

Для использования автономной таблицы не требуется проходить проверку подлинности. При взаимодействии с серверной службой необходимо пройти проверку подлинности.

Синхронизация автономной таблицы

Автономные таблицы по умолчанию не синхронизируются с серверной частью. Синхронизация разделена на две части. Вы можете отправлять изменения отдельно от скачивания новых элементов. Ниже приведен типичный метод синхронизации:

public async Task SyncAsync()
{
    ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;

    try
    {
        await this.client.SyncContext.PushAsync();

        await this.todoTable.PullAsync(
            //The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
            //Use a different query name for each unique query in your program
            "allTodoItems",
            this.todoTable.CreateQuery());
    }
    catch (MobileServicePushFailedException exc)
    {
        if (exc.PushResult != null)
        {
            syncErrors = exc.PushResult.Errors;
        }
    }

    // Simple error/conflict handling. A real application would handle the various errors like network conditions,
    // server conflicts and others via the IMobileServiceSyncHandler.
    if (syncErrors != null)
    {
        foreach (var error in syncErrors)
        {
            if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
            {
                //Update failed, reverting to server's copy.
                await error.CancelAndUpdateItemAsync(error.Result);
            }
            else
            {
                // Discard local change.
                await error.CancelAndDiscardItemAsync();
            }

            Debug.WriteLine(@"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
        }
    }
}

Если первый аргумент для PullAsync имеет значение NULL, то добавочная синхронизация не используется. Каждая операция синхронизации извлекает все записи.

Пакет SDK выполняет неявную PushAsync() перед извлечением записей.

Обработка конфликтов выполняется в методе PullAsync(). Вы можете справиться с конфликтами так же, как и с онлайн-таблицами. Конфликт создается при вызове PullAsync() вместо вставки, обновления или удаления. При возникновении нескольких конфликтов они объединяются в один mobileServicePushFailedException. Обработка каждого сбоя отдельно.

Работа с пользовательским API

Пользовательский API позволяет определить пользовательские конечные точки, предоставляющие функциональные возможности сервера, которые не сопоставляются с операцией вставки, обновления, удаления или чтения. Используя пользовательский API, вы можете контролировать обмен сообщениями, включая чтение и настройку заголовков HTTP-сообщений и определение формата текста сообщения, отличного от JSON.

Вы вызываете пользовательский API, вызвав один из методов InvokeApiAsync на клиенте. Например, следующая строка кода отправляет запрос POST в API completeAll на серверной части:

var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);

Эта форма является вызовом типизированного метода и требует определения типа возвращаемого значения MarkAllResult. Поддерживаются как типизированные, так и нетипизированные методы.

Метод InvokeApiAsync() предварительно добавляет метод "/api/" в API, который вы хотите вызвать, если API не начинается с "/". Например:

  • InvokeApiAsync("completeAll",...) вызовы /api/completeAll в серверной части
  • InvokeApiAsync("/.auth/me",...) вызовы /.auth/me на серверной части

Вы можете использовать InvokeApiAsync для вызова любого webAPI, в том числе тех веб-API, которые не определены в мобильных приложениях Azure. При использовании InvokeApiAsync() соответствующие заголовки, включая заголовки проверки подлинности, отправляются с запросом.

Проверка подлинности пользователей

Мобильные приложения поддерживают проверку подлинности и авторизацию пользователей приложений с помощью различных внешних поставщиков удостоверений: Facebook, Google, Учетной записи Майкрософт, Twitter и Идентификатора Microsoft Entra. Вы можете задать разрешения для таблиц, чтобы ограничить доступ для определенных операций только прошедшими проверку подлинности пользователями. Вы также можете использовать удостоверение прошедших проверку подлинности пользователей для реализации правил авторизации в сценариях сервера.

Поддерживаются два потока проверки подлинности: управляемые клиентом и поток управляемых сервером. Управляемый сервером поток обеспечивает простейший интерфейс проверки подлинности, так как он использует интерфейс веб-проверки подлинности поставщика. Управляемый клиентом поток обеспечивает более глубокую интеграцию с возможностями для конкретного устройства, так как она использует пакеты SDK для конкретного поставщика для конкретного устройства.

Заметка

Мы рекомендуем использовать управляемый клиентом поток в рабочих приложениях.

Чтобы настроить проверку подлинности, необходимо зарегистрировать приложение в одном или нескольких поставщиках удостоверений. Поставщик удостоверений создает идентификатор клиента и секрет клиента для приложения. Затем эти значения задаются в серверной части, чтобы включить проверку подлинности и авторизацию службы приложений Azure.

В этом разделе рассматриваются следующие разделы:

Управляемая клиентом проверка подлинности

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

Примеры приведены для следующих шаблонов проверки подлинности потока клиента:

Проверка подлинности пользователей с помощью библиотеки проверки подлинности Active Directory

Библиотеку проверки подлинности Active Directory (ADAL) можно использовать для запуска проверки подлинности пользователей от клиента с помощью проверки подлинности Microsoft Entra.

Предупреждение

Поддержка библиотеки проверки подлинности Active Directory (ADAL) завершится в декабре 2022 г. Приложения, использующие ADAL в существующих версиях ОС, будут продолжать работать, но техническая поддержка и обновления системы безопасности будут завершены. Дополнительные сведения см. в разделе Миграция приложений в MSAL.

  1. Настройте серверную часть мобильного приложения для входа в Microsoft Entra, следуя руководство по настройке службы приложений для входа Active Directory. Обязательно выполните необязательный шаг регистрации собственного клиентского приложения.

  2. В Visual Studio откройте проект и добавьте ссылку на пакет NuGet Microsoft.IdentityModel.Clients.ActiveDirectory. При поиске включите предварительные версии.

  3. Добавьте следующий код в приложение в соответствии с используемой платформой. В каждом из них сделайте следующее:

    • Замените INSERT-AUTHORITY-HERE именем клиента, в котором вы подготовили приложение. Формат должен быть https://login.microsoftonline.com/contoso.onmicrosoft.com. Это значение можно скопировать на вкладке "Домен" в идентификаторе Microsoft Entra на [портале Azure].

    • Замените INSERT-RESOURCE-ID-HERE идентификатором клиента для серверной части мобильного приложения. Идентификатор клиента можно получить на вкладке Advanced в разделе Параметры записи Майкрософт на портале.

    • Замените INSERT-CLIENT-ID-HERE идентификатором клиента, скопированным из собственного клиентского приложения.

    • Замените INSERT-REDIRECT-URI-HERE конечной точкой /.auth/login/done сайта с помощью схемы HTTPS. Это значение должно быть похоже на https://contoso.azurewebsites.net/.auth/login/done.

      Следующий код для каждой платформы:

      Windows:

      private MobileServiceUser user;
      private async Task AuthenticateAsync()
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         while (user == null)
         {
             string message;
             try
             {
                 AuthenticationContext ac = new AuthenticationContext(authority);
                 AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                     new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) );
                 JObject payload = new JObject();
                 payload["access_token"] = ar.AccessToken;
                 user = await App.MobileService.LoginAsync(
                     MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
                 message = string.Format("You are now logged in - {0}", user.UserId);
             }
             catch (InvalidOperationException)
             {
                 message = "You must log in. Login Required";
             }
             var dialog = new MessageDialog(message);
             dialog.Commands.Add(new UICommand("OK"));
             await dialog.ShowAsync();
         }
      }
      

      Xamarin.iOS

      private MobileServiceUser user;
      private async Task AuthenticateAsync(UIViewController view)
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         try
         {
             AuthenticationContext ac = new AuthenticationContext(authority);
             AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                 new Uri(redirectUri), new PlatformParameters(view));
             JObject payload = new JObject();
             payload["access_token"] = ar.AccessToken;
             user = await client.LoginAsync(
                 MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
         }
         catch (Exception ex)
         {
             Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message);
         }
      }
      

      Xamarin.Android

      private MobileServiceUser user;
      private async Task AuthenticateAsync()
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         try
         {
             AuthenticationContext ac = new AuthenticationContext(authority);
             AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                 new Uri(redirectUri), new PlatformParameters(this));
             JObject payload = new JObject();
             payload["access_token"] = ar.AccessToken;
             user = await client.LoginAsync(
                 MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
         }
         catch (Exception ex)
         {
             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.SetMessage(ex.Message);
             builder.SetTitle("You must log in. Login Required");
             builder.Create().Show();
         }
      }
      protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
      {
      
         base.OnActivityResult(requestCode, resultCode, data);
         AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
      }
      

Единый вход с помощью токена из Facebook или Google

Вы можете использовать поток клиента, как показано в этом фрагменте кода для Facebook или Google.

var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");

private MobileServiceUser user;
private async Task AuthenticateAsync()
{
    while (user == null)
    {
        string message;
        try
        {
            // Change MobileServiceAuthenticationProvider.Facebook
            // to MobileServiceAuthenticationProvider.Google if using Google auth.
            user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
            message = string.Format("You are now logged in - {0}", user.UserId);
        }
        catch (InvalidOperationException)
        {
            message = "You must log in. Login Required";
        }

        var dialog = new MessageDialog(message);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
}

Управляемая сервером проверка подлинности

После регистрации поставщика удостоверений вызовите метод LoginAsync в MobileServiceClient с помощью MobileServiceAuthenticationProvider значения поставщика. Например, следующий код инициирует вход потока сервера с помощью Facebook.

private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
    while (user == null)
    {
        string message;
        try
        {
            user = await client
                .LoginAsync(MobileServiceAuthenticationProvider.Facebook);
            message =
                string.Format("You are now logged in - {0}", user.UserId);
        }
        catch (InvalidOperationException)
        {
            message = "You must log in. Login Required";
        }

        var dialog = new MessageDialog(message);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
}

Если вы используете поставщик удостоверений, отличный от Facebook, измените значение MobileServiceAuthenticationProvider на значение поставщика.

В потоке сервера служба приложений Azure управляет потоком проверки подлинности OAuth, отображая страницу входа выбранного поставщика. После возврата поставщика удостоверений Служба приложений Azure создает маркер проверки подлинности службы приложений. Метод LoginAsync возвращает MobileServiceUser, который предоставляет идентификатор пользователя, прошедшего проверку подлинности, и MobileServiceAuthenticationToken в виде веб-токена JSON (JWT). Этот маркер можно кэшировать и повторно использовать до истечения срока его действия. Дополнительные сведения см. в разделе Кэширование маркера проверки подлинности.

Заметка

В разделе "Мобильные приложения Azure" для выполнения этой работы используется Xamarin.Essentials WebAuthenticator. Необходимо обработать ответ от службы, вернувшись в Xamarin.Essentials. Дополнительные сведения см. в разделе WebAuthenticator.

Кэширование маркера проверки подлинности

В некоторых случаях вызов метода входа можно избежать после первой успешной проверки подлинности, сохраняя маркер проверки подлинности от поставщика. Приложения Microsoft Store и UWP могут использовать PasswordVault для кэширования текущего маркера проверки подлинности после успешного входа, как показано ниже.

await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);

PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
    client.currentUser.MobileServiceAuthenticationToken));

Значение UserId хранится в качестве имени пользователя учетных данных, а маркер хранится в качестве пароля. В последующих запусках можно проверить PasswordVault для кэшированных учетных данных. В следующем примере используются кэшированные учетные данные при их обнаружении и в противном случае выполняется попытка повторной проверки подлинности с помощью серверной части:

// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
    // Create the current user from the stored credentials.
    client.currentUser = new MobileServiceUser(creds.UserName);
    client.currentUser.MobileServiceAuthenticationToken =
        vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
    // Regular login flow and cache the token as shown above.
}

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

client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));

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

var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");

// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);

Другие темы

Обработка ошибок

При возникновении ошибки в серверной части клиентский пакет SDK вызывает MobileServiceInvalidOperationException. В следующем примере показано, как обрабатывать исключение, возвращаемое серверной частью:

private async void InsertTodoItem(TodoItem todoItem)
{
    // This code inserts a new TodoItem into the database. When the operation completes
    // and App Service has assigned an ID, the item is added to the CollectionView
    try
    {
        await todoTable.InsertAsync(todoItem);
        items.Add(todoItem);
    }
    catch (MobileServiceInvalidOperationException e)
    {
        // Handle error
    }
}

Настройка заголовков запросов

Для поддержки конкретного сценария приложения может потребоваться настроить взаимодействие с серверной частью мобильного приложения. Например, можно добавить пользовательский заголовок в каждый исходящий запрос или даже изменить коды состояния ответов. Вы можете использовать настраиваемый DelegatingHandler, как показано в следующем примере:

public async Task CallClientWithHandler()
{
    MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
    IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
    var newItem = new TodoItem { Text = "Hello world", Complete = false };
    await todoTable.InsertAsync(newItem);
}

public class MyHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage>
        SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Change the request-side here based on the HttpRequestMessage
        request.Headers.Add("x-my-header", "my value");

        // Do the request
        var response = await base.SendAsync(request, cancellationToken);

        // Change the response-side here based on the HttpResponseMessage

        // Return the modified response
        return response;
    }
}

Включение ведения журнала запросов

Вы также можете использовать DelegatingHandler для добавления ведения журнала запросов:

public class LoggingHandler : DelegatingHandler
{
    public LoggingHandler() : base() { }
    public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
    {
        Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
        if (request.Content != null)
        {
            Debug.WriteLine($"[HTTP] >>> {await request.Content.ReadAsStringAsync().ConfigureAwait(false)}");
        }

        HttpResponseMessage response = await base.SendAsync(request, token).ConfigureAwait(false);

        Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
        if (response.Content != null)
        {
            Debug.WriteLine($"[HTTP] <<< {await response.Content.ReadAsStringAsync().ConfigureAwait(false)}");
        }

        return response;
    }
}