Using the Policy Injection Application Block in ASP.NET

©2007 Alex Homer, Stonebroom Limited, http://www.stonebroom.com

 

Contents:

About the Policy Injection Application Block. 2

Using the Policy Injection Application Block. 3

Creating and Wrapping Target Objects 3

The Built-in Matching Rules and Handlers 4

Matching Rules 4

Call Handlers 4

Policy Configuration Tools 5

Configuring an Application to use Policy Injection. 6

An ASP.NET Example Application. 6

Business Objects in the Example Application. 8

The User Interface in the Example Application. 8

Handler Attributes in the Example Application. 10

Configuration Settings for the Example Application. 12

Behavior of the Caching Handlers 14

Behavior of the Exception Handling Handler 15

Behavior of the Logging Handlers 16

Behavior of the Validation Handlers 19

Summary. 19

 

One of the techniques becoming increasingly popular with developers is the adoption of an Aspect-Oriented Programming (AOP) model. AOP provides techniques for changing the behavior of business objects and other classes through the application of policies; making it easier to implement common crosscutting concerns such as logging, validation, exception handling, caching, and more.

The terminology of AOP uses the word "concern" to mean a task or feature of an application. Core concerns are the features usually unique to a class or object, such as extracting specific data from a database, or calculating the result of a function directly applicable to the class or object. Other tasks common to more than one class or object are crosscutting concerns, and poor management of these can result in duplicated and hard to manage code, and unreliable applications.

One approach to simplifying and unifying the implementation of crosscutting concerns is through policy injection, and this is a feature introduced by the Microsoft patterns & practices group into the latest release of Enterprise Library. Version 3.0 contains the Policy Injection Application Block, which integrates with Enterprise Library and the other application blocks it contains to support simple management across applications and business objects of logging, exception handling, caching, validation, authorization, and instrumentation.

About the Policy Injection Application Block

The Policy Injection Application Block supports the application of policies for managing crosscutting concerns through an interception method that creates a handler pipeline, and injects handlers into this pipeline. As the application makes calls to methods and properties of the target object, the handler pipeline executes each handler in turn; both on the way to the target object (the pre-processing stage) and on the way back (the post-processing stage). Each handler can perform tasks that carry out a required feature, such as logging or validation.

The Policy Injection Application Block is also highly flexible and extensible. A set of built-in handlers provided with the block perform the common tasks exposed by the other application blocks within Enterprise Library, and developers can write their own handlers to accomplish almost any other tasks their applications require. They can even integrate these custom handlers with the Enterprise Library configuration system, making it simple to add them to a policy and configure them – in exactly the same way as the built-in handlers.

There are two ways that developers, administrators, and operators can create handler pipelines. The direct route, for the developer, is to decorate their classes and/or class members with the attributes exposed by the Policy Injection Application Block. For example, they can add the [ValidationCallHandler] attribute to a class member to specify that the block should validate the parameters in line with specific validation attributes applied to each parameter.

For enterprise solutions, an alternative approach is the use of a configuration file or other configuration resource. Enterprise Library supports a range of configuration options, such as application configuration files (App.config and Web.config), custom configuration files, and configuration storage in a database. Developers, administrators, and operators can configure policies (including the addition of new policies) simply by editing the application configuration. This approach provides an excellent technique for managing applications post deployment, and changing their behavior to meet changing conditions and requirements without requiring recompilation of the code. In conjunction with the new Group Policy-driven configuration management features in Enterprise Library 3.0, this allows central administration of all computers in an Active Directory domain or forest that run the application.

Some of the common uses for the Policy Injection Application Block with the default set of handlers are:

Many features of the Policy Injection Application Block make it easy to modify the block completely if the default implementation does not suit your requirements. For example, you can reuse the matching rules, handlers, policies, and other core types with your own interception mechanism if you create a custom proxy and injection pipeline generator.

For more details of Enterprise Library, and to download the latest version, see http://www.codeplex.com/entlib.

Using the Policy Injection Application Block

The Policy Injection Application Block includes a set of handlers for common Enterprise Library-related tasks, a set of attributes that apply these handlers to classes and class members, and a set of matching rules that – when defined within a configuration policy – select the target classes and class members to which the block will apply handlers.

