|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
AbstractOne 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 The BasicsAs 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 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
Given these requirements, here is what the property would look like using normal C# code: 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 AttributesWith 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 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 First off, though, let’s create our proxy generator. The proxy generator must inherit from As stated before, first inherit from public class CustomProxy : RealProxy, IDisposable
Now, you need some simple constructor logic. Here, we call 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 /// 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 /// 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;
}
ConclusionAnd 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 Points of InterestAlthough 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.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||