Share via


PIAB And WCF

Integrating the Policy Injection Application Block with WCF Services

Hugh Ang and David San Filippo

Code download available at:WCFandPIAB2008_02.exe(196 KB)

This article discusses:

  • Integrating the Policy Injection Application Block and WCF
  • Creating Custom WCF Behaviors
  • Applying behaviors using .NET attributes or configuration
  • Integration without changing any code
This article uses the following technologies:
WCF, PIAB

Contents

A Real-World Example
WCF Architecture Overview
Dispatcher
Extending IEndpointBehavior and IContractBehavior
The Configuration Approach
The Attribute Approach
The Sample Code

One of the most important software design principles is the separation of different responsibilities within our applications. In service-oriented design, we separate applications into services and operations. In real life, however, implementation concerns tend to leak into the services.

This is a problem not only in service-oriented design, but in object-oriented design as well. Enterprise Library 3.0 introduced the Policy Injection Application Block (PIAB) to solve this problem in object-oriented design. At the time of this writing, however, the latest version of Enterprise Library (3.1) doesn't directly support the integration of PIAB with Windows® Communication Foundation (WCF) services, so using PIAB to separate concerns in service-oriented applications might seem daunting. But, as you'll see, it's easy enough with the right techniques. Let's get started.

To leverage the PIAB, you must have control over how your objects are instantiated. With service frameworks like WCF that abstract object instantiation away from the developer, this creates a problem when trying to integrate the PIAB. However, WCF provides a variety of extensibility points through behaviors. Here we will show you how to leverage custom WCF behaviors in order to apply PIAB at the WCF service point without requiring additional code. With WCF service successfully integrated, PIAB can truly become a ubiquitous framework for separating service logic from cross-cutting implementation details.

A Real-World Example

To better understand this leakage problem, imagine you were building a simple service to find a custom customer object using a customer ID. You write a simple method in Visual Studio® 2005 to invoke a data access object and return a customer object:

public Customer GetCustomer(string customerID) { return CustomerDAO.GetCustomer(customerID); }

Then you remember the customer ID field cannot be empty. So you add a few lines for validation. You also add a few tracing statements so you can make sure the correct values are being passed to and returned from the data access object. Finally you add exception handling to wrap any exceptions thrown from the data access object with a custom exception. Next thing you know, you have 15 lines of code. And it could have been worse. Authorization logic, caching logic, or logic to update performance counters would have added even more code bloat.

Yet none of these additions has anything to do with retrieving a customer object. Ultimately, what you get for all this work is code that is more difficult to understand, maintain, and reuse. Unfortunately, examples like this are common in the real world, especially when business logic spans more than one line of code.

Using the PIAB, you would only need a single line of code in your method. The code for validation, exception handling, and so forth would be introduced through the creation of policies that define the implementation details needed at run time. Policies can be applied to objects through configuration or by decorating classes with attributes. Each policy has a set of matching rules to determine where it should be applied, and a collection of handlers that determine which behaviors should be introduced when invoking methods on the object. The PIAB comes with a number of useful handlers for validation, logging, exception handling, managing performance counters, authorization, and caching. Many of these handlers leverage Enterprise Library's other application blocks.

In order to consume an object that uses these policies, you must create an instance of the class using the PolicyInjectorFactory. Instead of instantiating the class using the "new" operator, you would pass the type to the Factory's "Create" method. The factory will then determine which policies apply to that type using the matching rules. PIAB supports matching rules based on types, assemblies, member names, method signatures, namespaces, parameter types, property types, return types, custom attributes, tags (PIAB gives you a tag attribute that can be set to a value for matching), and your own custom matching rules.

If no matching rules apply, the factory will simply instantiate the type and return it; otherwise it creates a proxy to that object. The proxy intercepts calls to the object and invokes the handlers in a pipeline, as shown in Figure 1.

Figure 1 Handlers Pipeline

Figure 1** Handlers Pipeline **(Click the image for a larger view)

The PIAB is exactly what we need to keep the GetCustomer service method to a single line of code. We just need to define a policy to match the "GetCustomer" operation and include handlers for logging, validation, and exception handling.

