Introducción a los servicios web

En esta guía se muestra cómo consumir diferentes tecnologías de servicio web. Entre los temas tratados se incluyen la comunicación con los servicios REST, los servicios SOAP y los servicios de Windows Communication Foundation.

Para funcionar correctamente, muchas aplicaciones móviles dependen de la nube, por lo que la integración de servicios web en aplicaciones móviles es un escenario común. La plataforma Xamarin admite el consumo de diferentes tecnologías de servicios web e incluye compatibilidad integrada y de terceros para consumir servicios RESTful, ASMX y Windows Communication Foundation (WCF).

Para los clientes que usan Xamarin.Forms, hay ejemplos completos que usan cada una de estas tecnologías en la documentación de servicios web de Xamarin.Forms.

Importante

En iOS 9, 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.

Puede no participar en ATS 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.

REST

Transferencia de estado representacional (REST) es un estilo arquitectónico para compilar servicios web. Las solicitudes REST se realizan a través de HTTP con los mismos verbos HTTP que usan los exploradores web para recuperar páginas web y enviar datos a los servidores. Los verbos son:

  • GET: esta operación se usa para recuperar datos del servicio web.
  • POST: esta operación se usa para crear un nuevo elemento de datos en el servicio web.
  • PUT: esta operación se usa para actualizar un elemento de datos en el servicio web.
  • PATCH: esta operación se usa para actualizar un elemento de datos en el servicio web describiendo un conjunto de instrucciones sobre cómo se debe modificar el elemento. Este verbo no se usa en la aplicación de ejemplo.
  • DELETE: esta operación se usa para eliminar un elemento de datos en el servicio web.

Las API de servicio web que se adhieren a REST se denominan API de RESTful y se definen mediante:

  • Identificador URI base.
  • Métodos HTTP, como GET, POST, PUT, PATCH o DELETE.
  • Un tipo de medio para los datos, como notación de objetos JavaScript (JSON).

La simplicidad de REST ha ayudado a convertirlo en el método principal para acceder a servicios web en aplicaciones móviles.

Consumo de servicios REST

Hay una serie de bibliotecas y clases que se pueden usar para consumir servicios REST y las siguientes subsecciones las describen. Para obtener más información sobre cómo consumir un servicio REST, consulte Consumo de servicios web RESTful.

HttpClient

El bibliotecas de cliente HTTP de Microsoft proporciona la clase HttpClient, que se usa para enviar y recibir solicitudes a través de HTTP. Proporciona funcionalidad para enviar solicitudes HTTP y recibir respuestas HTTP de un recurso identificado por URI. Cada una de estas solicitudes se envía como una operación asincrónica. Para obtener más información sobre las operaciones asincrónicas, consulte Introducción a la compatibilidad asincrónica.

La clase HttpResponseMessage representa un mensaje de respuesta HTTP recibido del servicio web después de realizar una solicitud HTTP. Contiene información sobre la respuesta, incluido el código de estado, los encabezados y el cuerpo. La clase HttpContent representa el cuerpo HTTP y los encabezados de contenido, como Content-Type y Content-Encoding. El contenido se puede leer mediante cualquiera de los métodos ReadAs, como ReadAsStringAsync y ReadAsByteArrayAsync, según el formato de los datos.

Para obtener más información sobre la clase HttpClient, vea Creación del objeto HTTPClient.

HTTPWebRequest

La llamada a servicios web con HTTPWebRequest implica:

  • Crear la instancia de solicitud para un URI determinado.
  • Establecer varias propiedades HTTP en la instancia de solicitud.
  • Recuperar HttpWebResponse de la solicitud.
  • Leer datos de la respuesta.

Por ejemplo, el código siguiente recupera datos del Servicio web de la Biblioteca nacional de medicina de EE. UU.:

var rxcui = "198440";
var request = HttpWebRequest.Create(string.Format(@"https://rxnav.nlm.nih.gov/REST/RxTerms/rxcui/{0}/allinfo", rxcui));
request.ContentType = "application/json";
request.Method = "GET";

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
  if (response.StatusCode != HttpStatusCode.OK)
     Console.Out.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
  using (StreamReader reader = new StreamReader(response.GetResponseStream()))
  {
               var content = reader.ReadToEnd();
               if(string.IsNullOrWhiteSpace(content)) {
                       Console.Out.WriteLine("Response contained empty body...");
               }
               else {
                       Console.Out.WriteLine("Response Body: \r\n {0}", content);
               }

               Assert.NotNull(content);
  }
}

