wtorek, 6 maja 2014

Kontrola wiadomości WCF - IDispatchMessageInspector

Jeśli korzystaliście kiedyś z WCF (lub pisaliście jakikolwiek inny serwis z wykorzystaniem SOAP), możliwe że mieliście zaimplementowany mechanizm kontroli wiadomości - np. mechanizm logowania wiadomości, które przychodzą do serwera. W tym poście chciałbym opisać jak zaimplementować taki mechanizm kontroli wiadomości przed jej przetworzeniem przez serwis WCF lub przed wysłaniem odpowiedzi do klienta. Dlaczego właściwie chcielibyśmy robić taką kontrolę? Tak wcześniej wspomniałem np. aby logować takie wiadomości na przykład w celach statystycznych lub w przypadku gdy chcemy coś szczególnego dodać do naszej wiadomości - odpowiedzi  na przykład w nagłówku przed wysłaniem jej do aplikacji klienckiej.
W WCF'ie możemy taką funkcjonalność zaimplementować w "dość prosty" sposób.  (Prosty dla kogoś, kto już miał wcześniej styczność z WCF'em).

Na początku musimy stworzyć klasę, w której będziemy robić inspekcję wiadomości WCF - to własnie tak klasa będzie implementowała interfejs IDispatchMessageInspector. Interfejs ten posiada dwie metody:

  • AfterReceiveRequest - wykonywana w momencie gdy wiadomość została odebrana przez serwis lecz przed przekazaniem jej do odpowiedniej metody serwisu
  • BeforeSendReply - wywoływana po przetworzeniu naszej wiadomości przez odpowiednią metodę i przed wysłaniem odpowiedzi do aplikacji klienckiej

Dodatkowo, z racji tego iż nasza klasa będzie określała pewne zachowanie dla serwisu lub endpointa musi również implementować odpowiednio:
IServiceBehavior lub IEndpointBehavior lub oba interfejsy jeśli zdecydujemy się, że może być używana zamiennie dla serwisu i endpointa.
Kod naszej klasy wygląda następująco:

public class ServerInspector : IDispatchMessageInspector, IEndpointBehavior, IServiceBehavior
    {
        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            Debug.WriteLine("Called after an inbound message has been received but before the message is forwared to the target operation");
            return request;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Debug.WriteLine("Called after the operation has returned but before the reply is actually relayed");
        }

        #endregion

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        #endregion

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (var endpoint in dispatcher.Endpoints)
                {
                    endpoint.DispatchRuntime.MessageInspectors.Add(this);
                }
            }
        }

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

        #endregion
    }

Należy zwrócić uwagę na metody ApplyDispatchBehavior w których dodajemy do naszego endpointa aktualnego inspektora. Bez dodania inspektora, nasz kod nie będzie działał. Jeśli chodzi o samą kontrolę wiadomości to może być ona wykonywana w 2 metodach o których wcześniej wspomniałem : AfterReceiveRequest oraz BeforeSendReply. Ja akurat w tych metodach dodałem zwykłe wyświetlanie wiadomości, jednak należy pamiętać, że własnie te metody przeznaczone są do przeprowadzania właściwej kontroli wiadomości WCF. 

Kolejnym krokiem jest dodanie "klasy rozszerzającej" która będzie dziedziczyła z BehaviorExtensionElement. Musimy mieć taką klasę ponieważ jak zobaczymy z chwilę, w pliku konfiguracyjnym ustawimy sobie takie własnie rozszerzenie zachowania naszego serwisu. Najważniejszą sprawą jaką trzeba zapamiętać, że taka klasa musi dziedziczyć z klasy BehaviorExtensionElement, w przeciwnym wypadku kompilator nam zaprotestuje i wywali błąd. W związku z tym nasza klasa rozszerzająca wygląda następująco:
public class ServerInspectorExtension : BehaviorExtensionElement
{
   public override Type BehaviorType
   {
      get { return typeof(ServerInspector); }
   }

   protected override object CreateBehavior()
   {
      return new ServerInspector();
   }
}
Jak widzimy klasa ta tworzy nową instancję naszego inspektora, którego dodaliśmy wcześniej. Ostatnim krokiem jest skonfigurowanie "behaviorExtension" w pliku konfiguracyjnym. Nasze rozszerzenie wygląda następująco:

   
      
   

Takie rozszerzenie przypisujemy do odpowiedniego "behavior'a":

   
      
   

Brak komentarzy:

Prześlij komentarz