2012-09-10 12 views
7

Actualmente estoy trabajando en la aplicación WPF con un backend WCF. Hemos implementado una solución de registro de clientes y una solución de registro de servidores para el manejo de excepciones, y funcionan muy bien, pero a menudo es difícil vincular la información por cable. Si se produce una excepción en el servidor, quería una forma de pasar un token de excepción a través del cable para poder registrarlo en el cliente con la excepción. De esta forma, cuando estoy solucionando un error del cliente, puedo correlacionarlo fácilmente con la excepción del servidor.Inyección de contrato de falla WCF utilizando Castle Dynamic Proxy Generation

Me gustaría dar un poco más de información acerca de nuestra arquitectura, y luego explicaré mi problema.

Nuestra implementación de WCF es un poco más robusta que el método listo para usar para establecer una referencia de servicio. Implementamos la generación de proxy dinámico en el cliente. Logramos esto mediante la creación de interfaces de servicios web que comparten el cliente y el servidor, y utilizamos la clase Castle.DynamicProxy.ProxyGenerator y el método CreateInterfaceProxyWithoutTarget para crear el proxy. Además, cuando llamamos al método CreateInterfaceProxyWithoutTarget, especificamos una implementación de IInterceptor. En el servidor hay tres clases principales que se usan para nuestro comportamiento de rastreo y falla:

FaultOperationInvoker (Implementa IOperationInvoker): Intenta llamar al método de servicio utilizando IOperationInvoker.Invoke. Si se trata de una excepción de error, escriba de nuevo, si es una excepción, intenta determinar si hay un contrato de falla que tenga un tipo de detalle específico, y si es así, luego envuelva y genere una nueva excepción de error con la información detallada.

internal class FaultOperationInvoker : IOperationInvoker 
    { 
     IOperationInvoker innerOperationInvoker; 
     FaultDescription[] faults; 

     public FaultOperationInvoker(IOperationInvoker invoker, FaultDescription[] faults) 
     { 
      this.innerOperationInvoker = invoker; 
      this.faults = faults; 
     } 

     #region IOperationInvoker Members 

     object[] IOperationInvoker.AllocateInputs() 
     { 
      return this.innerOperationInvoker.AllocateInputs(); 
     } 

     object IOperationInvoker.Invoke(object instance, object[] inputs, out object[] outputs) 
     { 
      try 
      { 
       return this.innerOperationInvoker.Invoke(instance, inputs, out outputs); 
      } 
      catch (FaultException e) 
      { 
       ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation."); 

       //allow fault exceptions to bubble out 
       throw; 
      }   
      catch (Exception e) 
      { 
       Type exceptionType = e.GetType(); 

       ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation."); 

       //if the excpetion is serializable and there operation is tagged to support fault contracts of type WcfSerivceFaultDetail 
       if (faults != null && (faults.OfType<WcfServiceFaultDetail>().Any() || faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) > 0)) 
       { 
        throw new FaultException<WcfServiceFaultDetail>(new WcfServiceFaultDetail(true, e), "Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType)); 
       } 
       else 
       { 
        throw new FaultException("Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType)); 
       } 

      } 
     } 

FaultOperationBehavior (Implementa IOperationBehavior) señala la operación invocador despacho al invocador operación de fallo descrito anteriormente.

[AttributeUsage(AttributeTargets.Method)] 
    public class FaultOperationBehavior : System.Attribute, IOperationBehavior 
    { 
     #region IOperationBehavior Members 

     void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, 
      System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } 


     void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, 
      System.ServiceModel.Dispatcher.ClientOperation clientOperation) { } 

     void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, 
      System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) 
     {   
      dispatchOperation.Invoker = new FaultOperationInvoker(dispatchOperation.Invoker, operationDescription.Faults.ToArray()); 
     } 

     void IOperationBehavior.Validate(OperationDescription operationDescription) { } 

     #endregion 
    } 