When using the configuration system to specify handlers for classes and class members, you define one or more policies using the Enterprise Library configuration tools. Each policy consists of a series of matching rules and a series of handlers. The matching rules select target classes and members, with the block ANDing together the results within each policy. This provides a comprehensive range of options for selecting the target classes and members to which you want to apply handlers. For each class or member selected by the matching rules, the block then generates a handler pipeline containing the configured set of handlers for the selected classes and members.

The block performs the selection and generates the pipelines at start-up and caches them to reduce processing overheads, and only injects pipelines into the selected members – not into all the members of selected classes. Together with several other internal optimizations, this helps to minimize the extra overheads that most AOP and policy injection technologies incur.

Creating and Wrapping Target Objects

To allow the Policy Injection Application Block to inject handler pipelines into method and property calls, it requires developers to use a specific technique when creating instances of objects. The block exposes two methods that generate a suitable proxy between the application and the target object. The PolicyInjection factory class provides the Create method to create new object instances from classes, and the Wrap method to apply policies to existing object instances.

Both of these are intelligent, in that they only create a proxy to the target object if there is at least one policy or handler to apply (at least one set of policy matching rules selects the target object, or a member carries a policy injection attribute). If there are no policies for the object, and no attributes on its members, the methods return the target object. The only overhead is the check through the cached configuration data and a scan for attributes on the members of the target class.

As long as the application creates instances of target objects using the Create method, or obtains references to existing instances using the Wrap method, the Policy Injection Application Block can apply the specified handlers. In particular, this approach allows administrators and operators to add new policies to existing objects simply by configuring suitable matching rules and handlers within these policies, without requiring any intervention from the developer or requiring recompilation and redeployment of the target objects – with all the issues such as testing, versioning, and management that this can incur.

There are some limitations with the Policy Injection Application Block, however, and it is important to consider these when planning to use it:

The Built-in Matching Rules and Handlers

Before looking at how you can use the Policy Injection Application Block in ASP.NET applications, this section provides a brief overview of the default matching rules and handlers available within the block.

Matching Rules

The Policy Injection Application Block provides the following set of built-in matching rules for selecting classes, and members within these classes, to which it will apply a handler pipeline:

Call Handlers

The Policy Injection Application Block provides the following set of built-in handlers that you can add to a handler pipeline, using both the application configuration or directly applied attributes within the target class:

Policy Configuration Tools

If you apply policies using configuration, rather than through directly applied attributes, you can use one of the two configuration tools provided with Enterprise Library 3.0. The stand-alone Configuration Console allows you to create new configuration files and open existing ones, and then edit the contents before saving the file into the required location. The Enterprise Library section of your Start menu contains a link to open the Configuration Console.

Alternatively, if you are using Visual Studio 2005 to create your application, you can use the Configuration Editor that Enterprise Library installs automatically. Simply right-click your existing application configuration file in Visual Studio's Solution Explorer window and click Edit Enterprise Library Configuration.

Both editors work in the same way, and provide an identical hierarchical view of the configuration settings. In the stand-alone Configuration Console, you edit the values for each node using the list of properties displayed in the right-hand window as you select each node. In the Visual Studio Configuration Editor, you edit the properties in the normal Visual Studio Properties window (see Figure 1).

Figure 1 – The Enterprise Library Configuration Editors

Configuring an Application to use Policy Injection

You must add the Policy Injection Application Block to your application configuration, even if you only intend to use directly applied attributes to configure handler pipelines for your target objects. In addition, if your attributes specify exception handling, logging, authorization, or a configured validation scheme, you must add the appropriate application blocks to the configuration as well as configuring the required settings in these blocks.

Also, bear in mind that, if you intend to run your ASP.NET application in partial trust mode, you must set the RequirePermission property for each block to False (the default is True). This prevents the blocks from demanding all the permissions they may use when they first load. If you subsequently use a feature of a block that requires permissions not available in your chosen partial trust mode (such as access to the file system or Windows Event Log), you can configure the relevant permissions for just the required features.

