Partilhar via


Como usar a biblioteca de cliente dos Aplicativos Móveis do Azure v4.2.0 para .NET

Observação

Este produto foi retirado. Para obter uma substituição para projetos que usam o .NET 8 ou posterior, consulte a biblioteca Community Toolkit Datasync.

Este guia mostra como executar cenários comuns usando a biblioteca de cliente .NET para Aplicativos Móveis do Azure. Use a biblioteca de cliente .NET em aplicativos Windows (WPF, UWP) ou Xamarin (nativos ou formulários). Se você é novo nos Aplicativos Móveis do Azure, considere primeiro concluir o tutorial Guia de início rápido para Xamarin.Forms.

Advertência

Este artigo aborda informações para a versão da biblioteca v4.2.0, que é substituída pela biblioteca v5.0.0. Para obter as informações mais atualizadas, consulte o artigo da versão mais recente

Plataformas suportadas

A biblioteca de cliente .NET suporta o .NET Standard 2.0 e as seguintes plataformas:

  • Xamarin.Android do nível de API 19 até o nível de API 30.
  • Xamarin.iOS versão 8.0 a 14.3.
  • A Plataforma Universal do Windows compila 16299 e superior.
  • Qualquer aplicativo .NET Standard 2.0.

A autenticação "fluxo de servidor" usa um WebView para a interface do usuário apresentada e pode não estar disponível em todas as plataformas. Se não estiver disponível, você deve fornecer uma autenticação de "fluxo de cliente". Esta biblioteca de cliente não é adequada para fatores de forma de observação ou IoT ao usar autenticação.

Configuração e pré-requisitos

Supomos que você já tenha criado e publicado seu projeto de back-end de Aplicativos Móveis do Azure, que inclui pelo menos uma tabela. No código usado neste tópico, a tabela é chamada TodoItem e tem uma cadeia de caracteres Ide campos Text e uma coluna Complete booleana. Esta tabela é a mesma tabela criada quando você conclui o de início rápido.

O tipo de cliente digitado correspondente em C# é esta classe:

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

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

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

O JsonPropertyAttribute é usado para definir o mapeamento de PropertyName entre o campo cliente e o campo de tabela.

Para saber como criar tabelas em seu back-end de Aplicativos Móveis, consulte o tópico SDK do .NET Server o tópico SDK do Node.js Server.

Instalar o pacote SDK do cliente gerenciado

Clique com o botão direito do mouse em seu projeto, pressione Gerenciar pacotes NuGet, procure o pacote Microsoft.Azure.Mobile.Client e pressione Instalar. Para recursos offline, instale o pacote Microsoft.Azure.Mobile.Client.SQLiteStore também.

Criar o cliente de Aplicativos Móveis do Azure

O código a seguir cria o objeto MobileServiceClient que é usado para acessar o back-end do aplicativo móvel.

var client = new MobileServiceClient("MOBILE_APP_URL");

No código anterior, substitua MOBILE_APP_URL pela URL do back-end do Serviço de Aplicativo. O objeto MobileServiceClient deve ser um singleton.

Trabalhar com tabelas

A seção a seguir detalha como pesquisar e recuperar registros e modificar os dados dentro da tabela. São abordados os seguintes tópicos:

Criar uma referência de tabela

Todo o código que acessa ou modifica dados em uma tabela de back-end chama funções no objeto MobileServiceTable. Obtenha uma referência à tabela chamando o GetTable método, da seguinte maneira:

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

O objeto retornado usa o modelo de serialização tipado. Um modelo de serialização sem tipo também é suportado. O exemplo a seguir cria uma referência a uma tabela sem tipo:

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

Em consultas sem tipo, você deve especificar a cadeia de caracteres de consulta OData subjacente.

Consultar dados a partir da sua aplicação móvel

Esta seção descreve como emitir consultas para o back-end do aplicativo móvel, que inclui a seguinte funcionalidade:

