Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / C#
Article

Using Attributes and RealProxy to perform property level processing

Rate me:
Please Sign up or sign in to vote.
4.90/5 (22 votes)
8 Mar 20066 min read 73K   537   46   13
In this document, I will explain how to use proxy classes and attributes to intercept method calls to set and get statements, and perform basic validation and change tracking.

Abstract

One of the great challenges in development, irrespective of the language and style, is reduction of duplicate code. Some of the most duplicative code I tend to see is in property setters and getters. If you think of all the various things that could be happening in your gets and sets; data validation, change history, data security; it’s not really a surprise. In this document, I will explain how to use proxy classes and attributes to intercept method calls to set and get statements, and perform basic validation and change tracking.

The Basics

As with any software application, we have to decide what it is that we are trying to accomplish. My client has simple needs - They want to collect a phone number, so long as it meets certain business requirements. To this end, I have defined here a basic class called DataClass.

C#
public class MySubClass: DataClass
{
    private string _myVal = string.Empty;
    
    public string myVal
    {
        get{return _myVal;}
        set{_myVal = value;}
    }
}

As you can see, there isn’t much to this class. It has one field called _myVal, and a property called myVal that exposes that field to the rest of the world. As explained above, though, my business requirements have placed some restrictions, on what _myVal can be. The requirements are:

  1. It is required. Since it is a string field, this means it cannot be DBNull.Value or String.Empty.
  2. It is going to hold a phone number with area code, prefix, and the actual number. This means that we want to either verify that the incoming value is in a recognizable phone number format, or that the when we strip the non-numeric values from the string, we are left with 10 digits.
  3. The actual value being persisted should not have any formatting, but should always display in the ###-###-#### format, no matter what the user entered.
  4. The original value should be stored for audit reasons.

Given these requirements, here is what the property would look like using normal C# code:

C#
public string myVal 
{
    get    
    {         
        //Format the return value in ###-###-#### Format         
        return _myVal.Substring(0,3) + "-" + _myVal.Substring(3,3) + "-" + 
            _myVal.Substring(7,4);     
    }     
    set     
    {         
        bool isValid = true; // Assume all values are valid to start         
        isValid = isValid && !Validation.isEmpty(value); //Not valid if empty         
        isValid = isValid && 
                Validation.isMatch(@"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"); 
                //Not valid if not match         
        if(isValid)         
        {            
            value = Utility.StripNonNumeric(value); 
            //Clean all non numeric characters from the string.
            LogChange("myVal", _myVal, value); 
            //Log Changes to local change log             
            _myVal = value;        
        }     
    } 
}

There really isn’t anything wrong with the implementation above, if it is one or even a few properties that have to do that functionality, but when you start putting that sort of validation and change logging and formatting across hundreds or thousands of properties throughout an organization, then you are faced with an enormous opportunity for mugged code. How do we prevent it?

Good OO programming teaches us to encapsulate and reuse code whenever possible as well as to keep irrelevant code out of our applications. So somehow, we should find a way to encapsulate and isolate the functions that handle our business requirements, preferably in a way that is easy to read and implement.

Using Attributes

With attributes, we can declaratively dictate things that should be done within a property, but without having to put the code directly in the property. Using custom attributes, we can change the above property example into the following:

C#
[Required()]
[FormatValidation(@"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")] 
[StripNonNumeric()] 
[LogChanges("_myVal")]
[StringOutputFormat(@"(\d{3})(\d{3})(\d{4})","$1-$2-$3")]
public string myVal 
{
    get{return _myVal;} 
    set{_myVal = value;}
}

The result is cleaner and easier to read. The body of the accessors is clean and discrete, showing only the basic functionality of the property without any of the other gobbledygook. So looking at this code, one has to wonder, how are these attributes enforced? The answer is simple, we intercept the method call and make the necessary changes before and after the action is taken.

So, now I can hear the collective groan of everyone who has ever built custom sinks. Don’t worry, there is no need to build custom sinks or put ContextBoundObject in the inheritance chain. The only requirements for this technique to work is that you must put MarshalByRefObject in your inheritance chain and you must make use of the RealProxy class. The nice thing about this is that by enforcing MarshalByRefObject inheritance within your application, you should be setup nicely for remoting if the need for such a thing arises.