En el ejemplo anterior se crea HttpWebRequest que devolverá datos con formato JSON. Los datos se devuelven en un HttpWebResponse, desde el que se puede obtener un StreamReader para leer los datos.

RestSharp

Otro enfoque para consumir servicios REST es usar la biblioteca RestSharp. RestSharp encapsula las solicitudes HTTP, incluida la compatibilidad para recuperar resultados como contenido de cadena sin procesar o como un objeto de C# deserializado. Por ejemplo, el código siguiente realiza una solicitud al Servicio web biblioteca nacional de medicina de EE. UU. y recupera los resultados como una cadena con formato JSON:

var request = new RestRequest(string.Format("{0}/allinfo", rxcui));
request.RequestFormat = DataFormat.Json;
var response = Client.Execute(request);
if(string.IsNullOrWhiteSpace(response.Content) || response.StatusCode != System.Net.HttpStatusCode.OK) {
       return null;
}
rxTerm = DeserializeRxTerm(response.Content);

DeserializeRxTerm es un método que tomará la cadena JSON sin formato de la propiedad RestSharp.RestResponse.Content y la convertirá en un objeto de C#. La deserialización de los datos devueltos desde servicios web se describe más adelante en este artículo.

NSUrlConnection

Además de las clases disponibles en la biblioteca de clases base mono (BCL), como HttpWebRequest, y las bibliotecas de C# de terceros, como RestSharp, las clases específicas de la plataforma también están disponibles para consumir servicios web. Por ejemplo, en iOS, se pueden usar las clases NSUrlConnection y NSMutableUrlRequest.

En el ejemplo de código siguiente se muestra cómo llamar al Servicio web de la Biblioteca nacional de medicina de EE. UU. mediante clases de iOS:

var rxcui = "198440";
var request = new NSMutableUrlRequest(new NSUrl(string.Format("https://rxnav.nlm.nih.gov/REST/RxTerms/rxcui/{0}/allinfo", rxcui)),
       NSUrlRequestCachePolicy.ReloadRevalidatingCacheData, 20);
request["Accept"] = "application/json";

var connectionDelegate = new RxTermNSURLConnectionDelegate();
var connection = new NSUrlConnection(request, connectionDelegate);
connection.Start();

public class RxTermNSURLConnectionDelegate : NSUrlConnectionDelegate
{
       StringBuilder _ResponseBuilder;
       public bool IsFinishedLoading { get; set; }
       public string ResponseContent { get; set; }

       public RxTermNSURLConnectionDelegate()
               : base()
       {
               _ResponseBuilder = new StringBuilder();
       }

       public override void ReceivedData(NSUrlConnection connection, NSData data)
       {
               if(data != null) {
                       _ResponseBuilder.Append(data.ToString());
               }
       }
       public override void FinishedLoading(NSUrlConnection connection)
       {
               IsFinishedLoading = true;
               ResponseContent = _ResponseBuilder.ToString();
       }
}

Por lo general, las clases específicas de la plataforma para consumir servicios web deben limitarse a escenarios en los que el código nativo se va a migrar a C#. Siempre que sea posible, el código de acceso al servicio web debe ser portátil para que se pueda compartir entre plataformas.

ServiceStack

Otra opción para llamar a servicios web es la biblioteca Service Stack. Por ejemplo, el código siguiente muestra cómo usar el método IServiceClient.GetAsync de Service Stack’para emitir una solicitud de servicio:

client.GetAsync<CustomersResponse>("",
          (response) => {
               foreach(var c in response.Customers) {
                       Console.WriteLine(c.CompanyName);
               }
       },
       (response, ex) => {
               Console.WriteLine(ex.Message);
       });

Importante

Aunque herramientas como ServiceStack y RestSharp facilitan la llamada y el consumo de servicios REST, a veces no es trivial consumir XML o JSON que no se ajusta a las convenciones de serialización estándar DataContract. Si es necesario, invoque la solicitud y controle la serialización adecuada explícitamente mediante la biblioteca ServiceStack.Text que se describe a continuación.