Observação

Um tamanho de página controlado por servidor é imposto para impedir que todas as linhas sejam retornadas. A paginação impede que as solicitações padrão para grandes conjuntos de dados afetem negativamente o serviço. Para retornar mais de 50 linhas, use o método Skip e Take, conforme descrito em Retornar dados nas páginas.

Filtrar dados retornados

O código a seguir ilustra como filtrar dados incluindo uma cláusula Where em uma consulta. Ele retorna todos os itens de todoTable cuja propriedade Complete é igual a false. A função Onde aplica um predicado de filtragem de linha à consulta em relação à tabela.

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

Você pode exibir o URI da solicitação enviada para o back-end usando o software de inspeção de mensagens, como ferramentas de desenvolvedor de navegador ou Fiddler. Se você examinar o URI da solicitação, observe que a cadeia de caracteres de consulta foi modificada:

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

Essa solicitação OData é convertida em uma consulta SQL pelo SDK do servidor:

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

A função que é passada para o método Where pode ter um número arbitrário de condições.

// 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();

Este exemplo seria traduzido em uma consulta SQL pelo SDK do servidor:

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

Esta consulta também pode ser dividida em várias cláusulas:

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

Os dois métodos são equivalentes e podem ser utilizados indistintamente. A primeira opção — de concatenar vários predicados em uma consulta — é mais compacta e recomendada.

A cláusula Where suporta operações que são traduzidas para o subconjunto OData. As operações incluem:

  • Operadores relacionais (==, !=, <, <=, >, >=),
  • Operadores aritméticos (+, -, /, *, %),
  • Precisão numérica (Math.Floor, Math.Ceiling),
  • Funções string (Length, Substring, Replace, IndexOf, StartsWith, EndsWith),
  • Propriedades de data (Year, Month, Day, Hour, Minute, Second),
  • Acessar propriedades de um objeto e
  • Expressões que combinam qualquer uma dessas operações.

Ao considerar o que o SDK do servidor suporta, você pode considerar o Documentação do OData v3.

Classificar dados retornados

O código a seguir ilustra como classificar dados incluindo uma OrderBy ou função de OrderByDescending na consulta. Ele retorna itens de todoTable classificados em ordem crescente pelo campo 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();

Retornar dados em páginas

Por padrão, o back-end retorna apenas as primeiras 50 linhas. Você pode aumentar o número de linhas retornadas chamando o método Take. Use Take juntamente com o método Skip para solicitar uma "página" específica do conjunto de dados total retornado pela consulta. A consulta a seguir, quando executada, retorna os três principais itens da tabela.

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

A consulta revisada a seguir ignora os três primeiros resultados e retorna os próximos três resultados. Esta consulta produz a segunda "página" de dados, onde o tamanho da página é de três itens.

// 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();

O método IncludeTotalCount solicita a contagem total para todos os registros que teriam sido retornados, ignorando qualquer cláusula de paginação/limite especificada:

query = query.IncludeTotalCount();

Em um aplicativo do mundo real, você pode usar consultas semelhantes ao exemplo anterior com um controle de pager ou uma interface do usuário comparável para navegar entre páginas.

Observação

Para substituir o limite de 50 linhas em um back-end de aplicativo móvel, você também deve aplicar o EnableQueryAttribute ao método GET público e especificar o comportamento de paginação. Quando aplicado ao método, o seguinte define o máximo de linhas retornadas como 1000:

[EnableQuery(MaxTop=1000)]

Selecionar colunas específicas

Você pode especificar qual conjunto de propriedades incluir nos resultados adicionando uma cláusula Select à sua consulta. Por exemplo, o código a seguir mostra como selecionar apenas um campo e também como selecionar e formatar vários campos:

// 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();

Todas as funções descritas até agora são aditivas, para que possamos continuar a encadeá-las. Cada chamada encadeada afeta mais da consulta. Mais um exemplo:

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