For more details of running ASP.NET applications that use Enterprise Library in partial trust mode, see http://www.daveandal.net/articles/EntLibASPNET/default.htm.

An ASP.NET Example Application

To demonstrate how you can use the Policy Injection Application Block in your ASP.NET applications, Figure 2 shows a simple example that uses most of the features of the block. The example provides buttons to execute several methods on two business objects, named AttributedCustomerModel and InterfaceCustomerModel. Both objects contain methods that access an XML file containing a list of customers. The InterfaceCustomerModel class also exposes a property, demonstrating how you can use the Policy Injection Application Block with target object properties as well as methods.

Figure 2 – The example application showing the trace information

Notice that the application has tracing enabled so that you can see the messages generated by the members of the business objects as the main application code calls them (we have manipulated the screenshot to remove some of the ancillary details so that you can more easily see the tracing information). The tracing information helps to show the operation of handlers such as the Caching Handler.

Business Objects in the Example Application

The two business objects used in the example application demonstrate the two different types of target object for which the Policy Injection Application Block can create handler pipelines. The AttributedCustomerModel class (in the App_Code subfolder) inherits from MarshalByRefObject, and so the Create and Wrap methods of the block's PolicyInjection class can create instances directly – building a suitable proxy and injecting a handler pipeline for any class members that have a policy defined in the configuration, or which carry handler attributes.

The second business object class, InterfaceCustomerModel, does not inherit from MarshalByRefObject, and so the example includes a interface definition (named ICustomerInterface) for this class that the Create and Wrap methods can use to create the appropriate proxy and handler pipelines. However, to demonstrate the way that the block relies on this interface, one of the public members of the InterfaceCustomerModel class does not exist in the interface definition. Table 1 shows the methods exposed by the two business objects.

Table 1 – Public members exposed by the two business objects

Member

Attributed Customer Model

Interface Customer Model

Notes

GetCustomerList()

Yes

Yes

 

GetCustomerName(String customerID)

Yes

No

 

GetCustomerNameWithWildcard

(String customerID)

No

Yes

Uses GetCustomerName method with a wildcard.

GetCustomerDetails(String customerID)

Yes

Yes

Not defined in interface.

GetCityList(Int32 minimumCount)

No

Yes

 

CityCount

No

Yes

Property. Uses GetCityList

The User Interface in the Example Application

The UI for the example application is the single page named Default.aspx, seen in Figure 2. Each button has an event handler in the associated code-behind file Default.aspx.cs, which creates an instance of the appropriate target class and calls the required method. Depending on the type of data returned (a String, a DataRow, or a DataSet), the event handlers display it in the page. If an exception occurs, the text "ERROR:" followed by the exception message appears instead.

For the AttributedCustomerModel class, which inherits MarshalByRefObject, the event handlers can create an instance using the Create method and the class type:

AttributedCustomerModel customers

   = PolicyInjection.Create<AttributedCustomerModel>();

For the InterfaceCustomerModel class, which does not inherit MarshalByRefObject, the event handlers must specify the known interface for this class as well as the class type when they call the Create method:

ICustomerInterface customers

   = PolicyInjection.Create<InterfaceCustomerModel, ICustomerInterface>();

Note that this approach returns an object of the interface type and not the actual class type. This is because the proxy between the application and the target object can only see the members of the interface, and not members of the concrete class. If you try to cast the returned interface reference to a concrete type, the result is null – as demonstrated by this code in the event handler that attempts to get the customer details from the InterfaceCustomerModel class:

ICustomerInterface customers

  = PolicyInjection.Create<InterfaceCustomerModel, ICustomerInterface>();

 

// NOTE: this method is not declared in the ICustomerInterface interface

// to access it, might try to cast it to the concrete type. However,

// the following cast of the proxy to the concrete class returns null

InterfaceCustomerModel realObject = (customers as InterfaceCustomerModel);

 

// and so the following code will fail because realObject is null

DataRow[] details = realObject.GetCustomerDetails(txtID_InterfaceCustDetails.Text);

if (details.GetLength(0) > 0)

  ...

If you click the corresponding button in the example page, you will see that it produces an error, as shown in Figure 3.