Consumo de datos RESTful

Normalmente, los servicios web RESTful usan mensajes JSON para devolver datos al cliente. JSON es un formato de intercambio de datos basado en texto que genera cargas compactas, lo que produce requisitos de ancho de banda reducidos al enviar datos. En esta sección, se examinarán los mecanismos para consumir respuestas RESTful en JSON y Plain-Old-XML (POX).

System.JSON

La plataforma de Xamarin se incluye con compatibilidad con JSON de fábrica. Mediante JsonObject, los resultados se pueden recuperar como se muestra en el ejemplo de código siguiente:

var obj = JsonObject.Parse(json);
var properties = obj["rxtermsProperties"];
term.BrandName = properties["brandName"];
term.DisplayName = properties["displayName"];
term.Synonym = properties["synonym"];
term.FullName = properties["fullName"];
term.FullGenericName = properties["fullGenericName"];
term.Strength = properties["strength"];

Sin embargo, es importante tener en cuenta que las herramientas de System.Json cargan la totalidad de los datos en la memoria.

JSON.NET

La biblioteca NewtonSoft JSON.NET es una biblioteca ampliamente utilizada para serializar y deserializar mensajes JSON. En el ejemplo de código siguiente se muestra cómo usar JSON.NET para deserializar un mensaje JSON en un objeto de C#:

var term = new RxTerm();
var properties = JObject.Parse(json)["rxtermsProperties"];
term.BrandName = properties["brandName"].Value<string>();
term.DisplayName = properties["displayName"].Value<string>();
term.Synonym = properties["synonym"].Value<string>();;
term.FullName = properties["fullName"].Value<string>();;
term.FullGenericName = properties["fullGenericName"].Value<string>();;
term.Strength = properties["strength"].Value<string>();
term.RxCUI = properties["rxcui"].Value<string>();

ServiceStack.Text

ServiceStack.Text es una biblioteca de serialización JSON diseñada para trabajar con la biblioteca ServiceStack. En el ejemplo de código siguiente se muestra cómo analizar JSON mediante ServiceStack.Text.JsonObject:

var result = JsonObject.Parse(json).Object("rxtermsProperties")
       .ConvertTo(x => new RxTerm {
               BrandName = x.Get("brandName"),
               DisplayName = x.Get("displayName"),
               Synonym = x.Get("synonym"),
               FullName = x.Get("fullName"),
               FullGenericName = x.Get("fullGenericName"),
               Strength = x.Get("strength"),
               RxTermDoseForm = x.Get("rxtermsDoseForm"),
               Route = x.Get("route"),
               RxCUI = x.Get("rxcui"),
               RxNormDoseForm = x.Get("rxnormDoseForm"),
       });

System.Xml.Linq

En caso de consumir un servicio web REST basado en XML, LINQ to XML se puede usar para analizar el XML y rellenar un objeto de C# insertado, como se muestra en el ejemplo de código siguiente:

var doc = XDocument.Parse(xml);
var result = doc.Root.Descendants("rxtermsProperties")
.Select(x=> new RxTerm()
       {
               BrandName = x.Element("brandName").Value,
               DisplayName = x.Element("displayName").Value,
               Synonym = x.Element("synonym").Value,
               FullName = x.Element("fullName").Value,
               FullGenericName = x.Element("fullGenericName").Value,
               //bind more here...
               RxCUI = x.Element("rxcui").Value,
       });

Servicio web de 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.

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.

Generación de un proxy

Se debe generar un proxy para consumir un servicio ASMX, 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 como un documento del lenguaje de descripción de servicios web (WSDL) generado por el servicio web. El proxy se compila mediante Visual Studio para Mac o Visual Studio para agregar una referencia web para el servicio web a los proyectos específicos de la plataforma.

La dirección URL del servicio web puede ser un origen remoto hospedado o un recurso del sistema de archivos local accesible a través del prefijo de ruta de acceso file:///, por ejemplo:

file:///Users/myUserName/projects/MyProjectName/service.wsdl

The web service URL can either be a hosted remote source or local file system resource accessible via the file path prefix