Procurar dados por ID

A função LookupAsync pode ser usada para procurar objetos do banco de dados com uma ID específica.

// 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");

Executar consultas não tipadas

Ao executar uma consulta usando um objeto de tabela sem tipo, você deve especificar explicitamente a cadeia de caracteres de consulta OData chamando ReadAsync, como no exemplo a seguir:

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

Você recebe de volta valores JSON que você pode usar como um saco de propriedades. Para obter mais informações sobre JToken e Newtonsoft Json, consulte o site Newtonsoft JSON.

Inserir dados

Todos os tipos de cliente devem conter um membro chamado Id, que é, por padrão, uma cadeia de caracteres. Este Id é necessário para executar operações CRUD e para sincronização offline. O código a seguir ilustra como usar o método InsertAsync para inserir novas linhas em uma tabela. O parâmetro contém os dados a serem inseridos como um objeto .NET.

await todoTable.InsertAsync(todoItem);

Se um valor de ID personalizado exclusivo não for incluído no todoItem durante uma inserção, um GUID será gerado pelo servidor. Você pode recuperar a ID gerada inspecionando o objeto após o retorno da chamada.

Para inserir dados não tipados, você pode aproveitar Json.NET:

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

Aqui está um exemplo usando um endereço de e-mail como um ID de cadeia de caracteres exclusivo:

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);

Trabalhando com valores de ID

As Aplicações Móveis suportam valores de cadeia de caracteres personalizados exclusivos para a coluna ID da tabela. Um valor de cadeia de caracteres permite que os aplicativos usem valores personalizados, como endereços de e-mail ou nomes de usuário para a ID. Os IDs de cadeia de caracteres oferecem os seguintes benefícios:

  • Os IDs são gerados sem fazer uma viagem de ida e volta ao banco de dados.
  • Os registros são mais fáceis de mesclar a partir de diferentes tabelas ou bancos de dados.
  • Os valores de IDs podem se integrar melhor com a lógica de um aplicativo.

Quando um valor de ID de cadeia de caracteres não é definido em um registro inserido, o back-end do Aplicativo Móvel gera um valor exclusivo para a ID. Você pode usar o método Guid.NewGuid para gerar seus próprios valores de ID, no cliente ou no back-end.

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

Atualizar dados

O código a seguir ilustra como usar o método UpdateAsync para atualizar um registro existente com a mesma ID com novas informações. O parâmetro contém os dados a serem atualizados como um objeto .NET.

await todoTable.UpdateAsync(todoItem);

Para atualizar dados não tipados, você pode aproveitar JSON Newtonsoft da seguinte maneira:

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);

Um campo id deve ser especificado ao fazer uma atualização. O back-end usa o campo id para identificar qual linha atualizar. O campo id pode ser obtido a partir do resultado da chamada InsertAsync. Um ArgumentException é gerado se você tentar atualizar um item sem fornecer o valor id.

Excluir dados

O código a seguir ilustra como usar o método DeleteAsync para excluir uma instância existente. A instância é identificada pelo campo id definido no todoItem.

await todoTable.DeleteAsync(todoItem);

Para excluir dados não tipados, você pode aproveitar Json.NET da seguinte maneira:

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

Quando você faz uma solicitação de exclusão, uma ID deve ser especificada. Outras propriedades não são passadas para o serviço ou são ignoradas no serviço. O resultado de uma chamada DeleteAsync é geralmente null. O ID a transmitir pode ser obtido a partir do resultado da chamada InsertAsync. Uma MobileServiceInvalidOperationException é lançada quando você tenta excluir um item sem especificar o campo id.

Resolução de conflitos e simultaneidade otimista

Dois ou mais clientes podem gravar alterações no mesmo item ao mesmo tempo. Sem a deteção de conflitos, a última gravação substituiria quaisquer atualizações anteriores. Controle de simultaneidade otimista pressupõe que cada transação pode ser confirmada e, portanto, não usa nenhum bloqueio de recurso. Antes de confirmar uma transação, o controle de simultaneidade otimista verifica se nenhuma outra transação modificou os dados. Se os dados tiverem sido modificados, a transação de confirmação será revertida.