Figure 3 – Attempting to access a member not declared in the interface

The code in the event handler for the button that gets a list of customers from the InterfaceCustomerModel class demonstrates how you can use the Wrap method with an existing instance of a target object. The code first creates an instance of the InterfaceCustomerModel class using the new operator. The Policy Injection Application Block will not be able to see this object, and so cannot create a proxy and handler pipeline for it. However, calling the Wrap method with the interface type and passing the existing target object to it allows the block to create the required proxy and pipeline:

InterfaceCustomerModel customerObject = new InterfaceCustomerModel();

 

// now wrap the existing object so that PIAB can see it

ICustomerInterface customers

      = PolicyInjection.Wrap<ICustomerInterface>(customerObject);

Handler Attributes in the Example Application

The AttributedCustomerModel class, as the name suggests, demonstrates the use of handler attributes. For example, the GetCustomerList method carries the CachingCallHandler attribute, specifying the cache duration as 20 seconds:

[CachingCallHandler(0, 0, 20)]

public DataTable GetCustomerList()

{

  ...

}

This use of the CachingCallHandler attribute takes advantage of the constructor of the attribute class that accepts a cache duration value as a number of hours, minutes, and seconds. If you omit these parameters to use the default attribute constructor, the default cache duration of five minutes applies.

The GetCustomerDetails method of the AttributedCustomerModel class carries the ValidationCallHandler attribute. This attribute takes advantage of the capabilities of the Validation Application Block, which exposes a comprehensive set of validation features. As with the Policy Injection Application Block, you can apply these features using validation attributes, or by configuring Rule Sets that define the validation rules to apply to a specific member of a class. In general, unless you need to reuse Rule Sets, the easiest approach is to use validation attributes as shown here:

[ValidationCallHandler]

public DataRow[] GetCustomerDetails(

                   [StringLengthValidator(5, RangeBoundaryType.Inclusive,

                                          5, RangeBoundaryType.Inclusive)]

                                          String customerID)

{

  ...

}

The GetCustomerDetails method takes a single parameter named customerID, which you can see at the end of the opening method declaration. This parameter carries a StringLengthValidator attribute, which specifies that the String passed in the customerID parameter must be exactly five characters (the minimum and maximum values are 5 and the boundaries are "inclusive").

The remaining method in the AttributedCustomerModel class, GetCustomerName, carries a special attribute that prevents the block from applying any policy defined in the configuration, even if the corresponding matching rules select this method. This is useful for methods where, at the design and development stage, you know that you do not want administrators or operators to apply any policies to specific methods.

[ApplyNoPolicies]

public String GetCustomerName(String customerID)

{

  ...

}

 

Note: You can use handler attributes on a class declaration to apply to all the members of a class, or on individual class members. You can also use attributes both in classes that inherit MarshalByRefObject, and in classes that depend on an interface for policy injection. However, when using an interface definition to apply policy injection, you will find that the Validation Handler requires the parameter validation attributes to exist in the interface definition rather than the concrete class definition.

Configuration Settings for the Example Application

The example application shows several of the ways that you can select target classes and class members using matching rules defined in the application configuration, and how you can use the four handlers most useful in ASP.NET applications: the Caching Handler, Exception Handling Handler, Logging Handler, and Validation Handler.

Figure 4 shows the configuration file for the application open in the Visual Studio Configuration Editor. You can see that it contains the Data Access Application Block, the Exception Handling Application Block, the Logging Application Block, and the Policy Injection Application Block (the example does not actually use the Data Access Application Block, but the configuration tool adds this by default).

Figure 4 – The Enterprise Library configuration for the example application

The Exception Handling Block configuration defines a simple single exception policy named CustomerModelPolicy, which will wrap any ArgumentNullException passed to it in a standard Exception with the message "You must specify a value for the customer ID".

The Logging Application Block configuration defines two category sources that the example application can use to generate log messages. The AuditFile category source writes log messages into a disk file named audit.log, located in the local C:\Temp folder. The EventLog category generates event entries in the Application section of Windows Event Log. All of these settings are, of course, configurable within the Logging Application Block section if you want to implement a different logging strategy.

