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
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 Id
e 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 de referência de tabela
- Consultar dados
- Filtrar dados retornados
- Classificar dados retornados
- Retornar dados em páginas
- Selecionar colunas específicas
- Procurar um registo por ID
- Executar consultas não tipadas
- Inserir dados
- Atualizar dados
- Excluir dados
- Resolução de conflitos e simultaneidade otimista
- Vincular dados a um de interface do usuário do Windows
- Alterar o tamanho da página
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:
- Filtrar dados retornados
- Classificar dados retornados
- Retornar dados em páginas
- Selecionar colunas específicas
- Procurar um registo por ID
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
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
// 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
- 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
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:
- de autenticação gerenciada pelo cliente
- de autenticação gerenciada pelo servidor
- Armazenando em cache o token de autenticaçã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.
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.
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.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 aohttps://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
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;
}
}