Os Aplicativos Móveis oferecem suporte ao controle de simultaneidade otimista rastreando as alterações em cada item usando a coluna de propriedades do sistema version definida para cada tabela no back-end do Aplicativo Móvel. Cada vez que um registro é atualizado, os Aplicativos Móveis definem a propriedade version desse registro como um novo valor. Durante cada solicitação de atualização, a propriedade version do registro incluído com a solicitação é comparada à mesma propriedade do registro no servidor. Se a versão passada com a solicitação não corresponder ao back-end, a biblioteca do cliente gerará uma exceção MobileServicePreconditionFailedException<T>. O tipo incluído com a exceção é o registro do back-end que contém a versão do servidor do registro. O aplicativo pode usar essas informações para decidir se deseja executar a solicitação de atualização novamente com o valor de version correto do back-end para confirmar alterações.

Defina uma coluna na classe table para a propriedade version system para habilitar a simultaneidade otimista. Por exemplo:

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; }
}

Os aplicativos que usam tabelas sem tipo permitem simultaneidade otimista definindo o sinalizador Version na SystemProperties da tabela da seguinte maneira.

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

Além de habilitar a simultaneidade otimista, você também deve capturar a exceção MobileServicePreconditionFailedException<T> em seu código ao chamar UpdateAsync. Resolva o conflito aplicando o version correto ao registro atualizado e chame UpdateAsync com o registro resolvido. O código a seguir mostra como resolver um conflito de gravação uma vez detetado:

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();
}

Para obter mais informações, consulte o tópico Sincronização de Dados Offline nos Aplicativos Móveis do Azure.

Vincular dados a uma interface de usuário do Windows

Esta seção mostra como exibir objetos de dados retornados usando elementos da interface do usuário em um aplicativo do Windows. O código de exemplo a seguir se liga à fonte da lista com uma consulta para itens incompletos. O MobileServiceCollection cria uma coleção de vinculação com reconhecimento de aplicativos móveis.

// 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;

Alguns controles no tempo de execução gerenciado suportam uma interface chamada ISupportIncrementalLoading. Essa interface permite que os controles solicitem dados extras quando o usuário rola. Há suporte interno para essa interface para aplicativos universais do Windows por meio do MobileServiceIncrementalLoadingCollection, que lida automaticamente com as chamadas dos controles. Use MobileServiceIncrementalLoadingCollection em aplicativos do Windows da seguinte maneira:

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

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

Para usar a nova coleção em aplicativos do Windows Phone 8 e "Silverlight", use os métodos de extensão ToCollection em IMobileServiceTableQuery<T> e IMobileServiceTable<T>. Para carregar dados, chame LoadMoreItemsAsync().

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

Ao usar a coleção criada chamando ToCollectionAsync ou ToCollection, você obtém uma coleção que pode ser vinculada a controles da interface do usuário. Esta coleção reconhece paginação. Como a coleta está carregando dados da rede, o carregamento às vezes falha. Para lidar com essas falhas, substitua o método OnException em MobileServiceIncrementalLoadingCollection para lidar com exceções resultantes de chamadas para LoadMoreItemsAsync.

Considere se sua tabela tem muitos campos, mas você só deseja exibir alguns deles em seu controle. Você pode usar a orientação na seção anterior "Selecionar colunas específicas" para selecionar colunas específicas para exibir na interface do usuário.

Alterar o tamanho da página

Os Aplicativos Móveis do Azure retornam um máximo de 50 itens por solicitação por padrão. Você pode alterar o tamanho da paginação aumentando o tamanho máximo da página no cliente e no servidor. Para aumentar o tamanho da página solicitada, especifique PullOptions ao usar PullAsync():

