Programming4us
         
 
 
Programming

Context and Interception : Custom Component Services (part 1) - Building a Custom Context Attribute & Installing a Custom Message Sink

8/9/2011 3:32:48 PM
The ability to install custom component services in .NET is a major advancement for software engineering and component-oriented programming. Custom component services allow you to fine-tune and optimize the way .NET services your particular application and business logic. Custom component services decouple clients from objects, because they don't need to coordinate the execution of the custom service; you can focus on implementing the business logic, rather than the service. Examples of custom services include application logging and tracing, performance counters, custom thread management, filtering of method calls, parameter checks, event subscriptions, and so on.

Custom component services are provided in the form of custom context attributes. Ordinary custom attributes have no use unless you provide the reflection code to look for these attributes, interpret their values, and act upon them. .NET is indifferent to such custom attributes. Unlike generic custom attributes, .NET is very much aware of custom context attributes when they are used on context-bound objects. Context attributes must derive from the class ContextAttribute, defined in the System.Runtime.Remoting.Contexts namespace. When creating a new context-bound object, .NET reflects the object's metadata and places it in the appropriate context based on the behavior of the attributes. Custom context attributes can affect the context in which the object is activated and can be used to install all four types of message sink interceptors. The next two sections demonstrate how to build custom context attributes and component services. First, you will see how to develop a custom context attribute and how it affects the activation context; then you'll look at how to install custom message sinks. Finally, you'll walk though the development of two real-life, useful custom component services.

Custom context attributes and custom message sinks are undocumented features of .NET. Microsoft is committed to supporting contexts in future versions of .NET, but not to extending the infrastructure and adding features.


1. Building a Custom Context Attribute

Each context has a set of properties associated with it. The properties are the component services the context supports. A context-bound object shares a context with its client only if the client's context has all the services the component requires—in other words, if the context has the required properties. If the client's context doesn't have one or more of the properties the object requires, .NET creates a new context and puts the object in it. In addition, a context property may require a new context regardless of the properties of the client's context. You use context attributes to specify the required services. The context attributes are those that decide whether or not the client's context is sufficient.

To understand how context attributes affect context activation, consider a custom context attribute that adds a color property to a context. The color is an enum of the type ColorOption:

    public enum ColorOption{Red,Green,Blue};

You use ColorAttribute as a class attribute on a class derived from ContextBoundObject:

    [Color(ColorOption.Blue)]
public class MyClass: ContextBoundObject
{...}

Obviously, a color property isn't much of a service, but it's a good example. .NET creates objects of the class MyClass in the client's context only if the creating client's context has a color property and if its value is set to ColorOption.Blue. Otherwise, .NET creates a new context, lets the attribute set its color property to ColorOption.Blue, and places the new object in the new context. ColorAttribute also has a default constructor, setting the context color to ColorOption.Red:

    [Color]//Default is ColorOption.Red
public class MyClass: ContextBoundObject
{...}

Example 1 shows the implementation of the ColorAttribute custom context attribute.

Example 1. The ColorAttribute custom context attribute
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;

public enum ColorOption {Red,Green,Blue};

[AttributeUsage(AttributeTargets.Class)]
public class ColorAttribute : ContextAttribute
{
ColorOption m_Color;

public ColorAttribute( ) : this(ColorOption.Red)//Default color is red
{}

public ColorAttribute(ColorOption color) : base("ColorAttribute")
{
m_Color = color;
}
//Add a new color property to the new context
public override void GetPropertiesForNewContext(IConstructionCallMessage ctor)
{
IContextProperty colorProperty = new ColorProperty(m_Color);
ctor.ContextProperties.Add(colorProperty);
}
//ctx is the creating client's context
public override bool IsContextOK(Context ctx,IConstructionCallMessage ctorMsg)
{
ColorProperty contextColorProperty = null;
//Find out if the creating context has a color property. If not, reject it
contextColorProperty = ctx.GetProperty("Color") as ColorProperty;
if(contextColorProperty == null)
{
return false;
}
//It does have a color property. Verify color match
return (m_Color == contextColorProperty.Color);
}
}

