Routing with WCF

Today we face a problem in Production environment. We needed to route some WCF requests from one “publicly visible” server to an internal one. A typical routing scenario.

Fortunately these requests were received in a WCF service and this technology has a built-in routing feature since 4.0 version. To use it we don’t need to change any code, it is enough to modify the app.config/web.config of the services. Here we can see how to use it, step by step. All these XML code will be place inside system.serviceModel tag

First Step – Define the new Service

We have to define a new Routing service that will receive all the requests. It will, later, internally dispatch them depending on certain routing rules. The XML necessary is:

<services>
  <service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="routerConfig">
    <endpoint address=""
              binding="basicHttpBinding"
              contract="System.ServiceModel.Routing.IRequestReplyRouter"
              name="reqReplyEndpoint" />
  </service>
</services>

Two things that we may notice:

  • The services needs a behavior configuration. In this configuration, later, we will define the routing table.
  • We don’t define an address because we assume the service will be deployed in a IIS server. If we want to do some tests with Casinni, we will need to define an address.

Second Step – SVC hosting file for the new service

Since WCF 4.0 we don’t need the SVC files to host a WCF service, we can define them in app.config/web.config and the internal plumbery of WCF/IIS is smart enough to allow us calling the corresponding URL as if the SVC file really exists. This is the XML necessary for that:

<serviceHostingEnvironment>
  <serviceActivations>
    <add relativeAddress="RiskManagementServiceUAT.svc" service="System.ServiceModel.Routing.RoutingService, System.ServiceModel.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </serviceActivations>
</serviceHostingEnvironment>

Two more points to consider:

  • The relative address must contain an extension. For example, if we define it as “RiskManagementService.UAT”, without SVC extension, it will fail.
  • The service needs to specify the complete qualify name in this case, but it is not usual. In other projects that we have used this “virtual SVC” system, it was not necessary. Apparently there is some kind of limitation with the RoutingService.

Third Step – Service behavior configuration

We referenced a service behavior when we defined the service in step 1. Below we can see that configuration, that will need to indicate what routing table we have to use.

<behaviors>
  <serviceBehaviors>
    <behavior name="routerConfig">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <routing routeOnHeadersOnly="false" filterTableName="routingTable" />
    </behavior>
  </serviceBehaviors>
</behaviors>

Things to consider here:

  • Be sure the name of the behavior matches what you set when defined the service.
  • serviceMetadata may not be necessary in a production environment, if you don’t expect new clients to be created from your service WSDL.
  • serviceDebug MUST NOT be activated in production environment. It is a security risk.
  • In the routing tag we will indicate the name for the routing table to be used in this RoutingService service.

Fourth Step – Routing table

We are getting close to the end… We need to define the routing table. There is complex patterns that we may want to follow, like taking into account headers or contents in the messages. It is really useful for versioning, load-balancing and similar stuff. However, in our concrete scenario we only want to redirect all messages to the internal server, so we didn’t need a complex solution. This was our table:

<routing>
  <filters>
    <filter name="matchAll" filterType="MatchAll" />
  </filters>

  <filterTables>
    <filterTable name="routingTable">
        <add filterName="matchAll" endpointName="RiskService" />
    </filterTable>
  </filterTables>
</routing>

Three important points here:

  • Under filters tag we will define all possible filters. In this case we use the MatchAll filter. In bibliography there is a link for all filter types in WCF.
  • Under filterTables tag we define all the possible tables. We may have different tables for different routers. In our case, we define a “routingTable” in the service behavior in Step 3 and here it is.
  • As part of every table we will bound filters with endpoints. This is a very flexible approach, we can define as many possible filters as we may need for all our endpoints and later just correlate them in the filter table.
    • filterName will be the name of the filter to apply.
    • endpointName will be the name of the endpoint where the message will be routed when the filter is matched. This endpoint corresponds to a new element defined in the next step.

Fifth Step – Client endpoints

The router is a sum of input messages, routing logic and destination services. We have defined 2 of 3. In this point we will define what services will receive the messages once the routing is done. Here it is is the XML.

<client>
  <endpoint name="RiskService"
            address="http://appserver5/RiskManagementTool/RiskManagementService.svc"
            binding="basicHttpBinding"
            contract="*" />
</client>

So what do we have here? Just being “client” of the service where we want to send messages under certain criteria defined in Step 4. Pay attention to the name that we give to the endpoint, because this name must match the value for endpointName in the entries of the filter table defined in Step 4.

Conclusions