PullOptions pullOptions = new PullOptions
    {
        MaxPageSize = 100
    };

Supondo que você tenha feito o PageSize igual ou maior que 100 dentro do servidor, uma solicitação retorna até 100 itens.

Trabalhar com tabelas offline

As tabelas offline usam um repositório SQLite local para armazenar dados para uso quando offline. Todas as operações de tabela são feitas no repositório SQLite local em vez do armazenamento do servidor remoto. Para criar uma tabela offline, primeiro prepare seu projeto.

  • No Visual Studio, clique com o botão direito do mouse na solução >Gerenciar pacotes NuGet para solução...e, em seguida, procure e instale o Microsoft.Azure.Mobile.Client.SQLiteStore pacote NuGet para todos os projetos na solução.
  • Para dispositivos Windows, pressione Referências>Adicionar Referência..., expanda a pasta Windows>Extensõese, em seguida, habilite o SDK SQLite for Windows apropriado junto com o Visual C++ 2013 Runtime for Windows SDK. Os nomes do SDK do SQLite variam ligeiramente com cada plataforma Windows.

Antes que uma referência de tabela possa ser criada, o armazenamento local deve ser preparado:

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

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

A inicialização da loja normalmente é feita imediatamente após a criação do cliente. O OfflineDbPath deve ser um nome de arquivo adequado para uso em todas as plataformas suportadas. Se o caminho for um caminho totalmente qualificado (ou seja, ele começa com uma barra), então esse caminho é usado. Se o caminho não estiver totalmente qualificado, o arquivo será colocado em um local específico da plataforma.

  • Para dispositivos iOS e Android, o caminho padrão é a pasta "Arquivos pessoais".
  • Para dispositivos Windows, o caminho padrão é a pasta "AppData" específica do aplicativo.

Uma referência de tabela pode ser obtida usando o método GetSyncTable<>:

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

Não é necessário autenticar para usar uma tabela offline. Você só precisa autenticar quando estiver se comunicando com o serviço de back-end.

Sincronizar uma tabela offline

As tabelas offline não são sincronizadas com o back-end por padrão. A sincronização é dividida em duas partes. Você pode enviar as alterações por push separadamente do download de novos itens. Aqui está um método de sincronização típico:

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"]);
        }
    }
}

Se o primeiro argumento a PullAsync for nulo, a sincronização incremental não será usada. Cada operação de sincronização recupera todos os registros.

O SDK executa uma PushAsync() implícita antes de extrair registros.

O tratamento de conflitos acontece de acordo com um método PullAsync(). Pode lidar com conflitos da mesma forma que as mesas online. O conflito é produzido quando PullAsync() é chamado em vez de durante a inserção, atualização ou exclusão. Se vários conflitos acontecerem, eles serão agrupados em um único MobileServicePushFailedException. Lide com cada falha separadamente.

Trabalhar com uma API personalizada

Uma API personalizada permite definir pontos de extremidade personalizados que expõem a funcionalidade do servidor que não é mapeada para uma operação de inserção, atualização, exclusão ou leitura. Usando uma API personalizada, você pode ter mais controle sobre as mensagens, incluindo a leitura e a configuração de cabeçalhos de mensagens HTTP e a definição de um formato de corpo de mensagem diferente de JSON.

Você chama uma API personalizada chamando um dos métodos de InvokeApiAsync no cliente. Por exemplo, a seguinte linha de código envia uma solicitação POST para o completeAll API no back-end:

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

Este formulário é uma chamada de método digitada e requer que o MarkAllResult tipo de retorno esteja definido. Há suporte para métodos digitados e não tipados.

O método InvokeApiAsync() precede '/api/' para a API que você deseja chamar, a menos que a API comece com um '/'. Por exemplo:

  • InvokeApiAsync("completeAll",...) chama /api/completeAll no back-end
  • InvokeApiAsync("/.auth/me",...) chama /.auth/me no back-end