//The ColorProperty is added to the context properties collection by the
//ColorAttribute class
public class ColorProperty : IContextProperty
{
ColorOption m_Color;

public ColorProperty(ColorOption ContextColor)
{
Color = ContextColor;
}
public string Name
{
get
{
return "Color";
}
}
//IsNewContextOK called by the runtime in the new context
public bool IsNewContextOK(Context ctx)
{
ColorProperty newContextColorProperty = null;
//Find out if the new context has a color property. If not, reject it
newContextColorProperty = ctx.GetProperty("Color") as ColorProperty;
if(newContextColorProperty == null)
{
return false;
}
//It does have color property. Verify color match
return (Color == newContextColorProperty.Color);
}

public void Freeze(Context ctx)
{}
//Color needs to be public so that the attribute class can access it
public ColorOption Color
{
get
{
return m_Color;
}
set
{
m_Color = value;
}
}
}



ColorAttribute has a member called m_Color that contains the required context color. The color is specified during the attribute construction, either explicitly or by using the default constructor. As a custom context attribute, it derives from ContextAttribute. The single constructor of ContextAttribute requires a string naming the new context attribute. This is provided by a call to the ContextAttribute constructor in the ColorAttribute constructor:

    public ColorAttribute(ColorOption color) : base("ColorAttribute")
{...}

ContextAttribute derives from and provides a virtual implementation of the IContextAttribute interface, defined as:

    public interface IContextAttribute
{
void GetPropertiesForNewContext(IConstructionCallMessage msg);
bool IsContextOK(Context ctx,IConstructionCallMessage msg);
}

The IsContextOK( ) method lets the context attribute examine the creating client's context, which is provided in the ctx parameter. If the client's context is adequate, no further action is required, and .NET activates the new object in the creating client's context. If the context attribute returns false from IsContextOK( ), .NET creates a new context and calls GetPropertiesForNewContext( ), letting the context attribute add new properties to the new context. Because a single object can have more than one context attribute, .NET can optimize its queries of the attributes. .NET starts iterating over the attribute list, calling IsContextOK( ) on each one. As soon as it finds an attribute in the list that returns false, .NET aborts the iteration and creates a new context. It then calls GetPropertiesForNewContext( ) on each context attribute, letting it add its properties to the new context. ColorAttribute needs to override both methods of IContextAttribute and manage its single context property. Context properties are objects that implement the IContextProperty interface:

    public interface IContextProperty
{
string Name{ get; }
void Freeze(Context newContext);
bool IsNewContextOK(Context newCtx);
}

Each context property is identified by name via the Name property of IContextProperty. ColorAttribute uses a helper class called ColorProperty to implement IContextProperty. ColorProperty names itself as "Color". ColorProperty also provides the Color public property of type ColorOption. This allows for type-safe checking of the color value.

In its implementation of IsContextOK( ), ColorAttribute checks whether the client's context has a property called "Color". If it doesn't, IsContextOK( ) returns false. If the client's context has a color property, ColorAttribute verifies that there is a color match by comparing the value of the color property with its own color.

The implementation of GetPropertiesForNewContext( ) is straightforward as well: the single parameter is an object of type IConstructionCallMessage, providing a collection of properties for the new context via the ContextProperties property. ColorAttribute creates an object of type ColorProperty, initializes it with the required color, and adds it to the collection of properties for the new context.

Because a single context-bound object can have multiple context attributes, it's possible that some will conflict with others. To handle such an eventuality, after adding all the properties to the new context, .NET calls IsNewContextOK( ) on each property. If a property returns false, .NET aborts creating the new object and throws an exception of type RemotingException. In IsNewContextOK( ), ColorAttribute simply verifies that the new context has the correct color. The Freeze( ) method lets a context property know that the final location of the context is established and available for advanced use only.

Figure 1 is a UML activity diagram summarizing the process flow when using a custom context attribute and a context property. The diagram shows the order in which the various methods take place and the resulting activation logic.

