martes, 16 de abril de 2013

Crear un cliente REST con Visual Studio 2010

Hoy veremos cómo crear un cliente REST desde Visual Studio 2010 que nos servirá para conectar a cualquier servicio REST.

Abrimos Visual Studio 2010, en primer lugar comentar que usaremos Microsoft ASP.NET Web API Client Libraries que está disponible en NuGet. Si no tenéis la extensión NuGet instalada se puede hacer siguiendo los pasos del siguiente link

Una vez ya tenemos NuGet en Visual Studio lo siguiente es ir al menú Herramientas desde Visual Studio y acceder al administrador de paquetes de la solución.

areaTIC, ASP.NET Cliente REST, Administrar Paquetes NuGet

Buscamos WebApi y añadimos el paquete que se muestra en la imagen.

areaTIC, ASP.NET Cliente REST, Descargar Microsoft ASP.NET Web API Client

Vamos ya a crear el cliente. Añadimos una clase que llamaremos RESTClient. La idea es jugar con genéricos para que la misma clase nos sirva para consumir cualquier servicio de una API que siga el patrón REST. Definiremos un constructor en el cual recibiremos los datos variables como URL base, nombre del servicio (...) y a continuación añadiremos un método para cada verbo Http.

Veamos como quedaría el código:
namespace areaTIC.REST
{
  public class RESTClient<T>
  {
    private Uri _BaseURL;
    private string _MediaTypeHeader = "application/json";
    private string _ServiceName = "";
    private HttpClient _Client;
               
    public RESTClient(Uri pBaseURL, string pServiceName) 
    {
      _BaseURL = pBaseURL;
      _ServiceName = pServiceName;

      _Client = new HttpClient();
      _Client.BaseAddress = _BaseURL;
      _Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(_MediaTypeHeader));
    }

    public List<T> Get() 
    {
      try
      {
        List<T> result;
        HttpResponseMessage response = _Client.GetAsync(_ServiceName).Result;
        if (response.IsSuccessStatusCode)
        {
          result = response.Content.ReadAsAsync<List<T>>().Result;
        }
        else
        {
          throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
        }
        return result;
      }
      catch (Exception ex)
      {
        throw ex;
      }
    }

    public T Get(int pId) 
    {
      try
      {
        T result;
        HttpResponseMessage response = _Client.GetAsync(string.Format("{0}/{1}",_ServiceName,pId)).Result;

        if (response.IsSuccessStatusCode)
        {
          result = response.Content.ReadAsAsync<T>().Result;
        }
        else
        {
          throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
        }

         return result;
       }
       catch (Exception ex) 
       {
         throw ex;
       }
   }

   public void Post(T pValue)
   {
     try
     {
        List<KeyValuePair<string, string>> oListTest = new AreaTICJavaScriptSerializer().GetKeyValuePair(pValue);
        FormUrlEncodedContent content = new FormUrlEncodedContent(oListTest);
        HttpResponseMessage response = _Client.PostAsync(_ServiceName, content).Result;

        if (!response.IsSuccessStatusCode)
        {
          throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
        }
     }
     catch (Exception ex)
     {
        throw ex;
     }
  }

  public void Put(int pId, T pValue)
  {
    try
    {
       List<KeyValuePair<string, string>> oListTest = new AreaTICJavaScriptSerializer().GetKeyValuePair(pValue);
       FormUrlEncodedContent content = new FormUrlEncodedContent(oListTest);
       HttpResponseMessage response = _Client.PutAsync(string.Format("{0}/{1}", _ServiceName, pId), content).Result;

       if (!response.IsSuccessStatusCode)
       {
          throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
       }
    }
    catch (Exception ex)
    {
       throw ex;
    }
  }

  public void Delete(int pId) 
  {
    HttpResponseMessage response = _Client.DeleteAsync(string.Format("{0}/{1}", _ServiceName, pId)).Result;
    if (!response.IsSuccessStatusCode)
    {
      throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
    }
  }

 }
}
Con esto ya tenemos un cliente genérico que nos permitirá conectar a cualquier servicio Web API Rest. Veamos como testearlo, lo más sencillo sería usar el ejemplo de la WebAPI que publicamos la semana pasada en areaTIC o bien crear una nueva (usando la plantilla de Visual Studio 2012 son 5 minutos).

Si usáis el ejemplo de la semana pasada sería conveniente modificar la configuración del proyecto para usar IIS Local (se cambia desde el apartado "web", pulsando botón derecho Propiedades sobre el proyecto). Por ejemplo en este caso yo estoy testeando con el servicio articulo que creamos la semana pasada, pongo un breakpoint en cada método y me aseguro que llegan bien y se serializan adecuadamente las peticiones que hacemos con el cliente:
//Prueba GET - recibe el articulo con ID = 5.
  //Articulo articulo = new areaTIC.RESTClient<Articulo>(new Uri("http://localhost/WebApi/"), "api/articulo").Get(5);

  //Prueba GET - recibe una lista de articulos
  //List<Articulo> articulos = new areaTIC.RESTClient<Articulo>(new Uri("http://localhost/WebApi/"), "api/articulo").Get();

  //Prueba POST que inserta el elemento articulo que enviamos como parámetro
  //new areaTIC.REST.RESTClient<Articulo>(new Uri("http://Localhost/WebApi/"), "api/articulo").Post(new Articulo() { Titulo = "1", Descripcion = "2", Autor = "3"});
  
  //Prueba PUT que actualizará el valor del artículo con ID = 1.
  //new areaTIC.RESTClient<Articulo>(new Uri("http://localhost/WebApi/"), "api/articulo").Put(1,new Articulo() { Expediente = "1", DireccionNotaria = "2", FechaFirma = "3", Notario = "4" });

  //Prueba DELETE que elimina el articulo cuyo ID pasemos como parámetro.
  //new areaTIC.RESTClient<Articulo>(new Uri("http://localhost/WebApi/"), "api/articulo").Delete(1);
