post sharp talk
DESCRIPTION
Introduction to Aspect Orientated Programming in .NET using PostSharpTRANSCRIPT
PostSharpAOP in .NET
Business Rules + Plumbing = Enterprise Software
David RossW: www.pebblesteps.comE: [email protected]
Application ConcernsCore CrosscuttingFunctional
RequirementsDomain/Object
ModelBusiness Logic
Non-functional Requirements
Model needs to provide capability for many different layers
At layer boundariesLoggingValidationSecurityData marshalling
Crosscutting Concerns – PlumbingLogging/Monitoring/AlertingType safety (Beyond value types int, string)
Validation/EnforcementSecurity
Authorisation/AuthenticationData Binding
INotifyPropertyChanged DependencyProperties
TransactionsConcurrency - Locking
Aspects - Reduce noise in sourceMove plumbing out of source, but keep behaviour
the same
AdviceThe duplicated plumbing code we are removingTypically under 30 lines of codeBehaviour that is injected at a join point
Join pointsPlaces within the code where the Aspect is insertedExamples
Entry/Exit of a method or propertyClass’s Type Definition
Aspects Lingo
Point cutLocates Join Points to apply adviceFilter driven – Automatic injection
Find all Methods, of type Setter, in all classes where Namespace equals “Application.Entities”
Attribute driven – Manual injection
Aspects LingoWeavingProcess of injecting functionality back into a
componentCan be performed by
Text post processor – Magic comments can replaced by code
Proxy container – Uses decorator pattern/hooks to allow code to be inserted
Binary manipulation - Modifying assemblies - by replacing and injecting IL code
Compile-time MSIL Injection process
Copyright © by Gael Fraiteur
If Postsharp modifies a type’s signature...•No Intellisense for a referenced projects that are modified•Intellisense for referenced assemblies that are modified
PostSharp WeavingCompile time Weaving - LGPL
Automatically if a component references PostSharp.Laos.dll – Default Install configures MSBuild
Manual application using an MSBuild task
Run time Weaving – GPL/Commercial LicenseManual via PostSharp.Core.dllStatically modify the assembly just before it is
loaded into the domain
Example classpublic class Demo{
public Demo(){ActivityRecorder.Record("In constructor");}public void Foo(){ActivityRecorder.Record("In Foo");}public void StaticFoo(){ActivityRecorder.Record("In StaticFoo");}
}
[Test] public void Verify_Aspect_Called_On_Method_Invocation() {
var d = new Demo ();d.Foo();d.StaticFoo();
ActivityRecorder.AssertActivityOccured("In constructor");
ActivityRecorder.AssertActivityOccured("Calling Void Foo()");ActivityRecorder.AssertActivityOccured("In Foo");
ActivityRecorder.AssertActivityOccured("Calling Void StaticFoo()");ActivityRecorder.AssertActivityOccured("In StaticFoo");
ActivityRecorder.AssertNoMoreActivities();}
Method Invocation - Aspect[Serializable]public class ExampleOnMethodInvocationAspect :
OnMethodInvocationAspect {
public override void OnInvocation(MethodInvocationEventArgs context) {
ActivityRecorder.Record(string.Format("Calling {0}", context.Delegate.Method.Replace(“~”, “”));
// Do I want to continue??
context.Proceed(); }}
Code after Injectionprivate void ~Foo() { ActivityRecorder.Record("In Foo");}
[DebuggerNonUserCode, CompilerGenerated]public void Foo(){
Delegate delegateInstance = new ~PostSharp~Laos~Implementation.~delegate~0(this.~Foo);
MethodInvocationEventArgs eventArgs = new MethodInvocationEventArgs(delegateInstance, null);
~PostSharp~Laos~Implementation.SkillsMatter.PostSharp.Aspects.
ExampleOnMethodInvocationAspect~1.OnInvocation(eventArgs);
}
Filter based Point Cut [assembly:
ExampleOnMethodInvocationAspect(AttributeTargetAssemblies =
"SkillsMatter.PostSharp", AttributeTargetTypes = "SkillsMatter.PostSharp.OnMethodInvocation.*")]
Very simple to apply changes to all business objects in the solution with a single filter...
On Boundary Invocation - Aspect
[Serializable]public class ExampleOnMethodBoundaryAspect : OnMethodBoundaryAspect{
public override void OnEntry(MethodExecutionEventArgs eventArgs) {ActivityRecorder.Record(string.Format("Before {0}",
eventArgs.Method));}
public override void OnExit(MethodExecutionEventArgs eventArgs) {ActivityRecorder.Record(string.Format("After {0}", eventArgs.Method));
}
public override void OnException(MethodExecutionEventArgs eventArgs) {ActivityRecorder.Record(string.Format("After {0}", eventArgs.Method));
}}
[Test] public void Verify_Aspect_Called_On_Method_Boundary() {var e = new Demo();
e.Foo();e.StaticFoo();
ActivityRecorder.AssertActivityOccured("Before Void .ctor()");ActivityRecorder.AssertActivityOccured("In constructor");ActivityRecorder.AssertActivityOccured("After Void .ctor()");
ActivityRecorder.AssertActivityOccured("Before Void Foo()");ActivityRecorder.AssertActivityOccured("In Foo");ActivityRecorder.AssertActivityOccured("After Void Foo()");
ActivityRecorder.AssertActivityOccured("Before Void StaticFoo()");ActivityRecorder.AssertActivityOccured("In StaticFoo");ActivityRecorder.AssertActivityOccured("After Void StaticFoo()");
ActivityRecorder.AssertNoMoreActivities();}
Manual Point Cutpublic class FoodQuestionaire{
[RegularExpressionValidator("^([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]? {1,2}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)$")]
public string Postcode { get; set; }
[RangeValidator(0, 5)]public int? LikesDiary { get; set; }[RangeValidator(0, 5)]public int? LikesBeef { get; set; }[RangeValidator(0, 5)]public int? LikesFish { get; set; }
}
public override void OnEntry(MethodExecutionEventArgs eventArgs) {
if (!eventArgs.Method.Name.StartsWith(“set_"))return;
int value = (int)args[0];if (value < LowerBoundary || value > UpperBoundary)
throw new ValidationException("Value must be between " + LowerBoundary + " and “ +
UpperBoundary);
base.OnEntry(eventArgs);}
[Test]public void RangeTest() {
FoodQuestionaire q = new FoodQuestionaire();
q.LikesBeef = 5;Assert.AreEqual(5, q.LikesBeef);Assert.Throws<ValidationException>(() => q.LikesBeef = 100);Assert.Throws<ValidationException>(() => q.LikesBeef = -3);Assert.AreEqual(5, q.LikesBeef);
q.Postcode = "E14 0AN";Assert.AreEqual("E14 0AN", q.Postcode);Assert.Throws<ValidationException>(() => q.Postcode = "Hello World");Assert.AreEqual("E14 0AN", q.Postcode);
}
On Method Boundary can be used for...How long did a method take to execute?Resource Management – Create resource
before a method is called and release afterwardsTransactions – Commit/Rollback as requiredConcurrency – Mutual Exclusion around code
Other AspectsImplement Method Aspect
Replace a method’s content with the advice in the aspect
Useful for modifying 3rd party components
Composition AspectAllows an interface/state to be injected into a
componentUsed to simulate multiple inheritanceExamples include
Adding .NET Win Form data binding to a POCO Adding Entity Framework interfaces to a POCO
Breaking the BuildAll NHibernate methods must be virtual – Davy Brion
public class RequireVirtualMethodsAndProperties : OnMethodBoundaryAspect {
public override bool CompileTimeValidate(MethodBase method) { if (!method.IsVirtual) { string methodName = method.DeclaringType.FullName + “.”
+ method.Name; var message = new Message(SeverityType.Fatal, “MustBeVirtual”, string.Format(“{0} must be virtual”, methodName), GetType().Name); MessageSource.MessageSink.Write(message); return false; } return true; }}
Learn more...Talk’s source code @ www.pebblesteps.com
http://www.postsharp.org/http://davybrion.com/blog/category/postsharp/http://www.codeplex.com/ValidationAspectshttp://www.eclipse.org/aspectj/doc/released/
progguide/
Thanks for listeningQuestions?