The settings for the Policy Injection Application Block come next. These consist of three policies, described in detail in Table 2.

Table 2 – The policies defined in the example application

Policy Name

Matching Rules

Call Handlers

InterfaceModelPolicy

Member Name =

GetCustomerNameWithWildcard or GetCustomerList

 

Type Name = InterfaceCustomerModel

Logging Handler with Categories =

"AuditFile" and "EventLog"

 

Caching Handler with cache duration 20 seconds

 

Exception Handler with Exception Policy = "CustomerModelPolicy"

 

AttributeModelPolicy

Method Signature, Name = * and parameter = System.String

 

Type Name = AttributedCustomerModel

 

Logging Handler with Categories =

"AuditFile"

 

CacheByTagPolicy

Tag Attribute = "ApplyTenSecondCaching"

Caching Handler with cache duration 10 seconds

 

As we look at the example application in use, you will see how a combination of policies like this, together with attributes defined within the business rules, provides a flexible mechanism for applying handlers to target objects, and managing crosscutting concerns.

Behavior of the Caching Handlers

The example application applies the Caching Handler in three different circumstances:

[CachingCallHandler(0, 0, 20)]

public DataTable GetCustomerList()

{

  ...

}

[Tag("ApplyTenSecondCaching")]

public Int32 CityCount

{

  ...

}

When you access any of these members, the trace section of the page shows the calls made to the target member. However, if you repeat the call within the cache duration period, the trace section shows that the block only instantiated the target class (it has to do this to add the handler pipeline), and did not call the target method or property – as shown in Figure 5.

Figure 5 – The effect of the Caching Handler when calling a method in the target class

In this example, you can see that the call to the GetCustomerNameWithWildcard method of the InterfaceCustomerModel class calls the GetCustomerName method. However, on the subsequent request, the block calls neither of these methods and returns the value from the cache instead. 

Note: The Caching Handler uses the built-in ASP.NET caching mechanism, which is domain-wide. The handler creates a cache key based on the complete signature of the current method, allowing it to vary the cache (create separate cached items) for calls that provide different parameters values. However, it does not detect and include the thread identity in the cache key, and so calls made on threads with different security contexts will return the same value from the cache. This may present a security risk if methods return sensitive or user-specific information. 

Behavior of the Exception Handling Handler

The example application applies the Exception Handling Handler to only two methods:

The configuration specifies the exception handling policy named CustomerModelPolicy, declared in the Exception Handling Application Block section, which will wrap any ArgumentNullException in a new Exception with a the message "You must specify a value for the customer ID".

In the AttributedCustomerModel class, the GetCustomerName method throws an ArgumentNullException when there is no customer ID:

public String GetCustomerName(String customerID)