Você pode usar InvokeApiAsync para chamar qualquer WebAPI, incluindo as WebAPIs que não estão definidas com os Aplicativos Móveis do Azure. Quando você usa InvokeApiAsync(), os cabeçalhos apropriados, incluindo cabeçalhos de autenticação, são enviados com a solicitação.

Autenticar usuários

As Aplicações Móveis suportam a autenticação e autorização de utilizadores de aplicações utilizando vários fornecedores de identidade externos: Facebook, Google, Conta Microsoft, Twitter e Microsoft Entra ID. Você pode definir permissões em tabelas para restringir o acesso de operações específicas apenas a usuários autenticados. Você também pode usar a identidade de usuários autenticados para implementar regras de autorização em scripts de servidor.

Há suporte para dois fluxos de autenticação: gerenciado pelo cliente e fluxo de gerenciado pelo servidor. O fluxo gerenciado pelo servidor fornece a experiência de autenticação mais simples, pois depende da interface de autenticação da Web do provedor. O fluxo gerenciado pelo cliente permite uma integração mais profunda com recursos específicos do dispositivo, pois depende de SDKs específicos do dispositivo do provedor.

Observação

Recomendamos o uso de um fluxo gerenciado pelo cliente em seus aplicativos de produção.

Para configurar a autenticação, você deve registrar seu aplicativo com um ou mais provedores de identidade. O provedor de identidade gera uma ID do cliente e um segredo do cliente para seu aplicativo. Esses valores são definidos em seu back-end para habilitar a autenticação/autorização do Serviço de Aplicativo do Azure.

Os seguintes tópicos são abordados nesta seção:

Autenticação gerenciada pelo cliente

Seu aplicativo pode entrar em contato de forma independente com o provedor de identidade e, em seguida, fornecer o token retornado durante a entrada com seu back-end. Esse fluxo de cliente permite que você forneça uma experiência de logon único para os usuários ou recupere dados extras do usuário do provedor de identidade. A autenticação de fluxo de cliente é preferível ao uso de um fluxo de servidor, pois o SDK do provedor de identidade fornece uma sensação de UX mais nativa e permite mais personalização.

São fornecidos exemplos para os seguintes padrões de autenticação de fluxo de cliente:

Autenticar usuários com a Biblioteca de Autenticação do Ative Directory

Você pode usar a Biblioteca de Autenticação do Ative Directory (ADAL) para iniciar a autenticação do usuário do cliente usando a autenticação do Microsoft Entra.

Advertência

O suporte para a Biblioteca de Autenticação do Ative Directory (ADAL) terminará em dezembro de 2022. As aplicações que utilizam a ADAL em versões existentes do SO continuarão a funcionar, mas o suporte técnico e as atualizações de segurança terminarão. Para obter mais informações, consulte Migrar aplicativos para o MSAL.

  1. Configure o back-end do seu aplicativo móvel para o logon do Microsoft Entra seguindo o tutorial Como configurar o de logon do Serviço de Aplicativo para o Ative Directory. Certifique-se de concluir a etapa opcional de registrar um aplicativo cliente nativo.

  2. No Visual Studio, abra seu projeto e adicione uma referência ao pacote NuGet Microsoft.IdentityModel.Clients.ActiveDirectory. Ao pesquisar, inclua versões de pré-lançamento.

  3. Adicione o seguinte código à sua aplicação, de acordo com a plataforma que está a utilizar. Em cada uma delas, faça as seguintes substituições:

    • Substitua INSERT-AUTHORITY-HERE pelo nome do locatário no qual você provisionou seu aplicativo. O formato deve ser https://login.microsoftonline.com/contoso.onmicrosoft.com. Esse valor pode ser copiado da guia Domínio em sua ID do Microsoft Entra no [portal do Azure].

    • Substitua INSERT-RESOURCE-ID-HERE pelo ID do cliente para o back-end do aplicativo móvel. Você pode obter a ID do cliente na guia Avançado em Configurações do Microsoft Entra no portal.

    • Substitua INSERT-CLIENT-ID-HERE pelo ID do cliente copiado do aplicativo cliente nativo.

    • Substitua INSERT-REDIRECT-URI-HERE pelo ponto de extremidade /.auth/login/done do seu site, usando o esquema HTTPS. Este valor deve ser semelhante ao https://contoso.azurewebsites.net/.auth/login/done.

      O código necessário para cada plataforma segue:

      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);
      }
      