Now that you understand PIAB policy, matching rules, and handlers, you need to tackle the problem of integrating this solution into WCF. Let's see how you could still leverage PIAB if the GetCustomer operation were defined in a WCF service.

WCF Architecture Overview

WCF is the Microsoft platform for building services that support a variety of transports including TCP/IP, HTTP, MSMQ, named pipes, and WS-* protocols. At a high level, the WCF architecture consists of a service model layer and a channel layer at both the sender and receiver, as shown in Figure 2. The service model layer provides the API for user code to interface with WCF; the channel layer handles the messaging and transport details.

Figure 2 WCF Architecture

Figure 2** WCF Architecture **(Click the image for a larger view)

When you host a WCF service, you have to define the address, binding, and contract—the ABCs—that define the service's endpoint. These ABCs can be defined in either code or the configuration file, and they effectively control the channel layer. WCF takes care of building up the channels needed for the binding and protocol, listens for incoming messages, and sends them to the intended service instances through invocation of its methods.

As discussed previously, to enable PIAB for a type, its instance needs to be created via the PIAB factory. When hosting a WCF service, you only provide the type of the service class. This implies that the WCF runtime will be responsible for creating an instance of the service when it is needed. So how can we go about altering this behavior so that a service class instance can be created by the PIAB factory within the WCF runtime? In order to do so, we need to delve deeper into the WCF runtime architecture and find out how WCF instantiates the service.

Dispatcher

Take a look at the receiving application in Figure 2. The diagram shows a simplified view of the physical WCF dispatcher model, as there actually exist channel dispatchers and endpoint dispatchers. Ignoring that detail doesn't affect what we're trying to accomplish, and it makes our discussion a lot easier. Note that the dispatcher sits within WCF's service model layer. The dispatcher plays an essential role for WCF, as it is responsible for deserializing the WCF message, creating the service instance, and eventually dispatching the call. Upon creating the dispatcher object, the WCF runtime initializes an instance behavior object that contains an instance provider object. The instance provider is responsible for instantiating the service. This process is shown in Figure 3.

Figure 3 Service Instantiation

Figure 3** Service Instantiation **(Click the image for a larger view)

Behaviors are the main extensibility point for WCF. By creating our own behaviors, we can alter the way WCF creates the instance of the target service, thereby putting the PIAB mechanism in place. Figure 4 shows how the PIAB is introduced when we create our own custom behavior and custom instance provider.

Figure 4 Introducing the PIAB

Figure 4** Introducing the PIAB **(Click the image for a larger view)

Now, let's create a custom behavior and instance provider and plug them into the WCF runtime—we will demonstrate this with both configuration data and Microsoft® .NET Framework attributes. You will see that no code is necessary to make WCF instantiate services using the PIAB. Note that our custom behavior and instance provider are generically designed so that they are not tightly coupled with service types. This means you can download the code and use it for any services to which you would like to apply cross-cutting logic via the PIAB.

Extending IEndpointBehavior and IContractBehavior

Before we create our custom behavior, we must understand how WCF behaviors are discovered and applied, particularly in the context of instantiating services. When the WCF runtime is instructed to host a service, the service model layer will go through the following process:

  1. Find behaviors in configuration.
  2. Find behaviors defined as attributes on the service class or operation.
  3. Build channel listeners for handling messaging and transport.
  4. Apply dispatch behaviors for both the configuration and attributes described in Steps 1 and 2.

For our purposes, we need to define configuration or attributes that can be applied to our service code so that our custom behavior can be found by the WCF runtime. In Step 4 of the process, the WCF runtime will apply the dispatch behavior as defined in custom behaviors. This is our chance to substitute the default instance provider with our own instance provider.

Regarding behaviors, WCF exposes the following interfaces: IServiceBehavior, IEndpointBehavior, IContractBehavior, and IOperationBehavior. IServiceBehavior deals with behaviors at the host level, and IOperationBehavior is limited to the operation's scope, so they are not suitable for creating a service instance. Both IEndpointBehavior and IContractBehavior require the implementation of the ApplyDispatchBehavior method, and it is from within this method where we can make the switch and substitute our own custom instance provider.

