Uso de la biblioteca cliente de Azure Mobile Apps v4.2.0 para .NET
Nota
Este producto se retira. Para obtener un reemplazo de proyectos con .NET 8 o posterior, consulte la biblioteca datasync de Community Toolkit.
En esta guía se muestra cómo realizar escenarios comunes mediante la biblioteca cliente de .NET para Azure Mobile Apps. Use la biblioteca cliente de .NET en aplicaciones de Windows (WPF, UWP) o Xamarin (nativo o forms). Si no está familiarizado con Azure Mobile Apps, considere la posibilidad de completar primero el inicio rápido de para Xamarin.Forms tutorial.
Advertencia
En este artículo se describe la información de la versión de biblioteca v4.2.0, que se superpone a la biblioteca v5.0.0. Para obtener la información más actualizada, consulte el artículo sobre la versión más reciente de
Plataformas admitidas
La biblioteca cliente de .NET admite .NET Standard 2.0 y las plataformas siguientes:
- Xamarin.Android desde el nivel de API 19 hasta el nivel de API 30.
- Xamarin.iOS versión 8.0 a 14.3.
- La Plataforma universal de Windows compila 16299 y versiones posteriores.
- Cualquier aplicación de .NET Standard 2.0.
La autenticación de "flujo de servidor" usa una vista web para la interfaz de usuario presentada y puede que no esté disponible en todas las plataformas. Si no está disponible, debe proporcionar una autenticación de "flujo de cliente". Esta biblioteca cliente no es adecuada para factores de forma de Inspección o IoT al usar la autenticación.
Configuración y requisitos previos
Se supone que ya ha creado y publicado el proyecto back-end de Azure Mobile Apps, que incluye al menos una tabla. En el código usado en este tema, la tabla se denomina TodoItem
y tiene una cadena Id
y Text
campos y una columna de Complete
booleana. Esta tabla es la misma tabla creada al completar el inicio rápido de .
El tipo de cliente con tipo correspondiente en C# es esta clase:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
El jsonPropertyAttribute de
Para obtener información sobre cómo crear tablas en el back-end de Mobile Apps, consulte el tema del SDK de .NET Server de el tema sdk de Node.js Server.
Instalación del paquete del SDK de cliente administrado
Haga clic con el botón derecho en el proyecto, presione Administrar paquetes NuGet, busque el paquete de Microsoft.Azure.Mobile.Client
y presione Instalar. Para las funcionalidades sin conexión, instale también el paquete de Microsoft.Azure.Mobile.Client.SQLiteStore
.
Creación del cliente de Azure Mobile Apps
El código siguiente crea el MobileServiceClient objeto que se usa para acceder al back-end de la aplicación móvil.
var client = new MobileServiceClient("MOBILE_APP_URL");
En el código anterior, reemplace MOBILE_APP_URL
por la dirección URL del back-end de App Service. El objeto MobileServiceClient
debe ser un singleton.
Trabajar con tablas
En la sección siguiente se detalla cómo buscar y recuperar registros y modificar los datos de la tabla. Se tratan los temas siguientes:
- Crear una referencia de tabla
- Consulta de datos
- Filtro de datos devueltos
- ordenar datos devueltos
- Devolver datos en páginas
- Seleccionar columnas específicas
- Buscar un registro por identificador
- Ejecutar consultas sin tipo
- Insertar de datos
- Actualizar de datos
- Eliminar de datos
- resolución de conflictos y de simultaneidad optimista
- Enlazar datos a una interfaz de usuario de Windows
- Cambiar el tamaño de página
Creación de una referencia de tabla
Todo el código que accede a los datos o modifica en una tabla de back-end llama a funciones en el objeto MobileServiceTable
. Obtenga una referencia a la tabla llamando al método GetTable, como se indica a continuación:
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
El objeto devuelto usa el modelo de serialización con tipo. También se admite un modelo de serialización sin tipo. En el ejemplo siguiente se crea una referencia a una tabla sin tipo:
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
En las consultas sin tipo, debe especificar la cadena de consulta OData subyacente.
Consulta de datos desde la aplicación móvil
En esta sección se describe cómo emitir consultas al back-end de aplicación móvil, que incluye la siguiente funcionalidad:
- Filtro de datos devueltos
- ordenar datos devueltos
- Devolver datos en páginas
- Seleccionar columnas específicas
- Buscar un registro por identificador
Nota
Se aplica un tamaño de página controlado por servidor para evitar que se devuelvan todas las filas. La paginación mantiene las solicitudes predeterminadas para grandes conjuntos de datos que afectan negativamente al servicio. Para devolver más de 50 filas, use el método Skip
y Take
, como se describe en Datos devueltos en páginas.
Filtrar los datos devueltos
En el código siguiente se muestra cómo filtrar los datos mediante la inclusión de una cláusula Where
en una consulta. Devuelve todos los elementos de todoTable
cuya propiedad Complete
es igual a false
. El función Where aplica un predicado de filtrado de filas a la consulta en la tabla.
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
Puede ver el URI de la solicitud enviada al back-end mediante el software de inspección de mensajes, como las herramientas de desarrollo del explorador o Fiddler. Si observa el URI de solicitud, observe que se modifica la cadena de consulta:
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
Esta solicitud de OData se traduce en una consulta SQL mediante el SDK de Server:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
La función que se pasa al método Where
puede tener un número arbitrario de condiciones.
// 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 ejemplo se traduciría en una consulta SQL mediante el SDK de Server:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
Esta consulta también se puede dividir en varias cláusulas:
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
Los dos métodos son equivalentes y se pueden usar indistintamente. La primera opción ( de concatenar varios predicados en una consulta) es más compacta y recomendada.
La cláusula Where
admite operaciones que se traducen en el subconjunto de OData. Las operaciones incluyen:
- Operadores relacionales (
==
,!=
,<
,<=
,>
,>=
), - Operadores aritméticos (
+
,-
,/
,*
,%
), - Precisión del número (
Math.Floor
,Math.Ceiling
), - Funciones de cadena (
Length
,Substring
,Replace
,IndexOf
,StartsWith
,EndsWith
), - Propiedades date (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Obtener acceso a las propiedades de un objeto y
- Expresiones que combinan cualquiera de estas operaciones.
Al considerar lo que admite el SDK de servidor, puede considerar la documentación de OData v3 de .
Ordenar los datos devueltos
En el código siguiente se muestra cómo ordenar los datos mediante la inclusión de una OrderBy o función OrderByDescending en la consulta. Devuelve elementos de todoTable
ordenados ascendentemente por el 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();
Devolver datos en páginas
De forma predeterminada, el back-end devuelve solo las primeras 50 filas. Puede aumentar el número de filas devueltas llamando al método Take. Use
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
La siguiente consulta revisada omite los tres primeros resultados y devuelve los tres resultados siguientes. Esta consulta genera la segunda "página" de datos, donde el tamaño de página es tres elementos.
// 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();
El método IncludeTotalCount solicita el recuento total de todos los los registros que se habrían devuelto, ignorando cualquier cláusula de paginación y límite especificada:
query = query.IncludeTotalCount();
En una aplicación real, puede usar consultas similares al ejemplo anterior con un control de buscapersonas o una interfaz de usuario comparable para navegar entre páginas.
Nota
Para invalidar el límite de 50 filas en un back-end de aplicación móvil, también debe aplicar el enableQueryAttribute al método GET público y especificar el comportamiento de paginación. Cuando se aplica al método , lo siguiente establece el número máximo de filas devueltas en 1000:
[EnableQuery(MaxTop=1000)]
Selección de columnas específicas
Puede especificar qué conjunto de propiedades se van a incluir en los resultados agregando una cláusula Select
// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();
// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => string.Format("{0} -- {1}",
todoItem.Text.PadRight(30), todoItem.Complete ?
"Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();
Todas las funciones descritas hasta ahora son sumas, por lo que podemos encadenarlas. Cada llamada encadenada afecta a más de la consulta. Un ejemplo más:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Buscar datos por identificador
La función LookupAsync se puede usar para buscar objetos de la base de datos con un identificador determinado.
// 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");
Ejecución de consultas sin tipo
Al ejecutar una consulta mediante un objeto de tabla sin tipo, debe especificar explícitamente la cadena de consulta OData llamando a ReadAsync, como en el ejemplo siguiente:
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
Obtendrá valores JSON que puede usar como un contenedor de propiedades. Para obtener más información sobre
Insertar datos
Todos los tipos de cliente deben contener un miembro denominado Id, que es de forma predeterminada una cadena. Este id. es necesario para realizar operaciones CRUD y para la sincronización sin conexión. En el código siguiente se muestra cómo usar el método InsertAsync para insertar nuevas filas en una tabla. El parámetro contiene los datos que se van a insertar como un objeto .NET.
await todoTable.InsertAsync(todoItem);
Si no se incluye un valor de identificador personalizado único en el todoItem
durante una inserción, el servidor genera un GUID. Puede recuperar el identificador generado inspeccionando el objeto después de que se devuelva la llamada.
Para insertar datos sin tipo, puede aprovechar las ventajas de Json.NET:
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
Este es un ejemplo de uso de una dirección de correo electrónico como identificador de cadena único:
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);
Trabajar con valores de identificador
Mobile Apps admite valores de cadena personalizados únicos para la columna id. de
- Los identificadores se generan sin realizar un recorrido de ida y vuelta a la base de datos.
- Los registros son más fáciles de combinar de diferentes tablas o bases de datos.
- Los identificadores pueden integrarse mejor con la lógica de una aplicación.
Cuando no se establece un valor de identificador de cadena en un registro insertado, el back-end de la aplicación móvil genera un valor único para el identificador. Puede usar el método Guid.NewGuid para generar sus propios valores de identificador, ya sea en el cliente o en el back-end.
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
Actualizar datos
En el código siguiente se muestra cómo usar el método updateAsync de
await todoTable.UpdateAsync(todoItem);
Para actualizar los datos sin tipo, puede aprovechar las ventajas de Newtonsoft JSON como se indica a continuación:
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);
Se debe especificar un campo id
al realizar una actualización. El back-end usa el campo id
para identificar qué fila se va a actualizar. El campo id
se puede obtener del resultado de la llamada InsertAsync
. Se genera un ArgumentException
si intenta actualizar un elemento sin proporcionar el valor id
.
Eliminar datos
El código siguiente muestra cómo usar el método DeleteAsync para eliminar una instancia existente. La instancia se identifica mediante el campo id
establecido en el todoItem
.
await todoTable.DeleteAsync(todoItem);
Para eliminar datos sin tipo, puede aprovechar las ventajas de Json.NET de la siguiente manera:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
Al realizar una solicitud de eliminación, se debe especificar un identificador. Otras propiedades no se pasan al servicio o se omiten en el servicio. El resultado de una llamada a DeleteAsync
suele ser null
. El identificador que se va a pasar se puede obtener del resultado de la llamada a InsertAsync
. Se produce un MobileServiceInvalidOperationException
al intentar eliminar un elemento sin especificar el campo id
.
Resolución de conflictos y simultaneidad optimista
Dos o más clientes pueden escribir cambios en el mismo elemento al mismo tiempo. Sin la detección de conflictos, la última escritura sobrescribiría las actualizaciones anteriores. control de simultaneidad optimista supone que cada transacción puede confirmarse y, por tanto, no usa ningún bloqueo de recursos. Antes de confirmar una transacción, el control de simultaneidad optimista comprueba que ninguna otra transacción ha modificado los datos. Si se han modificado los datos, la transacción de confirmación se revierte.
Mobile Apps admite el control de simultaneidad optimista mediante el seguimiento de los cambios en cada elemento mediante la columna de propiedades del sistema version
que se define para cada tabla del back-end de la aplicación móvil. Cada vez que se actualiza un registro, Mobile Apps establece la propiedad version
para ese registro en un nuevo valor. Durante cada solicitud de actualización, la propiedad version
del registro incluido con la solicitud se compara con la misma propiedad para el registro en el servidor. Si la versión pasada con la solicitud no coincide con el back-end, la biblioteca cliente genera una excepción de MobileServicePreconditionFailedException<T>
. El tipo incluido con la excepción es el registro del back-end que contiene la versión de los servidores del registro. Después, la aplicación puede usar esta información para decidir si debe ejecutar la solicitud de actualización de nuevo con el valor de version
correcto del back-end para confirmar los cambios.
Defina una columna en la clase de tabla para la propiedad del sistema version
para habilitar la simultaneidad optimista. Por ejemplo:
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; }
}
Las aplicaciones que usan tablas sin tipo permiten la simultaneidad optimista estableciendo la marca Version
en la SystemProperties
de la tabla de la siguiente manera.
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
Además de habilitar la simultaneidad optimista, también debe detectar la excepción de MobileServicePreconditionFailedException<T>
en el código al llamar a UpdateAsync. Resuelva el conflicto aplicando el version
correcto al registro actualizado y llame a UpdateAsync con el registro resuelto. El código siguiente muestra cómo resolver un conflicto de escritura una 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 más información, consulte el tema Sincronización de datos sin conexión en Azure Mobile Apps.
Enlazar datos a una interfaz de usuario de Windows
En esta sección se muestra cómo mostrar objetos de datos devueltos mediante elementos de interfaz de usuario en una aplicación de Windows. El código de ejemplo siguiente se enlaza al origen de la lista con una consulta para elementos incompletos. El
// 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;
Algunos controles del entorno de ejecución administrado admiten una interfaz denominada ISupportIncrementalLoading. Esta interfaz permite a los controles solicitar datos adicionales cuando el usuario se desplaza. Hay compatibilidad integrada con esta interfaz para aplicaciones universales de Windows a través de MobileServiceIncrementalLoadingCollection, que controla automáticamente las llamadas desde los controles. Use MobileServiceIncrementalLoadingCollection
en aplicaciones de Windows de la siguiente manera:
MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();
ListBox lb = new ListBox();
lb.ItemsSource = items;
Para usar la nueva colección en aplicaciones Windows Phone 8 y "Silverlight", use los métodos de extensión ToCollection
en IMobileServiceTableQuery<T>
y IMobileServiceTable<T>
. Para cargar datos, llame a LoadMoreItemsAsync()
.
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
Cuando se usa la colección creada llamando a ToCollectionAsync
o ToCollection
, se obtiene una colección que se puede enlazar a controles de interfaz de usuario. Esta colección es compatible con la paginación. Dado que la recopilación carga datos desde la red, a veces se produce un error en la carga. Para controlar estos errores, invalide el método OnException
en MobileServiceIncrementalLoadingCollection
para controlar las excepciones resultantes de llamadas a LoadMoreItemsAsync
.
Considere si la tabla tiene muchos campos, pero solo desea mostrar algunos de ellos en el control. Puede usar las instrucciones de la sección anterior "Seleccionar columnas específicas" para seleccionar columnas específicas que se van a mostrar en la interfaz de usuario.
Cambiar el tamaño de página
Azure Mobile Apps devuelve un máximo de 50 elementos por solicitud de forma predeterminada. Puede cambiar el tamaño de paginación aumentando el tamaño máximo de página tanto en el cliente como en el servidor. Para aumentar el tamaño de página solicitado, especifique PullOptions
al usar PullAsync()
:
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
Suponiendo que haya realizado el PageSize
igual o mayor que 100 dentro del servidor, una solicitud devuelve hasta 100 elementos.
Trabajar con tablas sin conexión
Las tablas sin conexión usan un almacén de SQLite local para almacenar datos para usarlos cuando están sin conexión. Todas las operaciones de tabla se realizan en el almacén de SQLite local en lugar del almacén del servidor remoto. Para crear una tabla sin conexión, prepare primero el proyecto.
- En Visual Studio, haga clic con el botón derecho en la solución >Administrar paquetes NuGet para la solución...y busque e instale el paquete Microsoft.Azure.Mobile.Client.SQLiteStore NuGet para todos los proyectos de la solución.
- Para dispositivos Windows, presione Referencias>Agregar referencia..., expanda la carpeta Windows>Extensionesy, a continuación, habilite el SDK SQLite para Windows junto con el SDK Visual C++ 2013 Runtime para Windows SDK. Los nombres del SDK de SQLite varían ligeramente con cada plataforma windows.
Para poder crear una referencia de tabla, el almacén local debe estar preparado:
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
Normalmente, la inicialización de la tienda se realiza inmediatamente después de crear el cliente. El OfflineDbPath debe ser un nombre de archivo adecuado para su uso en todas las plataformas compatibles. Si la ruta de acceso es una ruta de acceso completa (es decir, comienza con una barra diagonal), se usa esa ruta de acceso. Si la ruta de acceso no está completa, el archivo se coloca en una ubicación específica de la plataforma.
- Para dispositivos iOS y Android, la ruta de acceso predeterminada es la carpeta "Archivos personales".
- En el caso de los dispositivos Windows, la ruta de acceso predeterminada es la carpeta "AppData" específica de la aplicación.
Se puede obtener una referencia de tabla mediante el método GetSyncTable<>
:
var table = client.GetSyncTable<TodoItem>();
No es necesario autenticarse para usar una tabla sin conexión. Solo tiene que autenticarse cuando se comunica con el servicio back-end.
Sincronizar una tabla sin conexión
Las tablas sin conexión no se sincronizan con el back-end de forma predeterminada. La sincronización se divide en dos partes. Puede insertar cambios por separado de la descarga de nuevos elementos. Este es un método de sincronización 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"]);
}
}
}
Si el primer argumento para PullAsync
es NULL, no se usa la sincronización incremental. Cada operación de sincronización recupera todos los registros.
El SDK realiza una PushAsync()
implícita antes de extraer registros.
El control de conflictos se produce en un método PullAsync()
. Puede tratar los conflictos de la misma manera que las tablas en línea. El conflicto se produce cuando se llama a PullAsync()
en lugar de durante la inserción, actualización o eliminación. Si se producen varios conflictos, se agrupan en una única excepción MobileServicePushFailedException. Controle cada error por separado.
Trabajar con una API personalizada
Una API personalizada le permite definir puntos de conexión personalizados que exponen la funcionalidad del servidor que no se asigna a una operación de inserción, actualización, eliminación o lectura. Mediante el uso de una API personalizada, puede tener más control sobre la mensajería, incluida la lectura y configuración de encabezados de mensaje HTTP y la definición de un formato de cuerpo del mensaje distinto de JSON.
Para llamar a una API personalizada, llame a uno de los métodos invokeApiAsync de
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
Este formulario es una llamada de método con tipo y requiere que se defina el MarkAllResult tipo de valor devuelto. Se admiten métodos con tipo y sin tipo.
El método InvokeApiAsync() antepone "/api/" a la API a la que desea llamar a menos que la API comience con "/". Por ejemplo:
-
InvokeApiAsync("completeAll",...)
llama a /api/completeAll en el back-end -
InvokeApiAsync("/.auth/me",...)
llama a /.auth/me en el back-end
Puede usar InvokeApiAsync para llamar a cualquier WebAPI, incluidos los webAPIs que no están definidos con Azure Mobile Apps. Cuando se usa InvokeApiAsync(), los encabezados adecuados, incluidos los encabezados de autenticación, se envían con la solicitud.
Autenticación de usuarios
Mobile Apps admite la autenticación y autorización de usuarios de aplicaciones que usan varios proveedores de identidades externos: Facebook, Google, Cuenta microsoft, Twitter e Id. de Microsoft Entra. Puede establecer permisos en tablas para restringir el acceso a operaciones específicas solo a usuarios autenticados. También puede usar la identidad de los usuarios autenticados para implementar reglas de autorización en scripts de servidor.
Se admiten dos flujos de autenticación: administrados por el cliente y flujo de administrados por el servidor. El flujo administrado por el servidor proporciona la experiencia de autenticación más sencilla, ya que se basa en la interfaz de autenticación web del proveedor. El flujo administrado por el cliente permite una integración más profunda con funcionalidades específicas del dispositivo, ya que se basa en SDK específicos del dispositivo específicos del proveedor.
Nota
Se recomienda usar un flujo administrado por el cliente en las aplicaciones de producción.
Para configurar la autenticación, debe registrar la aplicación con uno o varios proveedores de identidades. El proveedor de identidades genera un identificador de cliente y un secreto de cliente para la aplicación. Estos valores se establecen en el back-end para habilitar la autenticación y autorización de Azure App Service.
En esta sección se tratan los temas siguientes:
- de autenticación administrada por el cliente
- de autenticación administrada por el servidor
- Almacenamiento en caché del token de autenticación
Autenticación administrada por el cliente
La aplicación puede ponerse en contacto con el proveedor de identidades de forma independiente y, a continuación, proporcionar el token devuelto durante el inicio de sesión con el back-end. Este flujo de cliente le permite proporcionar una experiencia de inicio de sesión único para los usuarios o recuperar datos de usuario adicionales del proveedor de identidades. Se prefiere la autenticación de flujo de cliente para usar un flujo de servidor, ya que el SDK del proveedor de identidades proporciona una sensación de experiencia de usuario más nativa y permite una mayor personalización.
Se proporcionan ejemplos para los siguientes patrones de autenticación de flujo de cliente:
-
biblioteca de autenticación de Active Directory - Facebook o Google
Autenticación de usuarios con la biblioteca de autenticación de Active Directory
Puede usar la Biblioteca de autenticación de Active Directory (ADAL) para iniciar la autenticación de usuario desde el cliente mediante la autenticación de Microsoft Entra.
Advertencia
La compatibilidad con la biblioteca de autenticación de Active Directory (ADAL) finalizará en diciembre de 2022. Las aplicaciones que usan ADAL en las versiones existentes del sistema operativo seguirán funcionando, pero el soporte técnico y las actualizaciones de seguridad finalizarán. Para obtener más información, consulte Migrar aplicaciones a MSAL.
Configure el back-end de la aplicación móvil para el inicio de sesión de Microsoft Entra siguiendo el tutorial Configuración de App Service para el inicio de sesión de Active Directory. Asegúrese de completar el paso opcional de registrar una aplicación cliente nativa.
En Visual Studio, abra el proyecto y agregue una referencia al paquete NuGet de
Microsoft.IdentityModel.Clients.ActiveDirectory
. Al buscar, incluya versiones preliminares.Agregue el código siguiente a la aplicación, según la plataforma que usa. En cada uno de ellos, realice los siguientes reemplazos:
Reemplace INSERT-AUTHORITY-HERE por el nombre del inquilino en el que aprovisionó la aplicación. El formato debe ser
https://login.microsoftonline.com/contoso.onmicrosoft.com
. Este valor se puede copiar desde la pestaña Dominio del identificador de Microsoft Entra en [Azure Portal].Reemplace INSERT-RESOURCE-ID-HERE por el identificador de cliente del back-end de la aplicación móvil. Puede obtener el identificador de cliente de la pestaña avanzadas de
en Configuración de Microsoft Entra en el portal.Reemplace INSERT-CLIENT-ID-HERE por el identificador de cliente que copió de la aplicación cliente nativa.
Reemplace INSERT-REDIRECT-URI-HERE por el punto de conexión de
/.auth/login/done
del sitio mediante el esquema HTTPS. Este valor debe ser similar ahttps://contoso.azurewebsites.net/.auth/login/done
.El código necesario para cada plataforma es el siguiente:
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(); } }
de 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); } }
de 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); }
Inicio de sesión único con un token de Facebook o Google
Puede usar el flujo de cliente como se muestra en este fragmento de código para Facebook o 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();
}
}
Autenticación administrada por el servidor
Una vez que haya registrado el proveedor de identidades, llame al método LoginAsync en MobileServiceClient con el MobileServiceAuthenticationProvider valor del proveedor. Por ejemplo, el código siguiente inicia un inicio de sesión de flujo de servidor mediante 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();
}
}
Si usa un proveedor de identidades distinto de Facebook, cambie el valor de MobileServiceAuthenticationProvider al valor de su proveedor.
En un flujo de servidor, Azure App Service administra el flujo de autenticación de OAuth mostrando la página de inicio de sesión del proveedor seleccionado. Una vez devuelto el proveedor de identidades, Azure App Service genera un token de autenticación de App Service. El método LoginAsync devuelve un MobileServiceUser, que proporciona el UserId del usuario autenticado y MobileServiceAuthenticationToken, como un token web JSON (JWT). Este token se puede almacenar en caché y reutilizarse hasta que expire. Para obtener más información, consulte Almacenamiento en caché del token de autenticación.
Nota
En segundo plano, Azure Mobile Apps usa una Xamarin.Essentials WebAuthenticator para realizar el trabajo. Debe controlar la respuesta del servicio llamando de nuevo a Xamarin.Essentials. Para obtener más información, consulte webAuthenticator.
Almacenamiento en caché del token de autenticación
En algunos casos, se puede evitar la llamada al método de inicio de sesión después de la primera autenticación correcta almacenando el token de autenticación del proveedor. Las aplicaciones de Microsoft Store y UWP pueden usar PasswordVault para almacenar en caché el token de autenticación actual después de un inicio de sesión correcto, como se indica a continuación:
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
El valor UserId se almacena como userName de la credencial y el token es el almacenado como contraseña. En los inicios posteriores, puede comprobar la
// 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.
}
Al cerrar la sesión de un usuario, también debe quitar la credencial almacenada, como se indica a continuación:
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
Al usar la autenticación administrada por el cliente, también puede almacenar en caché el token de acceso obtenido de su proveedor, como Facebook o Twitter. Este token se puede proporcionar para solicitar un nuevo token de autenticación desde el back-end, como se indica a continuación:
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 varios
Control de errores
Cuando se produce un error en el back-end, el SDK de cliente genera un MobileServiceInvalidOperationException
. En el ejemplo siguiente se muestra cómo controlar una excepción devuelta por el 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
}
}
Personalización de encabezados de solicitud
Para admitir su escenario de aplicación específico, es posible que tenga que personalizar la comunicación con el back-end de la aplicación móvil. Por ejemplo, puede que quiera agregar un encabezado personalizado a cada solicitud saliente o incluso cambiar los códigos de estado de las respuestas. Puede usar un
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;
}
}
Habilitación del registro de solicitudes
También puede usar un DelegatingHandler para agregar el registro de solicitudes:
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;
}
}