Friday, January 18, 2008

PostSharp - its like triggers that dont suck

**Update-- I have some amended code in a newer post located here**

Do you remember the first time you learned about triggers? Maybe you were like me, and you were initially fooled in to thinking that hidden behavior hiding behind basic operations was a good idea, only to discover its ugly side later down the road.

PostSharp brings the notion of triggers to C#, except this time I've found a safe and easy way to reuse a lot of common code with just a little bit of AOP.

Ive been pulled in to the debate about how to supplement class properties with common functionalities such as validation, logging, "NotifyPropertyChanged", Observability, and other common stuff that needs to occur with properties.

We want our Business objects to have a uniform behavior, but we often write redundant code in each property for validation, logging, databinding etc...

This afternoon I came accross PostSharp and WOW I am impressed with its potential!

Imagine this scenario:
you have a BusinessObject with a Name property.
  • the name property should not allow nulls
  • it should not allow empty strings
  • it should not allow strings longer than length of 10
  • logging should be performed before and after the property has changed
  • logging should occur if theres any exceptions that crop up inside of the depths of the Name property


public class SomeBusinessObject
{
#region members
private string m_Name;
#endregion

#region properties

[NotNullValidator]
[Logger( LogOnEntry = true, LogOnException = true, LogOnSuccess = true)]
[StringLengthValidator(MinLength = 1,MaxLength = 10)]
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
#endregion

#region methods
public SomeBusinessObject(string name)
{
m_Name = name;
}
#endregion
}

And I am done. No Kidding! Check out my little console program:

class Program
{
static void Main(string[] args)
{
SomeBusinessObject someBusinessObject = new SomeBusinessObject("Pete");

try
{
Console.WriteLine("Try passing in something long");
someBusinessObject.Name = "Some name that is too long";
}
catch(Exception e)
{
Console.WriteLine("Oops! that was too long");
}
try
{
Console.WriteLine("Try passing in something too short");
someBusinessObject.Name = string.Empty;
}
catch(Exception e)
{
Console.WriteLine("Oops! That was too short");
}
try
{
Console.WriteLine("Try passing in a null");
someBusinessObject.Name = null;
}
catch
{
Console.WriteLine("Oops, that was too null...");
}

Console.WriteLine("Run a successful property change.");
someBusinessObject.Name = "123";
Console.Read();
}
}

The output of this looks like:

Try passing in something long
OnEntry: set_Name
OnException: set_Name
Oops! that was too long
Try passing in something too short
OnEntry: set_Name
OnException: set_Name
Oops! That was too short
Try passing in a null
Oops, that was too null...
Run a successful property change.
OnEntry: set_Name
OnSuccess: set_Name

Note how the order of the attributes decides the precedence of execution. So, whats the trick? I defined those validation and logging attributes. They all inherit from OnMethodBoundaryAspect which gives me the ability to inject logic right before and after the execution of a method.
For example, here is the Null-checking property validator:

[Serializable]
public class NotNullValidator : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
if (eventArgs.GetArguments()[0] == null)
throw new Exception(string.Format("Value was null."));
base.OnEntry(eventArgs);
}
}

Here is the string length property validator:

[Serializable]
public class StringLengthValidator : OnMethodBoundaryAspect
{
public int MinLength
{
get;
set;
}
public int MaxLength
{
get;
set;
}
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
string value = eventArgs.GetArguments()[0] as string;
if(value.Length>MaxLength)
throw new Exception(string.Format("Exceeded maximum string length of {0}",MaxLength));
if(value.Length<MinLength)
throw new Exception(string.Format("Fell short of the minimum string length of {0}",MinLength));
base.OnEntry(eventArgs);
}
}

and finally, the Logging code:

[Serializable]
public class Logger : OnMethodBoundaryAspect
{
#region properties
public bool LogOnEntry
{
get;
set;
}
public bool LogOnExit
{
get;
set;
}
public bool LogOnException
{
get;
set;
}
public bool LogOnSuccess
{
get;
set;
}
#endregion

#region methods
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
if(LogOnEntry)
Console.WriteLine("OnEntry: {0}", eventArgs.Method.Name);
base.OnEntry(eventArgs);
}
public override void OnException(MethodExecutionEventArgs eventArgs)
{
if(LogOnException)
Console.WriteLine("OnException: {0}", eventArgs.Method.Name);
base.OnException(eventArgs);
}
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
if(LogOnExit)
Console.WriteLine("OnExit: {0}", eventArgs.Method.Name);
base.OnExit(eventArgs);
}
public override void OnSuccess(MethodExecutionEventArgs eventArgs)
{
if(LogOnSuccess)
Console.WriteLine("OnSuccess: {0}", eventArgs.Method.Name);
base.OnSuccess(eventArgs);
}
#endregion
}

and this is just a warmup!

I found an example of INotifyPropertyChanged and IEditable property attributes in vb, I'm in the process of converting it to C# and I should have this in the next few days.

0 Comments:

Post a Comment

<< Home