WCF as REST. Deeping as usual: SEO Routing, Newtonsoft.Json and more !!

Muy buenas,

Entre patines, mountaintbike, despedidas de soltero, resfriado/virus, playa y viajes de verano, saco un hueco para comentar este tema que tenía en borrador y que se estaba haciendo de rogar, es decir, como exponer servicios WCF como REST, haciendo uso de Routing para el enrutamiento basado en SEO y otras buenas prácticas al respecto.

Aunque la recomendación para la creación de nuevos servicios REST es WebApi, existirán ocasiones en la que esto no sea posible, para estos casos, he aquí las pautas a seguir a partir de nuestros servicios WCF:

1) Incluimos en los proyectos .NET la referencia a la dll “System.Web.Routing.dll” y “System.ServiceModel.Activation”.

2) Establecemos la compatibilidad ASP.NET en los servicios:

Incluimos en el Service: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

Incluimos en el web.config, en la sección “system.serviceModel”:

   1: <serviceHostingEnvironment

   2:    aspNetCompatibilityEnabled="true"

   3:    multipleSiteBindingsEnabled="true" />

3) Añadimos a nuestro proyecto un fichero “global.asax” e ncluimos en el método “Application_Start” instrucciones similares a las siguientes para cada uno de los servicios a enrutar:
   1: RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(Service1)));

   2: RouteTable.Routes.Add(new ServiceRoute("historial", new WebServiceHostFactory(), typeof(HistorialService)));

   3: RouteTable.Routes.Add(new ServiceRoute("private/buzon", new WebServiceHostFactory(), typeof(BuzonService)));

4) Modificamos los contracts asegurando la correcta “UriTemplate”:

   a) Ejemplo GET:

   1: [OperationContract]

   2: [WebGet(    

   3:     UriTemplate = "?data={value}",

   4:     ResponseFormat = WebMessageFormat.Json,

   5:     RequestFormat = WebMessageFormat.Json)]

   6: string GetData(int value);

   b) Ejemplo 2 GET:

   1: [OperationContract]

   2: [WebGet(    

   3:     UriTemplate = "{value}",

   4:     ResponseFormat = WebMessageFormat.Json,

   5:     RequestFormat = WebMessageFormat.Json)]

   6: string GetData(string value);

   c) Ejemplo PUT
   1: [OperationContract]

   2: [WebInvoke(

   3:     Method = "POST",    

   4:     UriTemplate = "",

   5:     BodyStyle = WebMessageBodyStyle.WrappedRequest,

   6:     ResponseFormat = WebMessageFormat.Json,

   7:     RequestFormat = WebMessageFormat.Json)]

   8: Stream Enviar(int IdMensaje, string Respuesta);

5) En los ficheros “.svc” de los servicios incluimos la siguiente instrucción añadiéndola a la existente generada por defecto:

   1: <%@ ServiceHost ...  Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

6) Incluimos en el web.config la siguiente configuración con objeto de poder navegar desde nuestro browser a la definición de los servicios Rest:

   1: <sectionGroup

   2:     name="system.serviceModel"

   3:     type="System.ServiceModel.Configuration.ServiceModelSectionGroup, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

   4:     <section name="standardEndpoints"

   5:         type="System.ServiceModel.Configuration.StandardEndpointsSection, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

   6: </sectionGroup>

Dentro de la sección “System.ServiceModel”:
   1: <standardEndpoints>  

   2:     <webHttpEndpoint>    

   3:         <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true">    

   4:         </standardEndpoint>  

   5:     </webHttpEndpoint>

   6: </standardEndpoints>

Para poder acceder a la información, bastará con incluir el sufijo “/help” a la url de cada servicio REST, de manera que la información obtenida sea similar a la siguiente:

image

imageimage

