Consumir un servicio web de Windows Communication Foundation (WCF)

WCF es el marco unificado de Microsoft para crear aplicaciones orientadas a servicios. Permite a los desarrolladores crear aplicaciones distribuidas seguras, confiables, con transacciones e interoperables. En este artículo se muestra cómo consumir un servicio WCF Simple Object Access Protocol (SOAP) desde una Xamarin.Forms aplicación.

WCF describe un servicio con una variedad de contratos diferentes, entre los que se incluyen:

  • Contratos de datos: definen las estructuras de datos que forman la base del contenido dentro de un mensaje.
  • Contratos de mensajes: redactan mensajes a partir de contratos de datos existentes.
  • Contratos de error: permiten especificar errores SOAP personalizados.
  • Contratos de servicio: especifican las operaciones que admiten los servicios y los mensajes necesarios para interactuar con cada operación. También especifican cualquier comportamiento de error personalizado que se pueda asociar a las operaciones en cada servicio.

Hay diferencias entre ASP.NET servicios web (ASMX) y WCF, pero WCF admite las mismas funcionalidades que proporciona ASMX: mensajes SOAP a través de HTTP. Para obtener más información sobre el consumo de un servicio ASMX, consulte Consumo de servicios web (ASMX)de ASP.NET.

Importante

La compatibilidad de la plataforma Xamarin con WCF se limita a los mensajes SOAP codificados con texto a través de HTTP/HTTPS mediante la clase BasicHttpBinding.

La compatibilidad con WCF requiere el uso de herramientas solo disponibles en un entorno de Windows para generar el proxy y hospedar todoWCFService. La compilación y prueba de la aplicación iOS requerirá la implementación de TodoWCFService en un equipo Windows o como servicio web de Azure.

Normalmente, las aplicaciones nativas de Xamarin Forms comparten código con una biblioteca de clases estándar de .NET. Sin embargo, .NET Core no admite ACTUALMENTE WCF, por lo que el proyecto compartido debe ser una biblioteca de clases portable heredada. Para obtener información sobre la compatibilidad con WCF en .NET Core, consulte Elección entre .NET Core y .NET Framework para aplicaciones de servidor.

La solución de aplicación de ejemplo incluye un servicio WCF que se puede ejecutar localmente y 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 WCF 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, consulte Modelado de los datos.

Se debe generar un proxy para consumir un servicio WCF, lo que permite a la aplicación conectarse al servicio. El proxy se construye mediante el consumo de metadatos de servicio que define los métodos y la configuración del servicio asociada. Estos metadatos se exponen en forma de un documento de lenguaje de descripción de servicios web (WSDL) generado por el servicio web. El proxy se puede compilar mediante el proveedor de WCF Web Service Reference de Microsoft en Visual Studio 2017 para agregar una referencia de servicio para el servicio web a una biblioteca estándar de .NET. Una alternativa a la creación del proxy mediante el WCF Web Service Reference de Microsoft en Visual Studio 2017 es usar la herramienta de utilidad de metadatos ServiceModel (svcutil.exe). Para obtener más información, consulte Herramienta de utilidad de metadatos de ServiceModel (Svcutil.exe).

Las clases de proxy generadas proporcionan métodos para consumir los servicios web que usan el patrón de diseño modelo de programación asincrónica (APM). En este patrón se implementa una operación asincrónica como dos métodos denominados BeginOperationName y EndOperationName, que comienzan y terminan la operación asincrónica.

El método BeginOperationName inicia la operación asincrónica y devuelve un objeto que implementa la interfaz IAsyncResult. Después de llamar a BeginOperationName, una aplicación puede seguir ejecutando instrucciones en el subproceso de llamada, mientras que la operación asincrónica tiene lugar en un subproceso de grupo de subprocesos.

