1. PostSharp AOP in .NET Business Rules + Plumbing = Enterprise Software David Ross W: www.pebblesteps.com E: willmation@gmail.com
2. Application Concerns Core Crosscutting Non-functional Requirements Model needs to provide capability for many different layers At layer boundaries Logging Validation Security Data marshalling Functional Requirements Domain/Object Model Business Logic
3. Crosscutting Concerns – Plumbing Logging/Monitoring/Alerting Type safety (Beyond value types int, string) Validation/Enforcement Security Authorisation/Authentication Data Binding INotifyPropertyChanged DependencyProperties Transactions Concurrency - Locking
4. Aspects - Reduce noise in source Move plumbing out of source, but keep behaviour the same Advice The duplicated plumbing code we are removing Typically under 30 lines of code Behaviour that is injected at a join point Join points Places within the code where the Aspect is inserted Examples Entry/Exit of a method or property Class’s Type Definition
5. Aspects Lingo Point cut Locates Join Points to apply advice Filter driven– Automatic injection Find all Methods, of type Setter, in all classes where Namespace equals “Application.Entities” Attribute driven – Manual injection
6. Aspects Lingo Weaving Process of injecting functionality back into a component Can 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
7.
8.
9. Example class public class Demo { public Demo() { ActivityRecorder.Record("In constructor"); } public void Foo() { ActivityRecorder.Record("In Foo"); } public void StaticFoo() { ActivityRecorder.Record("In StaticFoo"); } }
11. 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(); } }
12. Code after Injection private void ~Foo() { ActivityRecorder.Record("In Foo"); } [DebuggerNonUserCode, CompilerGenerated] public void Foo(){ Delegate delegateInstance = new ~PostSharp~Laos~Implementation.~delegate~0(this.~Foo); MethodInvocationEventArgseventArgs = new MethodInvocationEventArgs(delegateInstance, null); ~PostSharp~Laos~Implementation.SkillsMatter.PostSharp.Aspects. ExampleOnMethodInvocationAspect~1.OnInvocation(eventArgs); }
13. 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...
14. On Boundary Invocation - Aspect [Serializable] public class ExampleOnMethodBoundaryAspect : OnMethodBoundaryAspect{ public override void OnEntry(MethodExecutionEventArgseventArgs) { ActivityRecorder.Record(string.Format("Before {0}", eventArgs.Method)); } public override void OnExit(MethodExecutionEventArgseventArgs) { ActivityRecorder.Record(string.Format("After {0}", eventArgs.Method)); } public override void OnException(MethodExecutionEventArgseventArgs) { ActivityRecorder.Record(string.Format("After {0}", eventArgs.Method)); } }
15. [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(); }
16. Manual Point Cut public 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; } }
17. public override void OnEntry (MethodExecutionEventArgseventArgs) { 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); }
19. 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 afterwards Transactions – Commit/Rollback as required Concurrency – Mutual Exclusion around code
20. Other Aspects Implement Method Aspect Replace a method’s content with the advice in the aspect Useful for modifying 3rd party components Composition Aspect Allows an interface/state to be injected into a component Used to simulate multiple inheritance Examples include Adding .NET Win Form data binding to a POCO Adding Entity Framework interfaces to a POCO
21. Breaking the Build All NHibernate methods must be virtual – Davy Brion publicclassRequireVirtualMethodsAndProperties : OnMethodBoundaryAspect { publicoverrideboolCompileTimeValidate(MethodBase method) { if (!method.IsVirtual) { stringmethodName = method.DeclaringType.FullName + “.” + method.Name; var message = newMessage(SeverityType.Fatal, “MustBeVirtual”, string.Format(“{0} must be virtual”, methodName), GetType().Name); MessageSource.MessageSink.Write(message); returnfalse; } returntrue; } }