Figure 1. Custom context attribute and property activity diagram

2. Installing a Custom Message Sink

To provide a useful component service, the custom context attribute must install at least one custom message sink. The message sink can be either a server context sink, a client context sink, an envoy sink, or a server object sink. Commonly, a custom context attribute installs only a server context sink. The other sinks are intended for advanced cases, but you can install one if the need arises. For each type of custom sink you wish to contribute to the interception chain, the custom context property must implement a matching interface.

2.1. Providing a server context sink

To contribute a server context sink, the custom context property needs to implement the IContributeServerContextSink interface, defined as:

    public interface IContributeServerContextSink
{
IMessageSink GetServerContextSink(IMessageSink nextSink);
}

In its implementation of GetServerContextSink( ), the context property creates a sink object and concatenates it to the next sink in the chain, which is provided as the method parameter. GetServerContextSink( ) should return the new sink it created so that .NET can add it to the interception chain. For example, here is how to install GenericSink (presented in Example 11-2) as a server context sink:

    public IMessageSink GetServerContextSink(IMessageSink nextSink)
{
IMessageSink sink = new GenericSink(nextSink);
return sink;
}

The server context sink intercepts all calls coming into the context. .NET calls GetServerContextSink( ) after its call to IContextProperty.IsNewContextOK( ) and before creating the object, allowing the context property to provide the sink. A server context sink can intercept construction calls.

2.2. Providing a client context sink

To install a client context sink, the context property needs to implement the IcontributeClientContextSink interface, defined as:

    public interface IContributeClientContextSink
{
IMessageSink GetClientContextSink(IMessageSink nextSink);
}

A client context sink affects the context-bound object only when it's the client of another object outside the context; it intercepts all calls exiting the context. .NET calls GetClientContextSink( ) only when the object makes its first call outside the context. The information in the message object passed to the sink pertains to the target object, not the client.

2.3. Providing an envoy sink

The context property can also implement the IContributeEnvoySink interface, defined as:

    public interface IContributeEnvoySink
{
IMessageSink GetEnvoySink(MarshalByRefObject obj,IMessageSink nextSink);
}



In this case, when a proxy to an object on the client's side is set up, the proxy has an envoy sink as part of the interception chain leading to that object. The envoy sink intercepts all calls going from the client to the object. Other objects accessed by the client aren't affected. Every time a new client in a different context connects to the object, .NET installs an envoy sink in that client's context. .NET calls GetEnvoySink( ) after creating the new object but before returning control to the client. You can't intercept construction calls with an envoy sink.

2.4. Providing an object sink

To install a server object sink, the context property needs to implement the IContributeObjectSink interface, defined as:

    public interface IContributeObjectSink
{
IMessageSink GetObjectSink(MarshalByRefObject obj,IMessageSink nextSink);
}



The object sink is installed on an object-by-object basis, which means it intercepts calls only to the object whose reference is provided in the GetObjectSink( ) call. Other calls into the context aren't affected. .NET calls GetObjectSink( ) before the first method call is forwarded to the object. As a result, you can't intercept construction calls with an object sink.

2.5. Processing messages

The IMessage interface presented previously is a collection of information about the method being intercepted. Although you can retrieve that information from the dictionary, there is a better way. When you intercept an incoming call, the different message objects (used for synchronous methods, asynchronous methods, and constructor calls) all support the IMethodMessage interface, defined as:

    public interface IMethodMessage : IMessage
{
int ArgCount{ get; }
object[] Args{ get; }
bool HasVarArgs{ get; }
LogicalCallContext LogicalCallContext { get; }
MethodBase MethodBase { get; }
string MethodName{ get; }
object MethodSignature{ get; }
string TypeName{ get; }
string Uri{ get; }
object GetArg(int argNum);
string GetArgName(int index);
}