Para cada llamada a BeginOperationName, la aplicación también debe llamar a EndOperationName para obtener los resultados de la operación. El valor devuelto de EndOperationName es el mismo tipo devuelto por el método de servicio web sincrónico. Por ejemplo, el EndGetTodoItems método devuelve una colección de TodoItem instancias. El método EndOperationName también incluye un parámetro IAsyncResult que se debe establecer en la instancia devuelta por la llamada correspondiente al método BeginOperationName.

La biblioteca paralela de tareas (TPL) puede simplificar el proceso de consumo de un par de métodos de inicio y fin de APM encapsulando las operaciones asincrónicas en el mismo objeto Task. Esta encapsulación se proporciona mediante varias sobrecargas del método TaskFactory.FromAsync.

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

Creación del objeto TodoServiceClient

La clase de proxy generada proporciona la clase TodoServiceClient, que se usa para comunicarse con el servicio WCF a través de HTTP. Proporciona funcionalidad para invocar métodos de servicio web como operaciones asincrónicas desde una instancia de servicio identificada por URI. Para obtener más información sobre las operaciones asincrónicas, consulte Introducción a la compatibilidad asincrónica.

La instancia TodoServiceClient se declara en el nivel de clase para que el objeto resida mientras la aplicación necesite consumir el servicio WCF, como se muestra en el ejemplo de código siguiente:

public class SoapService : ISoapService
{
  ITodoService todoService;
  ...

  public SoapService ()
  {
    todoService = new TodoServiceClient (
      new BasicHttpBinding (),
      new EndpointAddress (Constants.SoapUrl));
  }
  ...
}

La instancia TodoServiceClient se configura con información de enlace y una dirección de punto de conexión. Un enlace se usa para especificar los detalles de transporte, codificación y protocolo necesarios para que las aplicaciones y los servicios se comuniquen entre sí. BasicHttpBinding especifica que los mensajes SOAP codificados con texto se enviarán a través del protocolo de transporte HTTP. Especificar una dirección de punto de conexión permite a la aplicación conectarse a diferentes instancias del servicio WCF, siempre que haya varias instancias publicadas.

Para obtener más información sobre cómo configurar la referencia de servicio, consulte Configuración de la referenciade servicio.

Crear 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 ToWCFServiceTodoItem, como se muestra en el ejemplo de código siguiente:

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

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

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

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

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

Recuperación de datos

Los métodos TodoServiceClient.BeginGetTodoItems y TodoServiceClient.EndGetTodoItems se usan para llamar a la operación GetTodoItems proporcionada por el servicio web. Estos métodos asincrónicos se encapsulan en un objeto Task, como se muestra en el ejemplo de código siguiente:

public async Task<List<TodoItem>> RefreshDataAsync ()
{
  ...
  var todoItems = await Task.Factory.FromAsync <ObservableCollection<TodoWCFService.TodoItem>> (
    todoService.BeginGetTodoItems,
    todoService.EndGetTodoItems,
    null,
    TaskCreationOptions.None);

  foreach (var item in todoItems)
  {
    Items.Add (FromWCFServiceTodoItem (item));
  }
  ...
}

El Task.Factory.FromAsync método crea un Task objeto que ejecuta el método TodoServiceClient.EndGetTodoItems una vez completado el método TodoServiceClient.BeginGetTodoItems, con el parámetro null que indica que no se pasa ningún dato al delegado BeginGetTodoItems. Por último, el valor de la enumeración TaskCreationOptions especifica que se debe usar el comportamiento predeterminado para la creación y ejecución de tareas.

El método TodoServiceClient.EndGetTodoItems devuelve una ObservableCollection de TodoWCFService.TodoItem instancias, que luego se convierte en una List de TodoItem instancias para mostrar.

Crear datos

Los métodos TodoServiceClient.BeginCreateTodoItem y TodoServiceClient.EndCreateTodoItem se usan para llamar a la operación CreateTodoItem proporcionada por el servicio web. Estos métodos asincrónicos se encapsulan en un objeto Task, como se muestra en el ejemplo de código siguiente:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginCreateTodoItem,
    todoService.EndCreateTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

