Zachowania w WCF umożliwiają rozszerzanie funkcjonalności poprzez np. doczepianie niestandardowych modułów. Do dyspozycji mamy 4 typy, różniące się zasięgiem obejmowania: Service, Operation, Endpoint, Contract. Utworzenie zachowania sprowadza się do implementacji odpowiedniego interfejsu, który zawiera m.in. następujące metody:
-
AddBindingParameters – umożliwia przekazanie dodatkowych parametrów.
-
Validate –waliduje (np. czy usługa może zostać uruchomiona).
-
ApplyDispatchBehavior/ApplyClientBehavior – służy do dodawania rozszerzeń (np. Message Inspector).
Zacznijmy od zachowania przeznaczonego dla usługi (Service). Zachowanie musi implementować interfejs IServiceBehavior. Obejmuje w swoim zasięgu całą usługę (wszystkie endpointy). Przykład doczepiania Message Inspector’a do wszystkich endpointów:
public class ServiceBehaviour : IServiceBehavior { #region IServiceBehavior Members 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 (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher endpoint in dispatcher.Endpoints) { endpoint.DispatchRuntime.MessageInspectors.Add(new ExampleMessageInspector()); } } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion }
ServiceBehaviour może zostać doczepiony przez kod, plik konfiguracyjny lub atrybut.
Zachowania dla endpoint muszą implementować interfejs IEndpointBehavior. Zasięgiem obejmują oczywiście pojedynczy endpoint. Zachowanie można doczepić za pomocą kodu lub pliku konfiguracyjnego – brak możliwości wykorzystania atrybutu. Przykład:
public class CustomBehaviour : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { InspectorExample1 inspector = new InspectorExample1(); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector); } public void Validate(ServiceEndpoint endpoint) { } }
Analogicznie zachowanie dla kontraktu musi implementować IContractBehavior. Zachowanie może zostać dodane wyłącznie przez atrybut lub kod. Przykład ustawiania własnej klasy zarządzającej instancjami usługi:
public class SingletonBehaviorAttribute : Attribute, IContractBehaviorAttribute, IContractBehavior { #region IContractBehaviorAttribute Members public Type TargetContract { get { return typeof(ISampleService); } } #endregion #region IContractBehavior Members public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection parameters) { return; } public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { return; } public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch) { dispatch.InstanceProvider = new ObjectProviderBehavior("Custom ObjectProviderBehavior constructor."); } public void Validate(ContractDescription description, ServiceEndpoint endpoint) { return; } #endregion }
Zachowania dla pojedynczych operacji muszą implementować IOperationBehavior i mogą zostać dodane za pomocą kodu lub atrybutu. Przykład dodania Parameter Inspector:
#region IOperationBehavior Members public void AddBindingParameters( OperationDescription operationDescription, BindingParameterCollection bindingParameters ) { return; } public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { clientOperation.ParameterInspectors.Add(new Inspector()); } public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) { dispatchOperation.ParameterInspectors.Add(new Inspector()); } public void Validate(OperationDescription operationDescription){ return; }
Parameter Inspectors pełnią rolę analogiczną do Message Inspector jednak ograniczają się do metod a nie całych wiadomości SOAP np:
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { Console.WriteLine( "IParameterInspector.AfterCall called for {0} with return value {1}.", operationName, returnValue.ToString() ); } public object BeforeCall(string operationName, object[] inputs) { Console.WriteLine("IParameterInspector.BeforeCall called for {0}.", operationName); return null; }
Wiemy już jak implementować zachowania. Pozostało wyjaśnić w jaki sposób możemy dodać zachowania tak aby dana usługa czy metoda wykorzystywała je. Zacznijmy od doczepiania różnych typów zachowań za pomocą kodu:
- ServiceBehaviour:
using (ServiceHost host = new ServiceHost(typeof(Test))) { host.Description.Behaviors.Add(customBehavour); }
- EndpointBehaviour:
foreach (ServiceEndpoint se in host.Description.Endpoints) se.Behaviors.Add(new CustomBehavior());
- OperationBehavour:
foreach (ServiceEndpoint se in host.Description.Endpoints) { foreach (OperationDescription od in se.Contract.Operations) { od.Behaviors.Add(new CustomBehavior); } }
- ContractBehaviour:
foreach (ServiceEndpoint se in host.Description.Endpoints) { se.Contract.Behaviors.Add(new CustomBehavior); }
Możemy również doczepić zachowanie za pomocą pliku konfiguracyjnego. W tym przypadku należy najpierw stworzyć klasę dziedziczącą po BehaviorExtensionElement:
public class CustomBehaviorExtensionElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new CustomBehaviour(); } public override Type BehaviorType { get { return typeof(CustomBehaviour); } } }
Musimy po prostu zwrócić instancję oraz typ zachowania. Następnie w pliku konfiguracyjnym:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="WebApp.OrderService"> <endpoint behaviorConfiguration="WebAppBeaviour" address="http://localhost:8000/OrderService" binding="wsHttpBinding" bindingConfiguration="customWsHttpBinding" contract="WebApp.OrderService.IOrderService" /> </service> </services> <extensions> <behaviorExtensions> <add name="WebAppBeaviourElement" type="WebApp.Extensions.CustomBehaviorExtensionElement, WebApp.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="WebAppBeaviour"> <WebAppBeaviourElement /> </behavior> </endpointBehaviors> </behaviors> <bindings> <wsHttpBinding> <binding name="customWsHttpBinding"> <security mode="None" /> </binding> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
Jak widać w pliku konfiguracyjnym, klasa BehaviorExtensionElement stanowi mediator pomiędzy plikiem XML a kodem konkretnym zachowaniem.
Ostatnią opcją jest użycie atrybutów .W tym przypadku należy oprócz implementacji stosownego zachowania (np. IServiceOperation) również dziedziczyć po Attribute np:
public class CustomBehaviour : Attribute,IServiceBehavior { //... } [CustomBehaviour(MaxPoolSize=10,MinPoolSize=2,IncrementSize=2)] public class Service:IService { //... }