So if both IEndpointBehavior and IContractBehavior offer the extensibility point for us to create a service instance via the PIAB factory, which one do we use? It turns out that we will use both. IEndpointBehavior can be used to extend the dispatch behavior in a configuration file, and IContractBehavior can be used to extend the dispatch behavior as a .NET attribute.

The Configuration Approach

The WCF configuration simply piggybacks on the .NET Framework configuration settings. In the app.config or web.config file of the .NET application that hosts the WCF runtime, there is a <system.serviceModel> section, which you will see later.

While seemingly complex, it is really well aligned with the WCF architecture, where service and client contain endpoints that consist of address, binding, and contract definitions.

Elements such as <behaviors>, <extensions>, and so on are simply details that are referenced by <service> and <endpoint> elements. In the configuration, behavior customization can be performed on a <service> or an <endpoint> element. The <behaviors> element contains <serviceBehaviors> and <endpointBehaviors>. Because the service level is not the appropriate place for us to customize behavior to instantiate a service type, we will provide a custom endpoint behavior.

As shown in Figure 5, our custom behavior will implement the IEndpointBehavior interface. Additionally, in order for it to be applied through configuration, it must derive from System.ServiceModel.Configuration.BehaviorExtensionElement and override the BehaviorType property and the CreateBehavior method.

Figure 5 Custom Behavior