Esto genera el proxy en la carpeta Referencias de servicio o web del proyecto. Dado que un proxy se genera código, no se debe modificar.

Agregar manualmente un proxy a un proyecto

Si tiene un proxy existente que se ha generado mediante herramientas compatibles, esta salida se puede consumir cuando se incluye como parte del proyecto. En Visual Studio para Mac, use la opción de menú Agregar archivos... para agregar el proxy. Además, esto requiere que se haga referencia a System.Web.Services.dll explícitamente mediante el cuadro de diálogo Agregar referencias....

Consumo del proxy

Las clases de proxy generadas proporcionan métodos para consumir el servicio 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. El ejemplo de código siguiente muestra un ejemplo:

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

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 Task.Factory.FromAsync. Este método crea un Task que ejecuta el método TodoService.EndGetTodoItems una vez completado el método TodoService.BeginGetTodoItems, con el parámetro null que indica que no se pasa ningún dato al delegado de 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.

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.

Para obtener más información sobre cómo consumir un servicio ASMX, consulte Consumo de un servicio web de ASP.NET (ASMX).

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.

WCF describe un servicio con una variedad de contratos diferentes que incluyen lo siguiente:

  • 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 los servicios web ASP.NET (ASMX) y WCF, pero es importante comprender que WCF admite las mismas funcionalidades que proporciona ASMX: mensajes SOAP a través de HTTP.

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. Además, la compatibilidad con WCF requiere el uso de herramientas solo disponibles en un entorno Windows para generar el proxy.

Generación de un proxy

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).

Configuración del proxy

La configuración del proxy generado generalmente tomará dos argumentos de configuración (según SOAP 1.1/ASMX o WCF) durante la inicialización: el EndpointAddress o la información de enlace asociada, como se muestra en el ejemplo siguiente:

var binding = new BasicHttpBinding () {
       Name= "basicHttpBinding",
       MaxReceivedMessageSize = 67108864,
};

binding.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() {
       MaxArrayLength = 2147483646,
       MaxStringContentLength = 5242880,
};

var timeout = new TimeSpan(0,1,0);
binding.SendTimeout= timeout;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;

client = new Service1Client (binding, new EndpointAddress ("http://192.168.1.100/Service1.svc"));

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.

Consumo del proxy

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. El ejemplo de código siguiente muestra un ejemplo:

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

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 Task.Factory.FromAsync. Este método crea un Task 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 de 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.

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.

Para obtener más información sobre cómo consumir un servicio WCF, vea Consumir un servicio web de Windows Communication Foundation (WCF).

Uso de seguridad de transporte

Los servicios WCF pueden emplear seguridad de nivel de transporte para protegerse contra la interceptación de mensajes. La plataforma Xamarin admite enlaces que emplean seguridad de nivel de transporte mediante SSL. Sin embargo, puede haber casos en los que la pila pueda necesitar validar el certificado, lo que da como resultado un comportamiento imprevisto. La validación se puede invalidar registrando un delegado ServerCertificateValidationCallback antes de invocar el servicio, como se muestra en el ejemplo de código siguiente:

System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(se, cert, chain, sslerror) => { return true; };

Esto mantiene el cifrado de transporte mientras se omite la validación del certificado del lado servidor. Sin embargo, este enfoque ignora totalmente los problemas de confianza asociados al certificado y puede que no sea adecuado. Para obtener más información, consulte Uso respetuoso de las raíces de confianza en mono-project.com.

Uso de seguridad de credenciales de cliente

Los servicios WCF también pueden requerir que los clientes de servicio se autentiquen mediante credenciales. La plataforma Xamarin no admite el protocolo WS-Security, que permite a los clientes enviar credenciales dentro del sobre de mensajes SOAP. Sin embargo, la plataforma de Xamarin admite la capacidad de enviar credenciales de autenticación básica HTTP al servidor especificando el ClientCredentialType adecuado:

basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

A continuación, se pueden especificar las credenciales de autenticación básicas:

client.ClientCredentials.UserName.UserName = @"foo";
client.ClientCredentials.UserName.Password = @"mrsnuggles";

Para obtener más información sobre la autenticación básica HTTP, aunque en el contexto de un servicio web REST, consulte Autenticación de un servicio web RESTful.