ExceptionTraceBehavior (atributo Inherits, implementos IServiceBehavior) para el manejo de excepciones que implementa IServiceBehavior. También tenemos una clase (FaultOperationBehavior)

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = true)] 
public class ExceptionTraceBehavior : Attribute, IServiceBehavior 
{ 
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 
    { 
    } 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
    { 
     foreach (var ep in serviceDescription.Endpoints) 
     { 
      foreach (var operation in ep.Contract.Operations) 
      { 
       if (operation.Behaviors.Contains(typeof(FaultOperationBehavior))) 
        continue; 

       operation.Behaviors.Add(new FaultOperationBehavior()); 

       //Check to see if this operation description contains a wcf service fault detail operation. If it doesn't, add one. 
       if (operation.Faults != null && (operation.Faults.Count == 0 || operation.Faults.Count > 0 && operation.Faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) == 0)) 
       { 
        FaultDescription faultDescription = new FaultDescription(operation.Name); 
        //faultDescription.Name = "WcfServiceFaultDetail"; 
        //faultDescription.Namespace = ""; 
        faultDescription.DetailType = typeof(WcfServiceFaultDetail); 
        operation.Faults.Add(faultDescription); 
       } 
      } 
     } 
    } 

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
    { 
    } 
} 

Cada una de nuestras interfaces de servicio tiene una aplicación concreta. Todos nuestros servicios también heredan nuestra clase de servicio base que está decorada con el atributo ExceptionTrace.

Entonces, ahora con la información de fondo, este es el problema. Quiero que cada operación de servicio tenga un contrato de falla con el tipo de detalle WCFServiceFaultDetail, pero no quiero poner un atributo FaultContract en cada operación de servicio. Como puede ver en ExceptionTraceBehavior, descubrí cómo agregar el contrato de falla programáticamente y funciona muy bien para agregar el error a la operación. Cuando se detecta una antigua excepción normal en el invocador de operación, se encuentra que existe el contrato de falla adecuado y se genera un nuevo FaultExcption. Sin embargo, cuando la excepción queda atrapada en el cliente, cae en el código catch (FaultExcection fe) en lugar del código catch (FaultException fe).

Sin embargo, si elimino el código para agregar programáticamente el contrato de falla, adoro cada operación de servicio con [FaultContract (typeof (WcfServiceFaultDetail))], el cliente detecta la excepción como se esperaba.

Lo único que puedo entender es que, dado que el proxy se genera dinámicamente desde la interfaz y no desde un WSDL u otro metadato, y no hay ningún defecto en la decoración del contrato, mi contrato de fallas programáticas no siendo honrado

Con esto en mente, he tratado de encontrar la manera de agregar el contrato de falla en la implementación de IInterceptor, pero no he tenido éxito.

Así que espero que alguien ya haya hecho esto y pueda proporcionar algunos detalles. Cualquier ayuda es apreciada.

+0

Para correlación de error de cliente/servidor, ¿miró Propagación de ID de actividad? – Paciv

+0

No lo hice, voy a echar un vistazo a eso. Gracias por el consejo. – BernicusMaximus

Respuesta

0

No soy un experto en WCF pero tengo mis manos un poco sucias.

Creo que tienes razón. Los contratos de falla se resuelven desde los metadatos del ensamblado al crear un ChannelFactory de algún tipo. Como sus interfaces no están decoradas con los atributos apropiados de FaultContract, su cliente utiliza un contrato de falla predeterminado sin ningún detalle.

Agregar atributos de FaultContract a sus métodos de interfaz en tiempo de ejecución probablemente tampoco funcionará.

Una solución podría ser generar y usar tipos dinámicamente en tiempo de ejecución, para generar fábricas de canales.

Nunca he hecho esto pero creo que esto podría hacerse comenzando con DefineDynamicAssembly de AppDomain.

+0

Eso es realmente lo que estamos haciendo. Creamos ChannelFactory del tipo de interfaz de servicio. Intenté agregar el comportamiento justo antes de llamar al método CreateChannel, y eso no parece funcionar. – BernicusMaximus

+0

Entiendo su punto, pero no lo estoy sugiriendo. "La adición de atributos FaultContract a sus métodos de interfaz en tiempo de ejecución probablemente tampoco funcionará". De hecho, estoy sugiriendo algo diferente. Estoy diciendo que una forma de lograr lo que intenta hacer es crear tipos, módulos, metadatos y ensamblajes en tiempo de ejecución, almacenarlos en caché y usarlos. Es por eso que estaba apuntando a DefineDynamicAssembly de AppDomain. –

+0

Lo siento, no entendí bien. Esa es una idea muy interesante, pero no estoy seguro de la viabilidad de la misma para nuestros propósitos. – BernicusMaximus

Cuestiones relacionadas