Hello everyone, had been a long time from my last post (as usual, been busy busy busy).
Today I want to talk about something that for most .Net developers is unknown but has been in the development industry a long time ago. I’m talking about Aspect Oriented Programming - AOP.
AOP can help you to remove a lot of boilerplate code and to separate concerns from the code your are implementing. I don’t want to talk much about this because surely there’s far more smarter people than I out there that could explain in more detail what AOP is and how you can improve yourself by using it (like ayende).
I came across PostSharp a couple of months ago and immediately felt that it was such a great tool to use with no effort, it allows you to do AOP in existent code, and even better if you start using it from day zero at a project.
What this post is about? Well, the idea is to introduce licensing features to an existent Windows Form application, you know, that little thing that you see when you are evaluating a product and you get notifications about license renewal or limited use of some features given some condition.
Our little app looks like this:
Every single button just display a MessageBox with its Text property as the message content. Our initial code looks like the following:
private void button_Click(object sender, EventArgs e)
{
MessageBox.Show("Executing => " + ((Button)(sender)).Text);
}
Every button has its Click event handler pointing to this method.
Here is the unrealistic scenario that our app now has to support:
- All the buttons inside the “Unrestricted Methods” groupbox are not handled by the licensing component.
- All the buttons inside the “Restricted Methods” groupbox are handled by the licensing component.
- For Button1 and Button2, the spec says that if the licensing component deny the use of the application, the method should not be executed, and a message at the status bar should appear notifying this problem.
- For Button3, the spec says that if the licensing component deny the use of the application, the method should be executed as always, but this only must be allowed 10 times at max, after that it should cancel the method execution and throw an exception, as with the previous buttons, the user should be notified about such problem.
- For Button4, the spec says that if the licensing component deny the use of the application, the method should be executed as always, but the user should be notified that besides this fact the license should be renewed.
Ok, so lets start implementing those updates into our code.
In the attached sample project you’ll find the final solution for this article; the EmiajNet.AOP.Licensing.Foundation is the key of everything, lets examine one by one each class.
LicensingManager.cs
This is the class which is in charge of evaluate if the license is valid. Well, as you can see from the code, the license is only valid when in the mornings. The silliest condition let me tell you, but for our sample project works just fine. In a real world app, you may want to check some registry key, or to call a webservice to check the user credentials or anything else.
public static class LicensingManager
{
public static bool IsValid()
{
return DateTime.Now.Hour < 12;
}
}
LicensingCheckOptions.cs
Those are the constraints that are going to be used for our app for the monitored methods.
ContinueInvocation means that it should not cancel the method execution.
EndInvocation means that it should cancel the method execution.
ThrowSecurityCheckOperationException means that it should throw our custom SecurityCheckOperationException error.
public enum LicensingCheckOptions
{
ContinueInvocation,
EndInvocation,
ThrowSecurityCheckOperationException
}
LicensingHandlerParameter.cs
This is a class used to pass information about the method being handled by our licensing component, it has properties to diagnostic where the problem occurs so the client could handled according by inspecting those properties.
Instance is the instance (duh!) of the class on which the method is executed.
Tag is an arbitrary value that is assigned to the LicensingCheckAttribute (more info below) which decorate the intercepted method.
Method is the object that represent to the method being intercepted.
Arguments is the object that represent the original parameters that were used to call to the intercepted method.
CheckOptions is the LicensingCheckOption enum value assigned to the LicensingCheckAttribute (more info below) which decorate the intercepted method.
public class LicensingHandlerParameter
{
public object Instance { get; protected set; }
public object Tag { get; protected set; }
public MethodInfo Method { get; protected set; }
public object[] Arguments { get; protected set; }
public LicensingCheckOptions CheckOption { get; protected set; }
public LicensingHandlerParameter(object instance, object tag, MethodInfo method, object[] arguments, LicensingCheckOptions checkOption)
{
Instance = instance;
Tag = tag;
Method = method;
Arguments = arguments;
CheckOption = checkOption;
}
}
ILicensingHandler.cs
This is a key class in our licensing component, when our LicensingCheckAttribute detects that the host instance implements this interface, uses their methods to notify the several stages of the evaluation. The name of the methods indicate their meaning as well.
public interface ILicensingHandler
{
void BeforePerformSecurityCheck(LicensingHandlerParameter information);
void AfterPerformSecurityCheck(LicensingHandlerParameter information, bool valid);
void SecurityCheckFailed(LicensingHandlerParameter information);
void HandleSecurityCheckOperationException(LicensingHandlerParameter information, ref bool cancel);
}
SecurityCheckOperationException.cs
This is our custom exception to be thrown when the IsValid method of our licensing component (LicensingManager) return false and the intercepted method is marked with our custom attribute indicating that an error must be thrown (by configuring the CheckOption property as ThrowSecurityCheckOperationException).
public class SecurityCheckOperationException : InvalidOperationException
{
}
LicensingCheckAttribute.cs
And finally the most important class in the whole project, this class inherits from PostSharp.Laos.OnMethodInvocationAspect, which allow us to decorate any method and intercept it just before gets executed, and we can do whatever we want at that stage, like cancel the execution or do some other crazy stuff.
The interesting bits occur in the OnInvocation method.
From line 21 to 25 we are just assigning values to variables that are going to be used later with the help of the eventArgs parameter which holds a lot of useful information.
Line 26 to 29 is for notify to the ILicesingHandler implementor (if any) that we are about to perform a validation check.
Line 30 is where we call to the LicensingManager class to check if the license is in a valid state.
Between line 31 to 59 we handle the scenario when the validation check had failed.
From line 33 to 36 we notify to the ILicensingHandler implementor (if any) that the validation check had failed.
From line 37 to 58 we check what is the value of the CheckOption property, and perform the according strategy to represent such option.
If the enum is assigned as ContinueInvocation, then we just execute the original method by calling to base.OnInvocation(eventArgs).
If the enum is assigned as EndInvocation, then we break the switch statement and nothing else is done.
if the enum is assigned as ThrowSecurityCheckOperationException, then we inform about this to the ILicensingHandler implementor (if any) and we pass a boolean variable “by ref”, this boolean variable can be used to abort this action (no exception is throw), if the implementor does not cancel this, then the exception is thrown, if is the opposite, then we do not throw any exception and execute the original method.
From line 60 to 64, we execute the original method because the LicensingManager class returns a valid result for the license evaluation.
And finally from line 65 to 68, we notify to the ILicensingHandler implementor (if any) that the security check had terminated.
6 [Serializable]
7 public class LicensingCheckAttribute : OnMethodInvocationAspect
8 {
9 public LicensingCheckOptions CheckOption
10 {
11 get;
12 set;
13 }
14
15 public object Tag { get; set; }
16
17 public override void OnInvocation(MethodInvocationEventArgs eventArgs)
18 {
19 // Security check goes here
20 // DO NOT USE THIS IN PRODUCTION
21 var licensingHandler = eventArgs.Instance as ILicensingHandler;
22 var instance = eventArgs.Instance;
23 var method = eventArgs.Method;
24 var arguments = eventArgs.GetArgumentArray();
25 var licensingHandlerParameter = new LicensingHandlerParameter(instance, Tag, method, arguments, CheckOption);
26 if (licensingHandler != null)
27 {
28 licensingHandler.BeforePerformSecurityCheck(licensingHandlerParameter);
29 }
30 var isValid = LicensingManager.IsValid();
31 if (!isValid)
32 {
33 if (licensingHandler != null)
34 {
35 licensingHandler.SecurityCheckFailed(licensingHandlerParameter);
36 }
37 switch (CheckOption)
38 {
39 case LicensingCheckOptions.ContinueInvocation:
40 base.OnInvocation(eventArgs);
41 break;
42 case LicensingCheckOptions.EndInvocation:
43 break;
44 case LicensingCheckOptions.ThrowSecurityCheckOperationException:
45 bool cancel = false;
46 if (licensingHandler != null)
47 {
48 licensingHandler.HandleSecurityCheckOperationException(licensingHandlerParameter, ref cancel);
49 }
50 if (!cancel)
51 {
52 throw new SecurityCheckOperationException();
53 }
54 base.OnInvocation(eventArgs);
55 return;
56 default:
57 throw new ArgumentOutOfRangeException();
58 }
59 }
60 else
61 {
62 base.OnInvocation(eventArgs);
63
64 }
65 if (licensingHandler != null)
66 {
67 licensingHandler.AfterPerformSecurityCheck(licensingHandlerParameter, isValid);
68 }
69 }
70 }
With all this infrastructure code we can go and change our Winform App to use our licensing component as follows:
8 public partial class Form1 : Form, ILicensingHandler
9 {
10 public Form1()
11 {
12 InitializeComponent();
13 }
14
15 private int _invalidCallsCount;
16
17 [LicensingCheck(CheckOption = LicensingCheckOptions.EndInvocation)]
18 private void button1_Click(object sender, EventArgs e)
19 {
20 MessageBox.Show("Executing => " + button1.Text);
21 }
22
23 [LicensingCheck(CheckOption = LicensingCheckOptions.EndInvocation)]
24 private void button2_Click(object sender, EventArgs e)
25 {
26 MessageBox.Show("Executing => " + button2.Text);
27 }
28
29 [LicensingCheck(CheckOption = LicensingCheckOptions.ThrowSecurityCheckOperationException)]
30 private void button3_Click(object sender, EventArgs e)
31 {
32 MessageBox.Show("Executing => " + button3.Text);
33 }
34
35 [LicensingCheck(CheckOption = LicensingCheckOptions.ContinueInvocation)]
36 private void button4_Click(object sender, EventArgs e)
37 {
38 MessageBox.Show("Executing => " + button4.Text);
39 }
40
41 private void unrestricted_Click(object sender, EventArgs e)
42 {
43 securityCheckStatus.Text = "Unrestricted method";
44 MessageBox.Show("Executing => " + ((Button)(sender)).Text);
45 }
46
47 public void BeforePerformSecurityCheck(LicensingHandlerParameter information)
48 {
49 securityCheckStatus.Text = "Performing Security Check";
50 }
51
52 public void AfterPerformSecurityCheck(LicensingHandlerParameter information, bool valid)
53 {
54 if (valid)
55 {
56 securityCheckStatus.Text = "You have licensing permissions to execute this method " + information.Method.Name;
57 }
58 else
59 {
60 if (information.CheckOption == LicensingCheckOptions.ContinueInvocation)
61 {
62 securityCheckStatus.Text = "You have temporary permissions to execute this method " +
63 information.Method.Name + "\n please renew your license";
64 }
65 else
66 {
67 securityCheckStatus.Text = "You don't have licensing permissions to execute this method " +
68 information.Method.Name + "\n please renew your license";
69 }
70 }
71 }
72
73 public void SecurityCheckFailed(LicensingHandlerParameter information)
74 {
75 Debug.WriteLine("Security check failed for " + information.Method.Name);
76 }
77
78 public void HandleSecurityCheckOperationException(LicensingHandlerParameter information, ref bool cancel)
79 {
80 if (_invalidCallsCount < 10)
81 {
82 string message =
83 string.Format(
84 "You only have {0} more chances of use this method, after that, it will throw an exception.",
85 10 - _invalidCallsCount);
86 securityCheckStatus.Text = message;
87 _invalidCallsCount++;
88 cancel = true;
89 }
90 else
91 {
92 securityCheckStatus.Text = "You have run out of chances to execute this method " +
93 information.Method.Name + "\n please renew your license";
94 }
95 }
96 }
You can inspect the solution attached to this article to understand how everything works, and for the lazy programmer out there, here is a small video showcasing how the application behaves after those changes.
You can download the sample project from here.
Shameless plug: You can read the original article on my blog here.
Happy coding, bye bye.