WCF is not a easy technology, but it is really powerful and, with enough knowledge and patience there is a great number of scenarios that you can cover with just defining appropriate XML configuration. No coding, no compiling, no deploying, just playing with the app.config/web.config and you get a very powerful Routing Service that is able to route base on headers, message content, protocol, etc.

Bibliography

[WCF] Nombres de máquina incorrectos en WSDL

Nos había pasado anteriormente y nos ha vuelto a pasar: WCF nos generaba el documento WSDL con el nombre de la máquina, no con su dominio.

Esto hacía que, por ejemplo, una máquina con nombre XXX en un dominio YYY, aunque su WSDL estuviera accesible en http://XXX/Servicio.svc?wsdl, internamente las rutas del documento WSDL hicieran referencia a http://XXX.YYY/, lo que hacía el WSDL inservible.

Parece ser que, hasta WCF 4.0, la solución no era sencilla: había que parchear este "bug”, “feature” o como queramos llamarlo, tal como se explica en este KB de Microsoft: http://support.microsoft.com/kb/971842/en-us

Sin embargo, a partir de WCF se ha incorporado la corrección y ya podemos tirar directamente de XML y olvidarnos de DLLs. El fragmento sería tan sencillo como éste:

<behaviors>
  <serviceBehaviors>
    <behavior>
      <!-- Necessary to avoid problems with domain/computer names -->
      <useRequestHeadersForMetadataAddress>
        <defaultPorts>
          <add scheme="http" port="80" />
        </defaultPorts>
      </useRequestHeadersForMetadataAddress>
    </behavior>
  </serviceBehaviors>
</behaviors>

Con esto ya tendremos nuestros WSDLs enlazando internamente de forma correcta el resto de elementos de que se compone.

WCF: OneWay y bloqueo del cliente

Cuando me estaba preparando el MCTS sobre WCF leí una afirmación sorprendente en el Training Kit oficial de Microsoft. En él venían a decir sus autores, en una traducción más o menos libre, lo siguiente sobre el patrón de intercambio de mensajes OneWay:

Dada la naturaleza OneWay del canal, uno podría pensar que, tan pronto el consumidor envía el mensaje, éste se procesa asíncronamente y el cliente es libre de hacer otras cosas. Sin embargo, la forma en que la maquinaria de WCF funciona significa que el consumidor, de hecho, se bloquea, incluso si el mensaje es OneWay, hasta que el dispatcher entrega el mensaje a una instancia del servicio, en la forma de una llamada a un método del objeto.

Teniendo en cuenta que OneWay es un patrón “Fire-and-Forget”, no encaja mucho que el cliente tenga que mantenerse bloqueado hasta que el servidor entrega el mensaje a un objeto que implemente el contrato de servicio. Es importante recordar que alguna de las particularidades de OneWay no hacen sino resaltar esta naturaleza “Fire-and-Forget”; por ejemplo, las siguientes dos propiedades:

  • Un método OneWay no soporta retornar ningún resultado, siempre será un método void. Tampoco soporta el atributo FaultContract, pues no pueden definirse errores que vaya a devolver al no tener capacidad de devolver nada.
  • Un método OneWay no soporta fluir transacciones entre el cliente y el servidor, y viceversa.

No parece que lo afirmado por los autores del Training Kit encaje mucho con estas propiedades (y otras que se quedan en el tintero) de OneWay. Así que lo mejor es salir de dudas con un pequeño ejemplo, para ver la validez de dicho comentario.

La forma más sencilla de poder comprobarlo es aprovecharse de la forma en que está definido el pipeline de WCF. Son varios los puntos de extensibilidad que tiene este framework. Si colocáramos en uno de ellos una clase que retuviera la entrega del mensaje a un objeto servidor, podríamos comprobar si efectivamente el cliente sigue bloqueado. Para comprobarlo, vamos a usar un Message Inspector.

Inyectar un Message Inspector

Vamos a partir de un nuevo proyecto de librería WCF. La estructura que nos genera Visual Studio nos vale; dejaremos únicamente un método que no devuelva nada, como se ve a continuación.

/// <summary>
/// One Way Service.
/// </summary>
[ServiceContract]
public interface IService
{
    /// <summary>
    /// OneWay method.
    /// </summary>
    /// <param name="value">Value to send.</param>
    [OperationContract(IsOneWay=true)]
    void SendData(int value);
}

La implementación de este contrato de servicio no tiene demasiada importancia, con saber que deberíamos colocar un breakpoint al comienzo del método para saber en qué momento es realmente invocado, es suficiente.