OK, so enough small talk. How is this implemented? The key is to create a proxy of your object and implement your attribute enforcement in the Invoke method. In case you are not fully aware of how a proxy works, basically, the .NET subsystem creates a shell for your real object that exists somewhere else in the digital landscape. In the case of .NET web services, the custom proxy class is what your application is really talking to, while the actual class exists solely in the memory space of the remote web server. When you request something from the distant class, the proxy takes your request and calls Invoke and passes some information in. You then normally pass that information right on and continue on your merry little way. In our case, we are going to do some other actions first.

First off, though, let’s create our proxy generator. The proxy generator must inherit from System.Runtime.Remoting.Proxies.RealProxy. When you inherit from RealProxy, you are required to implement the Invoke method. So let’s step through our custom RealProxy class.

As stated before, first inherit from RealProxy.

C#
public class CustomProxy : RealProxy, IDisposable

Now, you need some simple constructor logic. Here, we call AttachServer to make our proxy actually represent something. Notice we are detaching in our Dispose. GC would probably do this for us, but there is no sense in leaving these things just floating out there in memory.

C#
public CustomProxy(object subject):base(subject.GetType()) 
{
    AttachServer((MarshalByRefObject)subject); 
} 

public void Dispose() 
{
    DetachServer();
}

Now, in order to get a useable object, we need to get a transparent proxy. We can use a factory pattern to do this here. Note how this returns an object of type object. This is a rather abstract implementation that is intended to be used across an entire API.

C#
/// Here is the magic of our proxy.
/// We create a new instance of the class,
/// then get a transparent proxy of 
/// our server. Note that we are returning
/// object, but this is actually of 
/// whatever type our obj
/// parameter is.
public static object Instance(Object obj)
{
    return new CustomProxy(obj).GetTransparentProxy();
}

Now, we move on to our Invoke method. Here is where the magic happens. This provides for a means to intercept our method call message before it makes it to the actual method and do some things to it. This is a big method, so I have put the explanations in C# comments below:

C#
/// In the Invoke Method we do our interception work. 
public override IMessage Invoke(IMessage msg)
{ 
    /// The MethodCallMessageWrapper
    /// provides read/write access to the method 
    /// call arguments. 
    MethodCallMessageWrapper mc = 
        new MethodCallMessageWrapper((IMethodCallMessage)msg);
    ///This is the reflected method base of the called method. 
    MethodInfo mi = (MethodInfo)mc.MethodBase; 

    if(mi.IsConstructor)
    {
        object o = mi.Invoke(null, mc.Args);
        AttachServer((MarshalByRefObject)o);
    }

    ///This is the object we are proxying. 
    MarshalByRefObject owner = GetUnwrappedServer(); 
    ///Some basic initializations for later use 
    IMessage retval = null; 
    bool IsGet = false; 
    object outVal = null; 
    if(!(owner == null)) //Safety First!
    {  
        ///Let's see if the method we have 
        ///intercepted is an accessor of a 
        ///property for our object 
        PropertyInfo pi = GetMethodProperty(mi, owner, out IsGet); 
        ///if it is null, we are NOT in a property accessor 
        if(!(pi == null)) 
        { 
            ///IsGet is set by the GetMethodProperty method. It tells us if 
            ///we  are in the get accessor 
            if(IsGet)
            { 
                ///In this implementation, I have  not done any pre or post 
                ///processing for output attributes. This could
                ///be implemented, though to conditionally return something 
                ///other than the stored value. As it stands here, we simply
                ///invoke the get accessor and store the result into our 
                ///holding variable.
                outVal = mi.Invoke(owner, mc.Args);

                /// Ok, so we are in the get accessor, do we have any output 
                /// attributes assigned to this property? 
                if(pi.IsDefined(typeof(IOutputAttribute), true)) 
                { 
                    ///We do indeed! 
                    ///Now we get all of the custom attributes that inherit 
                    ///from IOutputAttribute.
                    IOutputAttribute[] outputattrs = 
                         (IOutputAttribute[])pi.GetCustomAttributes(
                             typeof(IOutputAttribute), true);
                    foreach(IOutputAttribute outputattr in outputattrs)
                    {
                        ///Pretty self explanatory here. Check the types and 
                        ///execute them accordingly. Calling order
                        ///would be a deterministic thing and would depend on 
                        ///some high level business requirements. (i.e.
                        ///formatters come after security modifiers, etc).
                        if( outputattr is StringOutputFormat)outVal = 
                              ((StringOutputFormat)outputattr).Parse(outVal);
                    }
                }
            }
            else
            {
                ///This is the else clause for the [if(IsGet)] statement 
                ///above. So we are in the set accessor.
                ///we initialize a local var to the first (and presumably 
                ///only) input argument for the method call.
                object inputParm = mc.InArgs[0];

                ///Since we *can* do validation, we should assume that we are.
                ///This flag defaults to true. The concept here 
                ///is that we are considered valid unless someone proves us 
                ///wrong.
                bool isValid = true;

                ///So do we have any Validators on this property?
                if(pi.IsDefined(typeof(ValidationAttributeBase),true))
                {
                    ///We do indeed!  Let's get them using the base class 
                    ///(don't you love inheritance?).
                    ValidationAttributeBase[] validators = 
                        (ValidationAttributeBase[])pi.GetCustomAttributes(
                            typeof(ValidationAttributeBase), true);

                    foreach(ValidationAttributeBase validator in validators)
                    {
                        ///So now we just run the isValid method for each 
                        ///validator and sum it against the existing isValid 
                        ///state.
                        isValid = isValid && validator.isValid(inputParm);
                    }
                }

                ///Are we still valid? If we are not, we do nothing. It's 
                ///like the set never happened. Of course, this could
                ///be handled in other ways. You could throw and invalid 
                ///value exception, or store the invalid value and log
                ///the validation issue, etc.
                if(isValid)
                {
                    ///Yes, we are valid. Now, let's do our other 
                    ///pre-processing stuff

                    ///Do we have any IDecorators?
                    if(pi.IsDefined(typeof(IDecorator),true))
                    {
                        ///Yes, we do, let's get them
                        IDecorator[] decorators = 
                            (IDecorator[])pi.GetCustomAttributes(
                                typeof(IDecorator), true);
                        foreach(IDecorator decorator in decorators)
                        {
                            ///Double checking that the decorator is intended 
                            ///for input processing, then execute it
                            if( decorator is IInputAttribute)inputParm = 
                                decorator.Parse(inputParm);
                        }
                    }

                    ///Do we have a LogChanges attribute and does the owner 
                    ///implement IChangeTracker?
                    if(pi.IsDefined(typeof(LogChanges),true) && 
                        owner is IChangeTracker)
                    {
                        ///LogChange is defined in the AtributeUsage to NOT 
                        ///allow multiple. So if there are ANY, 
                        ///then there will be only one.
                        LogChanges logChange = 
                            ((LogChanges[])pi.GetCustomAttributes(
                                typeof(LogChanges), true))[0];
                        logChange.DoLog(((IChangeTracker)owner), inputParm);
                    }

                    ///We could have changed our inputparm, so let's assign 
                    ///the changed value to the arguments array
                    mc.Args[0] = inputParm;

                    //Now, we can invoke out set accessor
                    outVal = mi.Invoke(owner, mc.Args);
                }
            }
        }
            ///This is the else for the [if(!(pi == null))] statement above.
            ///This means that the current method is NOT a property accessor.
        else
        {
            ///Pass the call to the method and get our return value
            outVal = mi.Invoke(owner, mc.Args);
        }
    }

    /// The caller is expecting to get something out of the method invocation.
    /// So we need to construct a return message. This is pretty simple, 
    /// pass back out most of the stuff that was passed into us, and hand it 
    /// the outVal as well.
    retval = new ReturnMessage(
                     outVal, mc.Args, mc.Args.Length, 
                     mc.LogicalCallContext, mc);
    return retval;
}

Conclusion

And there you have it. Nothing too complicated. I have included the attribute definitions in the source files, but I didn't want to get too much into them here. The main point is that we can intercept a method call for a get or set accessor and then perform actions to modify the input and/or output of a property. Please take some time to review the source code and see how the attributes work. Next time you are putting together a robust application that contains many intersecting aspects (such as a CSLA implementation), you might consider using this proxy technique.

Points of Interest

Although I used this technique to enforce property level attributes, there is nothing to stop you from intercepting other things. When you use a proxy like this, you have access to method calls of all sorts. You can apply method level security, or redirect the method calls.

Many thanks to Andrew Cain for editing this paper for me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Chris Rogers is a consultant for International Network Services, Inc. and is based out of Dallas, TX.

He has been developing software for almost 10 years and specializes in .Net web apps and business integration APIs.

Comments and Discussions

 
GeneralMarshalByValue objects Pin
Tommaso Caldarola24-Mar-06 4:23
Tommaso Caldarola24-Mar-06 4:23 
AnswerRe: MarshalByValue objects Pin
Chris Rogers24-Mar-06 4:49
Chris Rogers24-Mar-06 4:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.