Logon único usando um token do Facebook ou do Google

Você pode usar o fluxo de clientes como mostrado neste trecho para Facebook ou 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();
    }
}

Autenticação gerenciada pelo servidor

Depois de registrar seu provedor de identidade, chame o método LoginAsync no MobileServiceClient com o MobileServiceAuthenticationProvider valor do seu provedor. Por exemplo, o código a seguir inicia uma entrada de fluxo de servidor usando o 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();
    }
}

Se você estiver usando um provedor de identidade diferente do Facebook, altere o valor de MobileServiceAuthenticationProvider para o valor do seu provedor.

Em um fluxo de servidor, o Serviço de Aplicativo do Azure gerencia o fluxo de autenticação OAuth exibindo a página de entrada do provedor selecionado. Depois que o provedor de identidade retorna, o Serviço de Aplicativo do Azure gera um token de autenticação do Serviço de Aplicativo. O método LoginAsync retorna um MobileServiceUser, que fornece o UserId do usuário autenticado e o MobileServiceAuthenticationToken, como um token da Web JSON (JWT). Esse token pode ser armazenado em cache e reutilizado até expirar. Para obter mais informações, consulte Caching the authentication token.

Observação

Sob as capas, os Aplicativos Móveis do Azure usam um Xamarin.Essentials WebAuthenticator para fazer o trabalho. Você deve manipular a resposta do serviço chamando de volta para o Xamarin.Essentials. Para obter detalhes, consulte WebAuthenticator.

Armazenando em cache o token de autenticação

Em alguns casos, a chamada para o método de login pode ser evitada após a primeira autenticação bem-sucedida, armazenando o token de autenticação do provedor. Os aplicativos da Microsoft Store e UWP podem usar PasswordVault para armazenar em cache o token de autenticação atual após uma entrada bem-sucedida, da seguinte maneira:

await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);

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

O valor UserId é armazenado como o UserName da credencial e o token é o armazenado como a senha. Em inicializações subsequentes, você pode verificar o PasswordVault para credenciais armazenadas em cache. O exemplo a seguir usa credenciais armazenadas em cache quando elas são encontradas e, caso contrário, tenta autenticar novamente com o back-end:

// 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.
}

Ao sair de um usuário, você também deve remover a credencial armazenada, da seguinte maneira:

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

Ao usar a autenticação gerenciada pelo cliente, você também pode armazenar em cache o token de acesso obtido do seu provedor, como Facebook ou Twitter. Esse token pode ser fornecido para solicitar um novo token de autenticação do back-end, da seguinte maneira:

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);

Temas Diversos

Manipular erros

Quando ocorre um erro no back-end, o SDK do cliente gera um MobileServiceInvalidOperationException. O exemplo a seguir mostra como manipular uma exceção que é retornada pelo back-end:

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
    }
}

Personalizar cabeçalhos de solicitação

Para dar suporte ao cenário específico do aplicativo, talvez seja necessário personalizar a comunicação com o back-end do aplicativo móvel. Por exemplo, você pode adicionar um cabeçalho personalizado a cada solicitação de saída ou até mesmo alterar os códigos de status das respostas. Você pode usar um personalizado DelegatingHandler, como no exemplo a seguir:

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;
    }
}

Ativar o registo de pedidos

Você também pode usar um DelegatingHandler para adicionar log de solicitação:

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;
    }
}