{

  // ensure there is a customer ID specified

  if (customerID == String.Empty)

  {

    throw new

          ArgumentNullException("This exception will not be replaced...");

  }

  ...

Therefore, as the attributes and configuration settings do not apply the Exception Handling Handler to this method, the page shows this error message:

ERROR: Value cannot be null. Parameter name: This exception will not be replaced...

The GetCustomerNameWithWildcard method of the InterfaceCustomerModel class also throws an ArgumentNullException when there is no customer ID:

public String GetCustomerNameWithWildcard(String customerID)

{

  // ensure there is a customer ID specified

  if (customerID == String.Empty)

  {

    throw new ArgumentNullException("This exception should be replaced...");

  }

  ...

This method does have the Exception Handling Handler attached, and so – when there is no customer ID – the page displays the following error message generated by the exception policy defined in the Exception Handling Application Block configuration:

ERROR: You must specify a value for the customer ID

Behavior of the Logging Handlers

The example application applies the Logging Handler in two circumstances:

Executing one of the methods listed above should produce log messages in the relevant logs. For example, executing the GetCustomerNameWithWildcard method with the value "A" for the single parameter produces a "Before" and "After" entry in the audit.log file. This is the content of the "After" message:

--------------------------------

Timestamp: 21/03/2007 10:50:01

Message: After

Category: AuditFile, EventLog

Type: InterfaceCustomerModel

Method: GetCustomerNameWithWildcard

Parameters: customerID : A

Return Value: Alfreds Futterkiste

Exception:

Call Time: 00:00:00.0002551

Priority: 0

EventId: 0

Severity: Information

Title: Call Logging

Machine: DIMINUTIVEC

Application Domain: f4e1bb0-9-128189471987530168

Process Id: 3724

Process Name: WebDev.WebServer.EXE

Win32 Thread Id: 2412

Thread Name:

--------------------------------

You can see that it contains a wealth of information, including the target type name, method name, parameters, return value, call duration (Call Time), and more. You can configure more or less information using the settings for the Logging Handler, and in the Logging Application Block section.

The call to the GetCustomerNameWithWildcard method also generates "Before" and "After" entries in Windows Event Log, containing the same information as the audit.log file entry. Figure 6 shows the same "After" message generated by the Logging Block in Windows Event Log.

Figure 6 – Entries in Windows Event Log created by the Logging Handler

You can execute the other methods to which this handler applies, and view the results in the audit.log file and (for the GetCustomerNameWithWildcard and the GetCustomerList methods of the InterfaceCustomerModel class) in Windows Event Log. However, you will discover that the GetCustomerName method of the AttributedCustomerModel class does not generate any log file entries, even though the matching rules for the AttributeModelPolicy declared in the configuration select this method. This is because, as you saw earlier, this method carries the ApplyNoPolicies attribute:

[ApplyNoPolicies]

public String GetCustomerName(String customerID)

{

  ...

}

This overrides the configuration, and prevents the block from applying the configured policy to this member.

Behavior of the Validation Handlers

The example application applies the Validation Handler in only one circumstance:

[ValidationCallHandler]

public DataRow[] GetCustomerDetails([StringLengthValidator(

                                     5, RangeBoundaryType.Inclusive,

                                     5, RangeBoundaryType.Inclusive)]

                                     String customerID)

{

  ...

}

If you execute this method with a customer ID consisting of anything other than five characters, you will see the error raised by the Validation Application Block:

ERROR: Parameter validation failed Parameter name: customerID

There are many ways that you can set up validation using the Validation Application Block, in particular by creating Rule Sets that can customize the validation error message. There is also a huge range of validation types you can take advantage of in your application configuration, or as validation attributes on parameters.

Note: If you decide to experiment with the example by validating methods of the InterfaceCustomerModel class (such as GetCityList) using attributes, you must apply the [ValidationCallHandler] attribute to the method declaration in the concrete InterfaceCustomerModel class, but apply the validation attributes such as a [RangeValidator] to the declaration of the method in the ICustomerInterface. This is required because, as you saw earlier, the Policy Injection Application Block cannot see the original class through the proxy it creates.

Summary

In this article, you have seen one of the latest additions to Enterprise Library – and its use within a simple ASP.NET example application. Microsoft's patterns & practices group added a raft of new and updated features to version 3.0 of Enterprise Library. Amongst these is the Policy Injection Application Block – a new block that provides a flexible, configurable, and extensible solution for managing crosscutting concerns in enterprise-level applications.

The Policy Injection Application Block allows developers, administrators, and operators to change the behavior of business objects and target classes by injecting a pipeline containing handlers between the client application code and members of the target class. These handlers can help to minimize crosscutting concerns by implementing tasks such as validation, caching, logging, and authorization with no extra effort required by the developer or the administrator. Instead, the handlers integrate with the other application blocks within Enterprise Library and take advantage of the features they expose.

Developers can hard-code policies into objects using attributes, and administrators can add, remove, or modify policies using the configuration tools. In conjunction with new Group Policy configuration management capabilities within Enterprise Library, The Policy Injection Application Block provides a simple to use, yet enterprise-wide and powerful technique for managing ancillary tasks and crosscutting concerns.

In addition, developers can extend the Policy Injection Application Block by creating new handlers, handler attributes, and matching rules. They can even modify the block to change the interception and pipeline injection mechanism to suit their specific requirements.

 

©2007 Alex Homer, Stonebroom Limited, http://www.stonebroom.com