Vamos ahora a empezar a crear el Message Inspector. Crearemos una clase que implemente la interfaz IDispatchMessageInspector e implementaremos uno de sus métodos.

/// <summary>
/// My message inspector class.
/// </summary>
public class MyMessageInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(
        ref System.ServiceModel.Channels.Message request, 
        System.ServiceModel.IClientChannel channel, 
        System.ServiceModel.InstanceContext instanceContext)
    {
        // Crear un "replicador" de mensajes y usarlo para obtener una copia del mismo
        MessageBuffer buffer = request.CreateBufferedCopy(int.MaxValue);
        string messageContent = buffer.CreateMessage().GetReaderAtBodyContents().ReadOuterXml();
        System.Diagnostics.Debug.WriteLine(messageContent);
        
        // Asignar una copia sin leer del mensaje en request, para que otros 
        // componentes del pipeline de WCF puedan leerlo sin fallar.
        request = buffer.CreateMessage();

        // Devolver null como resultado, que será lo que reciba el metodo BeforeSendReply
        // en el parámetro correlationState
        return null;
    }

    public void BeforeSendReply(
        ref System.ServiceModel.Channels.Message reply, 
        object correlationState)
    {
    }
}

En cuanto al método AfterReceiveRequest, se ejecuta en el camino de subida del mensaje desde la red hacia el objeto que va a servir esta petición. Queremos mostrarlo en la consola, para lo cual vamos a crear un buffer y generar una copia del mismo. Es importante usar el buffer en primer lugar, puesto que un mensaje no puede leerse dos veces. Si lo hiciéramos con el parámetro request, luego no podría leerse otra vez por otros componentes del pipeline de WC y cada petición fallaría irremediablemente.

En cuanto al método BeforeSendReply, no queremos hacer nada especial con él así que simplemente dejamos pasar el mensaje reply, sin leerlo para no tener el problema antes comentado de lecturas. Como curiosidad, decir que lo que devuelve el método AfterReceiveRequest lo recibe el método BeforeSendReply en su parámetro correlationState, para poder relacionar ambas llamadas entre sí.

Ahora que ya tenemos el Inspector, es el momento de modificar la configuración de WCF para usarlo. Para ello tenemos que crear una clase que modifique el comportamiento del endpoint sobre el que vamos a escuchar. Para ello, tendremos que implementar la interfaz IEndpointBehavior.

/// <summary>
/// Custom endpoint behavior.
/// </summary>
public class MyEndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, 
        System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
        System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

De esta interfaz, como se puede observar, sólo nos interesa implementar el método ApplyDispatchBehavior, puesto que nuestro MessageInspector sólo va a actuar en el lado del servidor. Es importante no dejarnos ningún método con su implementación por defecto, puesto que tirar una NotImplementedException en cualquiera de ello tendría consecuencias indeseadas.

Ya sólo nos queda una última clase para tener todo el código listo. Este comportamiento personalizado para el endpoint necesitamos configurarlo de algún modo. La forma más limpia siempre es a través del fichero app.config, pero para ello necesitamos representar este comportamiento personalizable como un elemento de configuración. Esto podemos hacerlo creando una clase que herede de BehaviorExtensionElement, clase que por otra parte cargará nuestro comportamiento custom para el endpoint. El código sería algo así:

/// <summary>
/// Custom behavior extension element for the custom endpoint behavior.
/// </summary>
public class MyBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(MyEndpointBehavior); }
    }

    protected override object CreateBehavior()
    {
        return new MyEndpointBehavior();
    }
}

¿Sencillo, verdad? La clase simplemente representa al comportamiento personalizado que deseamos añadir como configuración. ¿Y ahora esto cómo se usaría? Primero se cargaría la clase que acabamos de definir como una extensión de los behaviors, y posteriormente se definiría como nueva configuración para un endpoint. Finalmente se cargaría en el endpoint correspondiente esta nueva configuración. Sin embargo, con las novedades que trae WCF4 en la configuración, este último paso podemos saltárnoslo, sabiendo que a partir de ese momento todos los endpoints van a tener esa extensión cargada.

  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="myCustomEndpointBehavior" 
             type="OneWayService.MyBehaviorExtensionElement, OneWayService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>      
    </extensions>
    <behaviors>
      <endpointBehaviors>
        <behavior>
          <myCustomEndpointBehavior/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Como se puede ver, en primer lugar se carga la extensión y, a continuación, se define como parte del comportamiento de los endpoints. Al no darle nombre al endpointBehavior, se cargará para todos los endpoints existentes. Por último, he activado la generación del fichero WSDL para poder generar un proxy.