El Task.Factory.FromAsync método crea un objeto Task que ejecuta el método TodoServiceClient.EndCreateTodoItem una vez completado el método TodoServiceClient.BeginCreateTodoItem, con el parámetro todoItem siendo los datos que se pasan al BeginCreateTodoItem delegado para especificar el objeto TodoItem que va a crear el servicio web. Por último, el valor de la enumeración TaskCreationOptions especifica que se debe usar el comportamiento predeterminado para la creación y ejecución de tareas.

El servicio web produce un FaultException si no puede crear el TodoItem, que se controla mediante la aplicación.

Actualizar datos

Los métodos TodoServiceClient.BeginEditTodoItem y TodoServiceClient.EndEditTodoItem se usan para llamar a la EditTodoItem operación proporcionada por el servicio web. Estos métodos asincrónicos se encapsulan en un objeto Task, como se muestra en el ejemplo de código siguiente:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginEditTodoItem,
    todoService.EndEditTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

El método Task.Factory.FromAsync crea un objeto Task que ejecuta el método TodoServiceClient.EndEditTodoItem una vez completado el método TodoServiceClient.BeginCreateTodoItem, con el parámetro todoItem siendo los datos que se pasan al delegado BeginEditTodoItem para especificar el objeto TodoItem que va a actualizar el servicio web. Por último, el valor de la enumeración TaskCreationOptions especifica que se debe usar el comportamiento predeterminado para la creación y ejecución de tareas.

El servicio web produce un FaultException si no encuentra o actualiza TodoItem, que se controla mediante la aplicación.

Eliminación de datos

Los métodos TodoServiceClient.BeginDeleteTodoItem y TodoServiceClient.EndDeleteTodoItem se usan para llamar a la operación DeleteTodoItem proporcionada por el servicio web. Estos métodos asincrónicos se encapsulan en un objeto Task, como se muestra en el ejemplo de código siguiente:

public async Task DeleteTodoItemAsync (string id)
{
  ...
  await Task.Factory.FromAsync (
    todoService.BeginDeleteTodoItem,
    todoService.EndDeleteTodoItem,
    id,
    TaskCreationOptions.None);
  ...
}

El método Task.Factory.FromAsync crea un objeto Task que ejecuta el método TodoServiceClient.EndDeleteTodoItem una vez completado el método TodoServiceClient.BeginDeleteTodoItem, con el parámetro id siendo los datos que se pasan al delegado BeginDeleteTodoItem para especificar el objeto TodoItem que va a eliminar el servicio web. Por último, el valor de la enumeración TaskCreationOptions especifica que se debe usar el comportamiento predeterminado para la creación y ejecución de tareas.

El servicio web produce un FaultException si no encuentra o elimina el TodoItemque la aplicación controla.

Configuración del acceso remoto a IIS Express

En Visual Studio 2017 o Visual Studio 2019, deberías poder probar la aplicación para UWP en un equipo sin ninguna configuración adicional. La prueba de clientes Android e iOS puede requerir los pasos adicionales de esta sección. Consulte Conexión a servicios web locales desde simuladores de iOS y Emuladores de Android para obtener más información.