Nota: La ruta completa “SEO” se compone por la suma de los tres siguiente elementos:

  • Servidor: HTTP(S)://<SITE | DIRECTORIO VIRUTAL>[:PUERTO]/
  • Routing: “”
  • UriTemplate “{value}”, o, “?data={value}”, según el método del servicio a utilizar.

    De manera que la url completa sería: http://localhost/WcfService1/1

     

    7) [Opcional] Sólo en caso de publicar el servicio REST en  IIS6: Configuramos el Web Site o el Directorio Virtual del IIS como sigue:

    imageimage

    Donde la extensión será “C:WindowsMicrosoft.NETFrameworkv4.0.30319aspnet_isapi.dll” o “C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll”, asegurando que el check  “Comprobar si el archivo existe” está desmarcado.

    8) Uso de entidades JSON

    Una vez el servicio ya está adaptado a un servicio REST, ahora necesitamos revisar o crear o modificar las entidades de tipo JSON para nuestros métodos. Para ello utilizaremos la dll “Newtonsoft.Json”  que puede ser descargada desde aquí, o incluso desde NuGet: “PM> Install-Package Newtonsoft.Json”.

    Esta dll nos permitirá trasformar nuestras entidades en JSON, transformar String del tipo JSON en entidades y vicebersa, entre otras cosas, facilitando toda esta labor de conversión/transformación de una manera más fácil a como se podría hacer con la socialización por defecto a JSON de .NET, por tanto su uso es recomendado.

    Cada método debería retornar un entidad JSON con la información esperada, sin embargo también se hace necesario, en ocasiones, el retorno de códigos y descripciones de error. En este caso, la recomendación sería utilizar una entidad común de la forma siguiente, donde en cado de que Error=0, Mensaje será NULL y Data tendrá el dato esperado.

       1: [JsonObject]

       2: public class ResponseJson<T> 

       3: {

       4:     [JsonProperty]

       5:     public int Error { get; set; }

       6:     

       7:     [JsonProperty]

       8:     public string Messaje { get; set; }     

       9:     

      10:     [JsonProperty]

      11:     public T Data { get; set; }

      12:  }

    Adicionalmente, si tenemos otro tipo de requisitos que no se adapte a este formato, siempre podemos retornar un “Stream”. Podría ser un “String”, pero, en tal caso el JSon resultante no es correcto, incluyendo el carácter “”” con carácter especial “””:

       1: [JsonObject]

       2: public class ResponseJson<T> 

       3: {    

       4:     [JsonProperty]    

       5:     public int Error { get; set; }    

       6:  

       7:     [JsonProperty]    

       8:     public T Mensaje { get; set; }

       9: }

    En este caso, si Error=0, Mensaje tomará el dato esperado, mientras que si Error != 0, Mensaje tendrá un String con la descripción del error. En el siguiente snippet podemos ver un ejemplo de como quedaría nuestro método en términos generales para este último caso:

       1: public Stream Consultar(int page)

       2: {

       3:     int retCode = 0;

       4:     object retMessage = null;

       5:  

       6:     WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";

       7:  

       8:     try

       9:     {         

      10:         //

      11:         // Procesar y obtener objeto obj1

      12:         //

      13:  

      14:         retMessage = new PageResponseJson<Entidad1Json>()

      15:         {

      16:             Pagina = 1,

      17:             PaginasTotales = obj1.TotalPages,

      18:             RegistrosTotales = obj1.TotalRecords,

      19:             Listado = obj1.Listado

      20:         };

      21:  

      22:     }

      23:     catch (Exception ex)

      24:     {

      25:         ExceptionHelper.HandleException(ex);

      26:  

      27:         retCode = -1;

      28:         retMessage = ex.Message;

      29:     }

      30:  

      31:     var result = new

      32:     {

      33:         Error = retCode,

      34:         Mensaje = retMessage

      35:     };

      36:  

      37:     return new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result)));

      38: }

    Donde:

    • PageResponseJson, es un entidad Json para el retorno de listados paginados,
    • ExceptionHelper, es simplemente una clase para el tratamiento de excepciones con Entlib.

    Nota: El hecho de retornar un “Stream”, se debe a que, al devolver un objeto que puede ser de dos tipos, la serialización/deserialización falla debido a que la exposición de un servicio WCF como REST no serializa debidamente el string JSON resultante. Bien generando el string Json con caracteres especiales “” o bien, no realizándose la deserialización al tipo a retornar de manera correcta o incluso no funcionando. Otra solución, pasaría por crear el conversor adecuado, sin embargo, considero más eficiente retornar el Stream evitando así una conversión adicional.

    Con todo lo anterior en mente, nuestros servicios WCF serán servicios REST/JSON pudiendo ser consumidos 100% como tales cumpliendo además todas las especificaciones al respecto.

    Recodemos también que, Fiddler, va a permitirnos asegurar que nuestros JSON en las peticiones HTTP son totalmente correctos y se adaptan a las especificaciones requeridas.

    ¡Ahora si que dará que pensar: WCF as REST o WebAPI! ¿Tienes un departamento que controla 100% de WCF y/o WebApi, no quieres ser usada? ¿Por qué solución optarías? Si aún te quedan dudas, echa un vistazo a este link de MSDN: http://msdn.microsoft.com/en-us/library/jj823172.aspx, o incluso a este otro si tienes ganas de saber más sobre todo este tema: http://www.codeproject.com/Articles/341414/WCF-or-ASP-NET-Web-APIs-My-two-cents-on-the-subjec.

     

    Feliz verano/vacaciones a Tod@s
    Saludos @Higuera la Real
    JuanluElGuerre