La clase articulo que hemos enviado como parámetro es una clase que he generado en cliente y tiene los mismos atributos que el modelo "Articulo" que creamos en la WebAPI de servidor. A parte si os fijáis en la clase RESTClient métodos POST y PUT usamos un AreaTICJavascriptSerializer para devolver una lista de KeyPairValue a partir de uno o varios objetos. Os paso también el código:
namespace AreaTIC.REST
{
  public class AreaTICJavaScriptSerializer : JavaScriptSerializer
  {
    public List<KeyValuePair<string, string>> GetKeyValuePair(string jSonText) 
    {
      List<KeyValuePair<string, string>> lstResultado = new List<KeyValuePair<string, string>>();

      //separar la cadena jSon para tener los objetos
      MatchCollection mcElementosLista = Regex.Matches(jSonText, @"(?<=\{)(.*?)(?=\})", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);

      foreach (Match mElemento in mcElementosLista)
      {
         //separar por clave : valor
         MatchCollection mcRelacionClavesValores = Regex.Matches(mElemento.Value, @"(?<=(^|\,)).*?(?=(\,|$))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
         foreach (Match mClaveValor in mcRelacionClavesValores)
         {
            //separar cadena por ':' y asignar valores
            string[] strSeparador = { "\":\"", "\":null" };
            string[] strClaveValor = mClaveValor.Value.Split(strSeparador, StringSplitOptions.None);
            if (strClaveValor.Length == 2)
            {
               string strClave = strClaveValor[0];
               string strValor = strClaveValor[1];

               if (strClave.StartsWith("\""))
                 strClave = strClave.Substring(strClave.IndexOf("\"") + "\"".Length);

               if (strClave.EndsWith("\""))
                 strClave = strClave.Substring(0, strValor.IndexOf("\""));

               if (strValor.StartsWith("\""))
                 strValor = strValor.Substring(strValor.IndexOf("\"") + "\"".Length);

               if (strValor.EndsWith("\""))
                 strValor = strValor.Substring(0, strValor.IndexOf("\""));

               if (strValor.Length == 0)
                 strValor = "null";

               KeyValuePair<string, string> kvpAux = new KeyValuePair<string, string>(strClave, strValor);
               lstResultado.Add(kvpAux);
            }
         }
      }

      return lstResultado;
   }

   public List<KeyValuePair<string, string>> GetKeyValuePair(object objectValue) 
   {
      try
      {
         List<KeyValuePair<string, string>> lstResultado = new List<KeyValuePair<string, string>>();
         System.Reflection.PropertyInfo[] propiedadesObjeto = objectValue.GetType().GetProperties();
         foreach (System.Reflection.PropertyInfo propiedad in propiedadesObjeto)
         {
            //obtener el nombre y valor de la propiedad a añadir en el Diccionario
            string strValor = "";
            object o = propiedad.GetValue(objectValue, null);
            if (o != null)
              strValor = o.ToString();
            if (strValor.Length == 0)
              strValor = "null";

            KeyValuePair<string, string> kvpAux = new KeyValuePair<string, string>(propiedad.Name, strValor);
            lstResultado.Add(kvpAux);
         }

         return lstResultado;
       }
       catch (Exception ex)
       {
         throw ex;
       }
    }
  }
}
Por último comentar que desde cliente apuntamos a localhost como servidor asumiendo que tenemos publicado en el IIS Local un site con la WebAPI correspondiente. Espero os sea útil el cliente genérico, cualquier duda o corrección ya sabéis. Que vaya bien la semana y recordar como siempre que podéis seguir areaTIC en las redes sociales!

5 comentarios:

Unknown dijo...

Hola siguiendo tu tutorial, que tipo de solucion creo...??
Gracias.
Saludos,

Unknown dijo...

Hola tienes la ultima actualización del cliente publicada....????
Gracias.
Saludos,

Carlos Cañizares dijo...

Hola, disculpa que voy un poco tarde. Si aún lo necesitas escríbeme a c.canizares@gmail.com y te paso el código actualizado.

Carlos Cañizares dijo...

la dirección es c.canizaresestevez@gmail.com...

Jorge dijo...

A mi me da error el JavaScriptSerializer, me dice que no encuentra dicha clase:

public class AreaTICJavaScriptSerializer : JavaScriptSerializer

Publicar un comentario