De forma predeterminada, IIS Express solo responderá a las solicitudes a localhost. Los dispositivos remotos (como un dispositivo Android, un iPhone o incluso un simulador) no tendrán acceso al servicio WCF local. Necesitará conocer la dirección IP de la estación de trabajo de Windows 10 en la red local. Para este ejemplo, supongamos que la estación de trabajo tiene la dirección IP 192.168.1.143. En los pasos siguientes se explica cómo configurar Windows 10 e IIS Express para aceptar conexiones remotas y conectarse al servicio desde un dispositivo físico o virtual:

  1. Agregue una excepción a Firewall de Windows. Debe abrir un puerto a través del Firewall de Windows que las aplicaciones de la subred pueden usar para comunicarse con el servicio WCF. Cree un puerto de apertura de regla de entrada 49393 en el firewall. Desde un símbolo del sistema administrativo, ejecute este comando:

    netsh advfirewall firewall add rule name="TodoWCFService" dir=in protocol=tcp localport=49393 profile=private remoteip=localsubnet action=allow
    
  2. Configure IIS Express para aceptar conexiones remotas. Puede configurar IIS Express editando el archivo de configuración de IIS Express en [directorio de soluciones].vs\config\applicationhost.config. Busque el elemento site con el nombre TodoWCFService. Debería tener un aspecto similar al siguiente XML:

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
        </bindings>
    </site>
    

    Deberá agregar dos elementos binding para abrir el puerto 49393 al tráfico exterior y al emulador de Android. El enlace usa un formato [IP address]:[port]:[hostname] que especifica cómo IIS Express responderá a las solicitudes. Las solicitudes externas tendrán nombres de host que se deben especificar como binding. Agregue el siguiente XML al elemento bindings y reemplace la dirección IP por su propia dirección IP:

    <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
    <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
    

    Después de cambiar el elemento debe tener un aspecto similar al bindings siguiente:

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
            <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
            <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
        </bindings>
    </site>
    

    Importante

    De forma predeterminada, IIS Express no aceptará conexiones de orígenes externos por motivos de seguridad. Para habilitar conexiones desde dispositivos remotos, debe ejecutar IIS Express con permisos administrativos. La manera más fácil de hacerlo es ejecutar Visual Studio 2017 con permisos administrativos. Esto iniciará IIS Express con permisos administrativos al ejecutar TodoWCFService.

    Con estos pasos completados, debería poder ejecutar TodoWCFService y conectarse desde otros dispositivos de la subred. Para probarlo, ejecute la aplicación y visite http://localhost:49393/TodoService.svc. Si recibe un error de solicitud incorrecta al visitar esa dirección URL, bindings puede ser incorrecto en la configuración de IIS Express (la solicitud llega a IIS Express pero se rechaza). Si recibe un error diferente, puede ser que la aplicación no se esté ejecutando o que el firewall esté configurado incorrectamente.

    Para permitir que IIS Express siga ejecutándose y sirviendo el servicio, desactive la opción Editar y continuar en Propiedades del proyecto>>Depuradores web.

  3. Personalice los dispositivos de punto de conexión que usan para acceder al servicio. Este paso implica configurar la aplicación cliente, que se ejecuta en un dispositivo físico o emulado, para acceder al servicio WCF.

    El emulador de Android utiliza un proxy interno que impide que el emulador acceda directamente a la dirección localhost de la máquina host. En su lugar, la dirección 10.0.2.2 del emulador se enruta a localhost en el equipo host a través de un proxy interno. Estas solicitudes proxy tendrán 127.0.0.1 como nombre de host en el encabezado de solicitud, por lo que creó el enlace de IIS Express para este nombre de host en los pasos anteriores.

    El simulador de iOS se ejecuta en un host de compilación de Mac, incluso si usa el simulador remoto de iOS para Windows. Las solicitudes de red del simulador tendrán la dirección IP de la estación de trabajo en la red local como nombre de host (en este ejemplo, es 192.168.1.143, pero es probable que la dirección IP real sea diferente). Este es el motivo por el que creó el enlace de IIS Express para este nombre de host en los pasos anteriores.

    Asegúrese de que la SoapUrl propiedad del archivo Constants.cs del proyecto TodoWCF (Portable) tiene valores correctos para la red:

    public static string SoapUrl
    {
        get
        {
            var defaultUrl = "http://localhost:49393/TodoService.svc";
    
            if (Device.RuntimePlatform == Device.Android)
            {
                defaultUrl = "http://10.0.2.2:49393/TodoService.svc";
            }
            else if (Device.RuntimePlatform == Device.iOS)
            {
                defaultUrl = "http://192.168.1.143:49393/TodoService.svc";
            }
    
            return defaultUrl;
        }
    }
    

    Una vez configurado el Constants.cs con los puntos de conexión adecuados, debería poder conectarse a TodoWCFService que se ejecuta en la estación de trabajo de Windows 10 desde dispositivos físicos o virtuales.