Como usar a biblioteca de clientes dos Aplicativos Móveis do Azure v4.2.0 para .NET
Nota
Este produto está desativado. Para obter uma substituição para projetos que usam o .NET 8 ou posterior, consulte a biblioteca datasync do Kit de Ferramentas da Comunidade .
Este guia mostra como executar cenários comuns usando a biblioteca de clientes do .NET para Aplicativos Móveis do Azure. Use a biblioteca de clientes .NET em aplicativos Windows (WPF, UWP) ou Xamarin (nativos ou formulários). Se você for novo nos Aplicativos Móveis do Azure, considere primeiro concluir o guia de início rápido para o Xamarin.Forms tutorial.
Aviso
Este artigo aborda informações sobre 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 para a versão mais recente do
Plataformas com suporte
A biblioteca de clientes do .NET dá suporte ao .NET Standard 2.0 e às seguintes plataformas:
- Xamarin.Android do nível de API 19 até o nível 30 da API.
- 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 ele não estiver disponível, você deverá fornecer uma autenticação de "fluxo de cliente". Essa biblioteca de clientes não é adequada para fatores de inspeção ou de formulário de IoT ao usar a autenticação.
Configuração e pré-requisitos
Presumimos que você já criou e publicou seu projeto de back-end dos Aplicativos Móveis do Azure, que inclui pelo menos uma tabela. No código usado neste tópico, a tabela é denominada TodoItem
e tem uma cadeia de caracteres Id
, campos Text
e uma coluna de Complete
booliana. Esta tabela é a mesma tabela criada quando você conclui o início rápido .
O tipo do lado do 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
Para saber como criar tabelas no back-end dos Aplicativos Móveis, consulte o tópico SDK do .NET Server o tópico do SDK do servidor Node.js.
Instalar o pacote do SDK do cliente gerenciado
Clique com o botão direito do mouse no projeto, pressione Gerenciar pacotes NuGet, pesquise 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 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. Os seguintes tópicos são abordados:
- Criar uma referência de tabela
- de dados de consulta
- Filtrar de dados retornados
- Classificar de dados retornados
- Retornar dados em páginas
- Selecionar colunas específicas
- pesquisar um registro por de ID
- executar consultas não tipados
- Inserir de dados
- atualizar de dados
- Excluir de dados
- resolução de conflitos e de simultaneidade otimista
- Associar dados a uma 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 método GetTable da seguinte maneira:
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
O objeto retornado usa o modelo de serialização tipado. Também há suporte para um modelo de serialização não tipado. O exemplo a seguir cria uma referência a uma tabela não tipada:
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
Em consultas não tipados, você deve especificar a cadeia de caracteres de consulta OData subjacente.
Consultar dados de seu aplicativo móvel
Esta seção descreve como emitir consultas para o back-end do Aplicativo Móvel, que inclui a seguinte funcionalidade:
- Filtrar de dados retornados
- Classificar de dados retornados
- Retornar dados em páginas
- Selecionar colunas específicas
- pesquisar um registro por de ID
Nota
Um tamanho de página controlado pelo servidor é imposto para impedir que todas as linhas sejam retornadas. A paginação impede que 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 em 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 Where aplica um predicado de filtragem de linha à consulta na 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 ao back-end usando o software de inspeção de mensagens, como ferramentas de desenvolvedor do 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 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 convertido em uma consulta SQL pelo SDK do Servidor:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
Essa 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 usados de forma intercambiável. A opção anterior, de concatenar vários predicados em uma consulta, é mais compacta e recomendada.
A cláusula Where
dá suporte a operações que serão convertidas no subconjunto OData. As operações incluem:
- Operadores relacionais (
==
,!=
,<
,<=
,>
,>=
), - Operadores aritméticos (
+
,-
,/
,*
,%
), - Precisão de número (
Math.Floor
,Math.Ceiling
), - Funções de cadeia de caracteres (
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 dá suporte, você pode considerar a documentação do OData v3.
Classificar dados retornados
O código a seguir ilustra como classificar dados incluindo uma função OrderBy ou 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 três resultados seguintes. Essa consulta produz a segunda "página" de dados, em que 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 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 interface do usuário comparável para navegar entre páginas.
Nota
Para substituir o limite de 50 linhas em um back-end do 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 Selecionar à 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 encadeando-as. 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();
Pesquisar dados por ID
A função LookupAsync pode ser usada para pesquisar 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 tipados
Ao executar uma consulta usando um objeto de tabela não tipado, 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ê obtém de volta valores JSON que você pode usar como um recipiente de propriedades. Para obter mais informações sobre JToken
e Newtonsoft Json, consulte o site de JSON da Newtonsoft.
Inserir dados
Todos os tipos de cliente devem conter um membro chamado ID, que é, por padrão, uma cadeia de caracteres. Esse de 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 estiver 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 email como uma ID de cadeia de caracteres exclusiva:
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
Os Aplicativos Móveis dão suporte a valores de cadeia de caracteres personalizados exclusivos para a coluna de ID de
- As IDs são geradas sem fazer uma viagem de ida e volta ao banco de dados.
- Os registros são mais fáceis de mesclar de diferentes tabelas ou bancos de dados.
- Os valores de IDs podem se integrar melhor à 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 da 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 do resultado da chamada InsertAsync
. Um ArgumentException
será 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
. A ID a ser passada pode ser obtida do resultado da chamada InsertAsync
. Um MobileServiceInvalidOperationException
é gerado 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 detecção de conflitos, a última gravação substituiria as atualizações anteriores. controle de simultaneidade otimista pressupõe que cada transação pode ser confirmada e, portanto, não usa nenhum bloqueio de recursos. 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 dão suporte ao controle de simultaneidade otimista, acompanhando as alterações em cada item usando a coluna de propriedade do sistema version
definida para cada tabela no back-end do Aplicativo Móvel. Sempre que um registro é atualizado, os Aplicativos Móveis definem a propriedade version
para esse 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 de clientes 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 de servidores do registro. Em seguida, o aplicativo pode usar essas informações para decidir se executará a solicitação de atualização novamente com o valor version
correto do back-end para confirmar as alterações.
Defina uma coluna na classe de tabela para a propriedade do sistema version
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 não tipadas permitem simultaneidade otimista definindo o sinalizador Version
no 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 detectado:
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.
Associar dados a uma interface do usuário do Windows
Esta seção mostra como exibir objetos de dados retornados usando elementos de interface do usuário em um aplicativo do Windows. O código de exemplo a seguir associa-se à origem da lista com uma consulta para itens incompletos. O MobileServiceCollection cria uma coleção de associaçã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 runtime gerenciado dão suporte a uma interface chamada ISupportIncrementalLoading. Essa interface permite que os controles solicitem dados extras quando o usuário rolar. Há suporte interno para essa interface para aplicativos universais do Windows por meio de MobileServiceIncrementalLoadingCollection, que manipula automaticamente 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 nos aplicativos 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 associada a controles de interface do usuário. Esta coleção tem reconhecimento de paginação. Como a coleção 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 as diretrizes na seção anterior "Selecionar colunas específicas" para selecionar colunas específicas a serem exibidas na interface do usuário.
Alterar o tamanho da página
Os Aplicativos Móveis do Azure retornam no máximo 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 a PageSize
igual ou maior que 100 no 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 repositório de 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, pesquise e instale o pacote Microsoft.Azure.Mobile.Client.SQLiteStore NuGet para todos os projetos na solução.
- Para dispositivos Windows, pressione References>Add Reference..., expanda a pasta windows>Extensions, em seguida, habilite o SQLite apropriado para Windows SDK juntamente com o Visual C++ 2013 Runtime para 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 repositório local deve estar 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 do repositório 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 compatíveis. Se o caminho for um caminho totalmente qualificado (ou seja, ele começa com uma barra), esse caminho será usado. Se o caminho não for 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>();
Você não precisa se autenticar para usar uma tabela offline. Você só precisa se 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 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 um PushAsync()
implícito antes de efetuar pull de registros.
O tratamento de conflitos ocorre em um método PullAsync()
. Você pode lidar com conflitos da mesma maneira que tabelas 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. Manipule 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 mensagens, incluindo ler e definir cabeçalhos de mensagens HTTP e definir um formato de corpo de mensagem diferente de JSON.
Você chama uma API personalizada chamando um dos métodos InvokeApiAsync
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
Esse formulário é uma chamada de método tipado e requer que o MarkAllResult tipo de retorno seja definido. Há suporte para métodos digitados e não tipados.
O método InvokeApiAsync() anexa '/api/' à 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 os WebAPIs que não estão definidos 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
Os Aplicativos Móveis dão suporte à autenticação e autorização de usuários de aplicativos usando vários provedores de identidade externos: Facebook, Google, Conta da Microsoft, Twitter e ID do Microsoft Entra. Você pode definir permissões em tabelas para restringir o acesso a operações específicas somente para 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: fluxo de gerenciado pelo cliente e 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 específicos do provedor.
Nota
É recomendável usar 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 no back-end para habilitar a autenticação/autorização do Serviço de Aplicativo do Azure.
Os tópicos a seguir são abordados nesta seção:
- de autenticação gerenciada pelo cliente
- de autenticação gerenciada pelo servidor
- armazenar em cache o token de autenticação
Autenticação gerenciada pelo cliente
Seu aplicativo pode entrar em contato independentemente 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 do cliente é preferencial para usar um fluxo de servidor, pois o SDK do provedor de identidade fornece uma sensação de UX mais nativa e permite mais personalização.
Exemplos são fornecidos para os seguintes padrões de autenticação de fluxo de cliente:
- biblioteca de autenticação do Active Directory
- Facebook ou Google
Autenticar usuários com a Biblioteca de Autenticação do Active Directory
Você pode usar a ADAL (Biblioteca de Autenticação do Active Directory) para iniciar a autenticação de usuário do cliente usando a autenticação do Microsoft Entra.
Aviso
O suporte para a ADAL (Biblioteca de Autenticação do Active Directory) terminará em dezembro de 2022. Os aplicativos que usam a ADAL em versões do sistema operacional existentes continuarão funcionando, mas as atualizações técnicas de suporte e segurança terminarão. Para obter mais informações, consulte Migrar aplicativos para a MSAL.
Configure o back-end do aplicativo móvel para logon do Microsoft Entra seguindo o tutorial Como configurar o logon do Serviço de Aplicativo para Active Directory tutorial. Conclua a etapa opcional de registro de 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 código a seguir ao seu aplicativo, de acordo com a plataforma que você está usando. 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 pela ID do cliente do back-end do aplicativo móvel. Você pode obter a ID do cliente na guia Advanced em configurações do Microsoft Entra no portal.
Substitua INSERT-CLIENT-ID-HERE pela ID do cliente copiada do aplicativo cliente nativo.
Substitua INSERT-REDIRECT-URI-HERE pelo ponto de extremidade
/.auth/login/done
do site usando o esquema HTTPS. Esse valor deve ser semelhante ahttps://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(); } }
do 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 google
Você pode usar o fluxo do cliente, conforme mostrado neste snippet 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 a UserId do usuário autenticado e o MobileServiceAuthenticationToken, como um JWT (token Web JSON). Esse token pode ser armazenado em cache e reutilizado até expirar. Para obter mais informações, consulte Armazenar em cache o token de autenticação.
Nota
Nas capas, os Aplicativos Móveis do Azure usam um Xamarin.Essentials WebAuthenticator para fazer o trabalho. Você deve lidar com a resposta do serviço chamando de volta para o Xamarin.Essentials. Para obter detalhes, consulte WebAuthenticator.
Armazenando o token de autenticação em cache
Em alguns casos, a chamada para o método de logon 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 Nome de Usuário da credencial e o token é armazenado como a Senha. Nas start-ups subsequentes, você pode verificar o PasswordVault em busca de credenciais armazenadas em cache. O exemplo a seguir usa credenciais armazenadas em cache quando elas são encontradas e, caso contrário, tenta se 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 desconectar 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);
Tópicos diversos
Manipular erros
Quando ocorre um erro no back-end, o SDK do cliente gera um MobileServiceInvalidOperationException
. O exemplo a seguir mostra como lidar com uma exceção 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 de aplicativo específico, talvez seja necessário personalizar a comunicação com o back-end do Aplicativo Móvel. Por exemplo, talvez você queira adicionar um cabeçalho personalizado a cada solicitação de saída ou até mesmo alterar códigos de status de respostas. Você pode usar umDelegatingHandler
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;
}
}
Habilitar registro em log de solicitação
Você também pode usar um DelegatingHandler para adicionar o registro em log de solicitações:
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;
}
}