public class PolicyInjectionEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior { public override Type BehaviorType { get { return typeof(PolicyInjectionEndpointBehavior); } } protected override object CreateBehavior() { return new PolicyInjectionEndpointBehavior(); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { Type contractType = endpoint.Contract.ContractType; endpointDispatcher.DispatchRuntime.InstanceProvider = new PolicyInjectionInstanceProvider(contractType); } // ... }

ApplyDispatchBehavior is the key method in IEndpointBehavior, as it allows for our custom endpoint behavior to substitute the WCF default instance provider with our custom instance provider—PolicyInjectionInstanceProvider. As shown in Figure 6, our provider implements the System.ServiceModel.Dispatcher.IInstanceProvider interface. The GetInstance method will then be called by the WCF runtime in order to get the instance of the service type. Our implementation of GetInstance is straightforward; it asks for the Enterprise Library's PIAB to create a policy injection proxy based on the service type and contract type (interface) of the received message. Because the default PIAB interception mechanism uses .NET remoting's transparent proxy, a type derived from MarshalByRefObject is required if no specific contract type (interface) is specified. Typically you don't have to worry about this requirement when using PIAB with WCF services since WCF contracts are .NET interfaces.

Figure 6 PolicyInjectionInstanceProvider

public class PolicyInjectionInstanceProvider : IInstanceProvider { private Type serviceContractType; private static PolicyInjectorFactory fac = new PolicyInjectorFactory(); private static PolicyInjector injector; public PolicyInjectionInstanceProvider(Type t) { this.serviceContractType = t; if (injector == null) injector = fac.Create(); } public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message) { Type type = instanceContext.Host.Description.ServiceType; if (serviceContractType != null) return injector.Create(type, serviceContractType); else { return injector.Create(type); } } public object GetInstance(InstanceContext instanceContext) { return GetInstance(instanceContext, null); } public void ReleaseInstance(InstanceContextinstanceContext, object instance) { IDisposable disposable = instance as IDisposable; if (disposable != null) { disposable.Dispose(); } }

Now with these two custom types for IEndpointBehavior and IInstanceProvider ready, we can plug them into the configuration code, as shown in Figure 7. Note that in the <BehaviorExtensions> element, we add the type definition for PolicyInjectionBehavior and, we apply it to the endpoint for our service. Along with the PIAB configuration, we are now able to inject policies into the CustomerService service without writing any additional lines of code.

Figure 7 Configuration File

<system.serviceModel> <services> <service name="CustomerService.CustomerService"> <endpoint address="https://localhost:8000/CustomerService/ CustomerService.svc" binding="wsHttpBinding" bindingConfiguration="" contract="CustomerService.ICustomerService" behaviorConfiguration="PolicyInjectionEndpointBehavior" /> </service> </services> <behaviors> <endpointBehaviors> <behavior name="PolicyInjectionEndpointBehavior" > <PolicyEndpointBehavior /> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="PolicyInjectionEndpointBehavior" type="PolicyInjectionBehaviors.PolicyInjectionEndpointBehavior, PIBehaviors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> </system.serviceModel>

The Attribute Approach

In .NET, another common method of declarative programming (in addition to the use of a configuration file) is to use attributes, which get compiled into the assembly as metadata and are visible to the CLR at run time. In Figure 8, we define a class, PolicyInjectionBehaviorAttribute, that implements System.ServiceModel.Description.IContractBehavior and System.ServiceModel.Description.IContractBehaviorAttribute interfaces. It will need to derive from the Attribute base class of .NET Framework in order to be applied as an attribute.

Figure 8 PolicyInjectionBehaviorAttribute Class

[AttributeUsage(AttributeTargets.Class)] public class PolicyInjectionBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute { public PolicyInjectionBehaviorAttribute() { } public Type TargetContract { get { return null; //return null so we apply to all contracts } } public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, BindingParameterCollection parameters) { } public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch) { Type contractType = description.ContractType; dispatch.InstanceProvider = new PolicyInjectionInstanceProvider(contractType); } public void Validate(ContractDescription description, ServiceEndpoint endpoint) { } }

The implementation for IContractBehaviorAttribute is very simple—we're returning null in the TargetContract property, as we're targeting any contract that has the PolicyInjectionBehaviorAttribute attached. The implementation of ApplyDispatchBehavior is similar to that of PolicyInjectionEndpointBehavior. We substitute the default instance provider with our own PolicyInjectionInstanceProvider. This is the same instance provider that is used for the endpoint behavior for the configuration approach. The code below shows how to inject policies through this attribute. Notice that no other effort is required from the developer besides applying this attribute:

[PolicyInjectionBehavior] public class DecoratedCustomerService : ICustomerService { public Customer GetCustomer(string customerID) { return CustomerDAO.GetCustomer(customerID); } }

The Sample Code

To demonstrate the use of the configuration-driven and attribute-based implementations of our custom WCF Behaviors, we created a sample that illustrates both. In the sample, we defined the service contract as an interface named ICustomerService. We then created two concrete implementations of that interface: CustomerService and DecoratedCustomerService. The only difference between these two classes is that DecoratedCustomerService has been decorated with the PolicyInjectionBehavior attribute.

Our sample code includes a console application that hosts both CustomerService and DecoratedCustomerService. We modified the configuration of the CustomerService class to introduce our PolicyInjectionEndPointBehavior. We also included a client application that calls each service once to perform GetCustomer functions. Here we show you callCustomerService, but callDecoratedCustomerService looks the same:

private static void callCustomerService() { CustomerService.CustomerServiceClient client = new CustomerService.CustomerServiceClient(); Console.WriteLine("Retreiving Customer 1"); CustomerService.Customer cust = client.GetCustomer("1"); Console.WriteLine("Retreived Customer, Name: [" + cust.Name + "]"); }

For both the configuration and attribute approaches, we set up the PIAB configuration (see Figure 9) to include a logging handler for calls made to the services. You can add any other handlers, such as exception handling and caching, if you like. The logging handler in our sample writes audit trails to a flat file called audit.log. After the calls are made, audit.log is created in the same directory as the executing code unless the configuration is modified to create it elsewhere.

Figure 9 App.config in Visual Studio

Figure 9** App.config in Visual Studio **(Click the image for a larger view)

As you can see, the PIAB is a good way to separate cross-cutting logic from domain-specific logic, and it can be used by WCF services. To give it a try, download the sample code from msdn.microsoft.com/msdnmag/code08.aspx. To apply the PIAB to your WCF Services, just add a reference to the PIBehaviors assembly and then either apply the PolicyInjectionEndpointBehavior via configuration or apply the PolicyInjectionBehaviorAttribute to your services directly.

Hugh Ang is a Senior Solution Architect at Avanade and is working as VP of Application Development for a client, overseeing its enterprise architecture as well as managing the application development department. You can read his blog at tigerang.blogspot.com.

David San Filippo is a Solutions Developer at Avanade and holds MCPD Enterprise Application Developer, MCSD.Net, and MCDBA credentials. You can read his blog at www.mtelligent.com.