Using PostSharp in FIM Synchronization rules to reduce code complexity, and improve readability.
This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm
Maintainability of FIM Sync rules is probably one of the most often raised issues by the customers, especially when the rules were developed by someone else. For this reason, I try my best to make the rules as modular and self-documenting as possible. Recently I came across Aspect Oriented Development framework, which I believe will go a long way in improving the quality of my deliverables. In this blog I would like to share my experience in using PostSharper to improve expressiveness and modularity of FIM Sync Rules. Of course, the framework is applicable to a wide range of scenarios. Before I go any further I would suggest that you explore https://www.sharpcrafters.com/ for some ideas and samples of how Aspect Oriented Development could benefit you. In a nutshell though, the Aspect model allows us to focus code on what matters, thus clearly expressing the intent of our code, while modularizing cross-cutting concerns.
Let me show you, as an example, a method I recently re-factored using Aspects. The main intent of the function is to determine whether an AD account could be safely deleted. The method leverages two helper methods which evaluate the status of the identity from AD and HR point of view. The details of the implementation are not important here. What is important that this method is only applicable to certain types of accounts (normal user objects and not system accounts, etc. ), and that it requires userAccountControl attribute to be passed to it. I also anticipate that more granular rules will be applied in the future to filter the application of this function.
So without Aspects, I would typically create multiple if conditions which would determine if the object meets certain requirements and that all required parameters where passed to the method. Trust me when I say this, that doing this would significantly reduce the readability and maintainability of this code. In addition, I have several other methods which may require the same logic.
So with PostSharp I created several Aspects (ApplicableToPrimaryAccontsOnlyAspect and RequiresUserAccountControlAttributeAspect), which are declaratively applied to the method. The Aspects themselves are implemented as separate modules (classes) which could be re-used declaratively throughout my project. The Aspects are “weaved” into the method code by PostSharper and are executed on the method entry boundary.
As you can see, the intent the the pre-conditions of the method are very clearly expressed, thanks to the Aspects.
[ApplicableToPrimaryAccountsOnlyAspect]
[RequiresUserAccountControlAttributeAspect]
public static void ARSSafeToDeleteFlagRule(CSEntry csEntry, MVEntry mvEntry)
{
if (AreActiveDirectoryConditionsForSettingSafeToDeleteFlagMet(csEntry) &&
AreHRConditionsForSettingSafeToDeleteFlagMet(mvEntry))
{
mvEntry["SafeToDeleteFlag"].BooleanValue = true;
}
else
{
mvEntry["SafeToDeleteFlag"].BooleanValue = false;
}
}
Here is the implementation of the ApplicableToPrimaryAcccountOnlyAspect.
There are two points worth mentioning here:
1. In the OnMethodBoundaryAspect Aspect we have access to the parameters passed to the method, hence the parameter validation logic could be extracted from the main method (by main method here I mean the method to which the Aspect is attached).
2. Based on the outcome of the validation logic, we can change the flow behavior of the main method. In this example, if the account is not Primary I simply exit the main method.
using System;
using PostSharp.Aspects;
using Microsoft.MetadirectoryServices;
[Serializable]
public class ApplicableToPrimaryAccountsOnlyAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
var csEntry = args.Arguments[0] as CSEntry;
if (csEntry == null)
{
throw new Exception(
"ApplicableToPrimaryAccountsOnlyAspect received unexpected argument, was expecting CSEntry");
}
if (!csEntry["AccountClassification"].IsPresent)
{
throw new Exception(
"ApplicableToPrimaryAccountsOnlyAspect received a CSEntry without classification");
}
if (!csEntry["AccountClassification"]
.StringValue.Equals(GlobalSettingsConfig.Constants.PrimaryAccountCode))
{
args.FlowBehavior = FlowBehavior.Return;
}
}
}
References: https://www.sharpcrafters.com/postsharp/documentation