Consumo de un servicio web ASP.NET (ASMX)

ASMX proporciona la capacidad de crear servicios web que envíen mensajes mediante el Protocolo simple de acceso a objetos (SOAP). SOAP es un protocolo independiente de la plataforma e independiente del lenguaje para compilar servicios web y acceder a ellos. Los consumidores de un servicio ASMX no necesitan saber nada sobre la plataforma, el modelo de objetos o el lenguaje de programación que se usa para implementar el servicio. Solo necesitan saber cómo enviar y recibir mensajes SOAP. En este artículo se muestra cómo consumir un servicio SOAP ASMX desde una aplicación Xamarin.Forms.

Un mensaje SOAP es un documento XML que contiene los siguientes elementos:

  • Elemento raíz denominado Envelope que identifica el documento XML como un mensaje SOAP.
  • Un elemento opcional Header que contiene información específica de la aplicación, como los datos de autenticación. Si el elemento Header está presente, debe ser el primer elemento secundario del elemento Envelope.
  • Elemento Body necesario que contiene el mensaje SOAP destinado al destinatario.
  • Elemento Fault opcional que se usa para indicar mensajes de error. Si el elemento Fault está presente, debe ser un elemento secundario del elemento Body.

SOAP puede funcionar a través de muchos protocolos de transporte, como HTTP, SMTP, TCP y UDP. Sin embargo, un servicio ASMX solo puede funcionar a través de HTTP. La plataforma Xamarin admite implementaciones estándar de SOAP 1.1 a través de HTTP y esto incluye compatibilidad con muchas de las configuraciones de servicio ASMX estándar.

Este ejemplo incluye las aplicaciones móviles que se ejecutan en dispositivos físicos o emulados, y un servicio ASMX que proporciona métodos para obtener, agregar, editar y eliminar datos. Cuando se ejecutan las aplicaciones móviles, se conectan al servicio ASMX hospedado localmente, como se muestra en la captura de pantalla siguiente:

Aplicación de ejemplo

Nota:

En iOS 9 y versiones posteriores, App Transport Security (ATS) exige conexiones seguras entre los recursos de Internet (como el servidor back-end de la aplicación) y la aplicación, lo que impide la divulgación accidental de información confidencial. Dado que ATS está habilitado de forma predeterminada en las aplicaciones compiladas para iOS 9, todas las conexiones estarán sujetas a los requisitos de seguridad de ATS. Si las conexiones no cumplen estos requisitos, producirán un error con una excepción. ATS puede desactivarse si no es posible usar el protocolo HTTPS y la comunicación segura para los recursos de Internet. Esto se puede lograr actualizando el archivo Info.plist de la aplicación. Para obtener más información, consulte Seguridad de transporte de aplicación.

Consumo del servicio web

El servicio ASMX proporciona las siguientes operaciones:

Operación Descripción Parámetros
GetTodoItems Obtención de una lista de tareas pendientes
CreateTodoItem Crear un nuevo elemento de tareas pendientes Un objeto TodoItem serializado XML
EditTodoItem Actualizar una tarea pendiente Un objeto TodoItem serializado XML
DeleteTodoItem Eliminar una tarea pendiente Un objeto TodoItem serializado XML

Para obtener más información sobre el modelo de datos usado en la aplicación, vea Modelado de los datos.

Creación del proxy TodoService

Una clase de proxy, denominada TodoService, extiende SoapHttpClientProtocol y proporciona métodos para comunicarse con el servicio ASMX a través de HTTP. El proxy se genera agregando una referencia web a cada proyecto específico de la plataforma en Visual Studio 2019 o Visual Studio 2017. La referencia web genera métodos y eventos para cada acción definida en el documento lenguaje de descripción de servicios web (WSDL) del servicio.

Por ejemplo, la acción GetTodoItems servicio da como resultado un método GetTodoItemsAsync y un evento GetTodoItemsCompleted en el proxy. El método generado tiene un tipo de valor devuelto void e invoca la GetTodoItems acción en la clase primaria SoapHttpClientProtocol. Cuando el método invocado recibe una respuesta del servicio, desencadena el evento GetTodoItemsCompleted y proporciona los datos de respuesta dentro de la propiedad Result del evento.

Creación de la implementación de ISoapService

Para permitir que el proyecto compartido y multiplataforma funcione con el servicio, el ejemplo define la ISoapService interfaz, que sigue el Modelo de programación asincrónica de tareas en C#. Cada plataforma implementa para ISoapService exponer el proxy específico de la plataforma. En el ejemplo se usan TaskCompletionSource objetos para exponer el proxy como una interfaz asincrónica de tareas. Los detalles sobre el uso TaskCompletionSource se encuentran en las implementaciones de cada tipo de acción en las secciones siguientes.

El ejemplo SoapService:

  1. Crea instancias TodoService como una instancia de nivel de clase
  2. Crea una colección denominada Items para almacenar TodoItem objetos
  3. Especifica un punto de conexión personalizado para la propiedad opcional Url en el TodoService
public class SoapService : ISoapService
{
    ASMXService.TodoService todoService;
    public List<TodoItem> Items { get; private set; } = new List<TodoItem>();

    public SoapService ()
    {
        todoService = new ASMXService.TodoService ();
        todoService.Url = Constants.SoapUrl;
        ...
    }
}

Creación de objetos de transferencia de datos

La aplicación de ejemplo usa la clase TodoItem para modelar datos. Para almacenar un elemento TodoItem en el servicio web, primero debe convertirse al tipo generado por proxy TodoItem. Esto se logra mediante el método ToASMXServiceTodoItem, como se muestra en el ejemplo de código siguiente:

ASMXService.TodoItem ToASMXServiceTodoItem (TodoItem item)
{
    return new ASMXService.TodoItem {
        ID = item.ID,
        Name = item.Name,
        Notes = item.Notes,
        Done = item.Done
    };
}

Este método crea una nueva ASMService.TodoItem instancia y establece cada propiedad en la propiedad idéntica de la TodoItem instancia.

De forma similar, cuando los datos se recuperan del servicio web, se deben convertir del tipo generado TodoItem por proxy a una TodoItem instancia. Esto se logra con el método FromASMXServiceTodoItem, como se muestra en el ejemplo de código siguiente:

static TodoItem FromASMXServiceTodoItem (ASMXService.TodoItem item)
{
    return new TodoItem {
        ID = item.ID,
        Name = item.Name,
        Notes = item.Notes,
        Done = item.Done
    };
}

Este método recupera los datos del tipo generado por TodoItem proxy y lo establece en la instancia recién creada TodoItem.

Recuperación de datos

La interfaz ISoapService espera que el método RefreshDataAsync devuelva un Task con la colección de elementos. Sin embargo, el método TodoService.GetTodoItemsAsync devuelve void. Para satisfacer el patrón de interfaz, debe llamar a GetTodoItemsAsync, esperar a que se active el GetTodoItemsCompleted evento y rellenar la colección. Esto le permite devolver una colección válida a la interfaz de usuario.

En el ejemplo siguiente se crea un nuevo TaskCompletionSource, se inicia la llamada asincrónica en el RefreshDataAsync método y se espera el Task proporcionado por el TaskCompletionSource. Cuando se invoca el controlador de eventos TodoService_GetTodoItemsCompleted, rellena la colección Items y actualiza el TaskCompletionSource:

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> getRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.GetTodoItemsCompleted += TodoService_GetTodoItemsCompleted;
    }

    public async Task<List<TodoItem>> RefreshDataAsync()
    {
        getRequestComplete = new TaskCompletionSource<bool>();
        todoService.GetTodoItemsAsync();
        await getRequestComplete.Task;
        return Items;
    }

    private void TodoService_GetTodoItemsCompleted(object sender, ASMXService.GetTodoItemsCompletedEventArgs e)
    {
        try
        {
            getRequestComplete = getRequestComplete ?? new TaskCompletionSource<bool>();

            Items = new List<TodoItem>();
            foreach (var item in e.Result)
            {
                Items.Add(FromASMXServiceTodoItem(item));
            }
            getRequestComplete?.TrySetResult(true);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(@"\t\tERROR {0}", ex.Message);
        }
    }

    ...
}

Para obtener más información, vea Modelo de programación asincrónica y TPL y programación asincrónica tradicional de .NET Framework.

Crear o editar datos

Al crear o editar datos, debe implementar el ISoapService.SaveTodoItemAsync método. Este método detecta si TodoItem es un elemento nuevo o actualizado y llama al método adecuado en el todoService objeto. Los controladores de eventos CreateTodoItemCompleted y EditTodoItemCompleted también deben implementarse para que sepa cuándo el todoService ha recibido una respuesta del servicio ASMX (estos se pueden combinar en un único controlador porque realizan la misma operación). En el ejemplo siguiente se muestran las implementaciones de interfaz y controlador de eventos, así como el objeto TaskCompletionSource usado para operar de forma asincrónica:

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> saveRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.CreateTodoItemCompleted += TodoService_SaveTodoItemCompleted;
        todoService.EditTodoItemCompleted += TodoService_SaveTodoItemCompleted;
    }

    public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
    {
        try
        {
            var todoItem = ToASMXServiceTodoItem(item);
            saveRequestComplete = new TaskCompletionSource<bool>();
            if (isNewItem)
            {
                todoService.CreateTodoItemAsync(todoItem);
            }
            else
            {
                todoService.EditTodoItemAsync(todoItem);
            }
            await saveRequestComplete.Task;
        }
        catch (SoapException se)
        {
            Debug.WriteLine("\t\t{0}", se.Message);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("\t\tERROR {0}", ex.Message);
        }
    }

    private void TodoService_SaveTodoItemCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        saveRequestComplete?.TrySetResult(true);
    }

    ...
}

Eliminación de datos

La eliminación de datos requiere una implementación similar. Defina un TaskCompletionSource, implemente un controlador de eventos y el método ISoapService.DeleteTodoItemAsync:

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> deleteRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.DeleteTodoItemCompleted += TodoService_DeleteTodoItemCompleted;
    }

    public async Task DeleteTodoItemAsync (string id)
    {
        try
        {
            deleteRequestComplete = new TaskCompletionSource<bool>();
            todoService.DeleteTodoItemAsync(id);
            await deleteRequestComplete.Task;
        }
        catch (SoapException se)
        {
            Debug.WriteLine("\t\t{0}", se.Message);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("\t\tERROR {0}", ex.Message);
        }
    }

    private void TodoService_DeleteTodoItemCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        deleteRequestComplete?.TrySetResult(true);
    }

    ...
}

Prueba del servicio web

La prueba de dispositivos físicos o emulados con un servicio hospedado localmente requiere la configuración personalizada de IIS, las direcciones de punto de conexión y las reglas de firewall. Para obtener más información sobre cómo configurar el entorno para las pruebas, vea la Configuración del acceso remoto a IIS Express. La única diferencia entre probar WCF y ASMX es el número de puerto de TodoService.