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 get
s and set
s; 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
.
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:
- It is required. Since it is a string field, this means it cannot be
DBNull.Value
or String.Empty
.
- 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.
- The actual value being persisted should not have any formatting, but should always display in the ###-###-#### format, no matter what the user entered.
- The original value should be stored for audit reasons.
Given these requirements, here is what the property would look like using normal C# code:
public string myVal
{
get
{
return _myVal.Substring(0,3) + "-" + _myVal.Substring(3,3) + "-" +
_myVal.Substring(7,4);
}
set
{
bool isValid = true;
isValid = isValid && !Validation.isEmpty(value);
isValid = isValid &&
Validation.isMatch(@"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}");
if(isValid)
{
value = Utility.StripNonNumeric(value);
LogChange("myVal", _myVal, value);
_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:
[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
.
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.
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.
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:
public override IMessage Invoke(IMessage msg)
{
MethodCallMessageWrapper mc =
new MethodCallMessageWrapper((IMethodCallMessage)msg);
MethodInfo mi = (MethodInfo)mc.MethodBase;
if(mi.IsConstructor)
{
object o = mi.Invoke(null, mc.Args);
AttachServer((MarshalByRefObject)o);
}
MarshalByRefObject owner = GetUnwrappedServer();
IMessage retval = null;
bool IsGet = false;
object outVal = null;
if(!(owner == null))
{
PropertyInfo pi = GetMethodProperty(mi, owner, out IsGet);
if(!(pi == null))
{
if(IsGet)
{
outVal = mi.Invoke(owner, mc.Args);
if(pi.IsDefined(typeof(IOutputAttribute), true))
{
IOutputAttribute[] outputattrs =
(IOutputAttribute[])pi.GetCustomAttributes(
typeof(IOutputAttribute), true);
foreach(IOutputAttribute outputattr in outputattrs)
{
if( outputattr is StringOutputFormat)outVal =
((StringOutputFormat)outputattr).Parse(outVal);
}
}
}
else
{
object inputParm = mc.InArgs[0];
bool isValid = true;
if(pi.IsDefined(typeof(ValidationAttributeBase),true))
{
ValidationAttributeBase[] validators =
(ValidationAttributeBase[])pi.GetCustomAttributes(
typeof(ValidationAttributeBase), true);
foreach(ValidationAttributeBase validator in validators)
{
isValid = isValid && validator.isValid(inputParm);
}
}
if(isValid)
{
if(pi.IsDefined(typeof(IDecorator),true))
{
IDecorator[] decorators =
(IDecorator[])pi.GetCustomAttributes(
typeof(IDecorator), true);
foreach(IDecorator decorator in decorators)
{
if( decorator is IInputAttribute)inputParm =
decorator.Parse(inputParm);
}
}
if(pi.IsDefined(typeof(LogChanges),true) &&
owner is IChangeTracker)
{
LogChanges logChange =
((LogChanges[])pi.GetCustomAttributes(
typeof(LogChanges), true))[0];
logChange.DoLog(((IChangeTracker)owner), inputParm);
}
mc.Args[0] = inputParm;
outVal = mi.Invoke(owner, mc.Args);
}
}
}
else
{
outVal = mi.Invoke(owner, mc.Args);
}
}
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.