IMethodMessage provides information about the method name, its arguments, the type on which the method was called, and the object's location. You can use that information in your pre-call message-processing logic. After the last sink—the stack builder—invokes the call on the object, it returns a different message object. Again, there are several types of returned method objects, but they are all polymorphic with the IMethodReturnMessage interface, defined as:

    public interface IMethodReturnMessage : IMethodMessage
{
Exception Exception { get; }
int OutArgCount { get; }
object[] OutArgs { get; }
object ReturnValue { get; }
object GetOutArg(int argNum);
string GetOutArgName(int index);
}

IMethodReturnMessage derives from IMethodMessage and provides additional information about the method's returned value, the values of any outgoing parameters, and any exceptions. The fact that exception information is captured is of particular interest. If the object throws an exception, the stack-builder sink silently catches it and saves it in the returned message object. This allows all the sinks up the call chain to examine the exception object. When control returns to the proxy, if exception information is present, the proxy re-throws it on the calling client's side.

Other -----------------
- Software Testing with Visual Studio Team System 2008 : Data-driven unit testing
- Software Testing with Visual Studio Team System 2008 : Unit testing an ASP.NET application
- Microsoft Enterprise Library : Error Management Made Exceptionally Easy - Replacing an Exception & Logging an Exception
- Microsoft Enterprise Library : Error Management Made Exceptionally Easy - Diving in with a Simple Example
- iPhone Programming : Connecting to the Network - Embedding a Web Browser in Your App
- iPhone Programming : Connecting to the Network - Detecting Network Status
- Parallel Programming with Microsoft Visual Studio 2010 : Introduction to Parallel Programming - Software Patterns
- Parallel Programming with Microsoft Visual Studio 2010 : Introduction to Parallel Programming - Multicore Computing & Speedup
- Microsoft ASP.NET 3.5 : Web Services for ASP.NET AJAX Applications (part 2) - Consuming AJAX Web Services
- Microsoft ASP.NET 3.5 : Web Services for ASP.NET AJAX Applications (part 1) - Remote Calls via Web Services
- Microsoft ASP.NET 3.5 : AJAX-Enabled Web Services - Implementing the AJAX Paradigm
- The Art of SEO : Measuring Search Traffic (part 2)
- The Art of SEO : Measuring Search Traffic (part 1)
- Programming Excel with VBA and .NET : Tasks in Visual Basic - Do Math
- Programming Excel with VBA and .NET : Tasks in Visual Basic - Interact with Users
- Context and Interception : The .NET Context
- Context and Interception : .NET Component Services
- Optimizing for Vertical Search : Mobile, Video & Multimedia Search
- Programming WCF Services : Data Contracts - Collections (part 2) - The CollectionDataContract Attribute & Dictionaries
- Programming WCF Services : Data Contracts - Collections (part 1) - Concrete Collections & Custom Collections
 
 
Most View
- Windows Server 2008 : Configuring FTP (part 5)
- Windows Remote Assistance : Troubleshoot From Afar
- Programming with SQL Azure : Application Deployment Factors
- Troubleshooting and Optimizing SQL Server 2005 : Data Analysis and Problem Diagnosis
- BizTalk Server 2006 Operations : Maintaining the BizTalk Group (part 3) - Restore Procedures
- iPad SDK : Keyboard Extensions and Replacements (part 2)
- Windows7: Managing Network Connections (part 1)
- SQL Server 2008 : Viewing and Modifying Data (part 2) - Creating Stored Procedures
- CSS for Mobile Browsers : Common Patterns (part 2) - Rounded corners
- SQL Azure : Securing Your Data (part 2) - Hashing
Top 10
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 3) - Configuring Recipient Filtering
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 2)
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 1)
- Implementing Edge Services for an Exchange Server 2007 Environment : Installing and Configuring the Edge Transport Server Components
- What's New in SharePoint 2013 (part 7) - BCS
- What's New in SharePoint 2013 (part 6) - SEARCH
- What's New in SharePoint 2013 (part 6) - WEB CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 5) - ENTERPRISE CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 4) - WORKFLOWS
- What's New in SharePoint 2013 (part 3) - REMOTE EVENTS