Probando el inspector

Bien, suponiendo que ya tenemos el servicio arriba y un proyecto de cliente correctamente creado. Tras añadir la referencia al servicio, podemos escribir algo como lo siguiente para ver si realmente nuestro cliente se bloquea o no.

using (ServiceReference.ServiceClient proxy = new ServiceReference.ServiceClient())
{
    Console.WriteLine("Calling the remote server...");
    proxy.SendData(int.MaxValue);
    Console.WriteLine("Remote server called...");
    Console.ReadLine();
}

Con esto, es momento de lanzar el cliente. Deberemos tener breakpoints tanto en el método que hemos implementado del inspector, como en la clase que implementa el servicio. Si todo va bien veremos la siguiente secuencia de pasos:

  1. El cliente invoca al servicio usando el proxy.
  2. Salta el breakpoint en el MessageInspector. Si ejecutamos cualquiera de las instrucciones del inspector, con ejecución paso a paso, veremos que inmediatamente salta el breakpoint del cliente. Por lo tanto, no se está bloqueando.
  3. El servidor se queda esperando a que sigamos la ejecución, para llegar al objeto servidor, donde el otro breakpoint debería saltar.

He probado el mismo código con las mismas condiciones (el servicio desplegado en IIS 7.0), pero con el framework anterior y el resultado es el mismo. Parece, por tanto, que OneWay sí es realmente un patrón “Fire-and-Forget” y no bloquea a los clientes

 

Bibliografía:

Microsoft .NET Framework 3.5 – Windows Communication Foundation. Self-Paced Training Kit

Descargas:

Orden de campos del DataContract

Cuando estaba preparándome la certificación de WCF, descubrí algo que es conveniente tener apuntado, pues puede dar bastantes quebraderos de cabeza: el orden en que aparecen los campos de información en los mensajes WCF una vez serializados. Estas son las reglas:

  • Si el contrato hereda de otro tipo, aparecen en primer lugar los elementos de ese tipo base.
  • Después, los elementos del propio contrato que no tengan la propiedad Order de DataMemberAttribute, ordenados alfabéticamente.
  • Por último, los elementos que tengan la propiedad Orden, según el valor asignado.

Me ha resultado curioso que aparezcan primero los elementos sin la propiedad Order. Seguramente haya una razón que desconozco para que éste sea el comportamiento, pero entiendo que puede llevar a error fácilmente. ¿Por qué? Pues porque si seleccionas un elemento que quieres que vaya primero, y le das la propiedad Order = 1, probablemente, de desconocer estas reglas, esperarás que con esto sea suficiente para que aparezca en primer lugar en el mensaje WCF.

Pero no, será necesario que asignes valores a todos los otros elementos, para que no se antepongan al que has elegido tú, por aquello de no tener propiedad Order.

Por último, destacar que estas reglas aplican también al orden en que aparecen los elementos que añadamos a un MessageContract con el atributo MessageBodyMemberAttribute.

Algún día tengo que buscar porqué la gente de .NET decidió esto así.

WCF: Validación de certificados

Cuando se está en un entorno de desarrollo, es bastante común utilizar certificados “auto-firmados” (self-signed) para bindings que requieran seguridad.

Sin embargo, WCF no acepta este tipo de certificados, lanzando una excepción de tipo SecurityNegotiationException, con un mensaje acompañando del estilo a “Could not establish trust relationship for the SSL/TLS secure channel with authority ‘localhost:8732’”. ¿Qué hacer? Es fácil, simplemente hay que “engañar” al cliente para que acepte este certificado que el servidor nos está ofreciendo para realizar la conexión SSL, aunque esté firmado por sí mismo.

Para ello, en el cliente, es necesario introducir el siguiente código:

   1: static void Main(string[] args)
   2: {
   3:     using (DemoService.GetHeadersClient proxy = new DemoService.GetHeadersClient())
   4:     {
   5:         ServicePointManager.ServerCertificateValidationCallback += 
   6:             new System.Net.Security.RemoteCertificateValidationCallback(ValidateCertificate);
   7:  
   8:         // Do stuff with proxy object
   9:     }            
  10: }
  11:  
  12: public static bool ValidateCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)
  13: {
  14:     return true;
  15: }

El método definido será invocado por WCF en el momento de la validación del certificado, asegurándonos que el cliente va a aceptarlo en cualquier caso.

Por supuesto, esto es algo totalmente desaconsejado en cualquier entorno de producción. Pero para salir del paso en desarrollo, viene muy bien.

Respuesta original vista aquí.