Last active
May 29, 2018 01:09
-
-
Save punitganshani/b642f883fad0cc0d304ed38f7bebd42e to your computer and use it in GitHub Desktop.
Roslyn way to generate NRules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"RuleName": "MyRuleRocks", | |
"Product": "general", | |
"Version": "1.0", | |
"Description" : "Wonderful rule, that doesnot do wonders", | |
"Tags": [ | |
"latest" | |
], | |
"IsActive": true, | |
"IfExpression": "() => customer, c => c.IsPreferred", | |
"ExistExpression": "o => true", | |
"Actions": [ | |
{ | |
"Name": "NotifyAboutDiscount", | |
"Type": 0, | |
"ParamNames": null | |
}, | |
{ | |
"Name": "DoSomethingToo", | |
"Type": 0, | |
"ParamNames": [ | |
"Price" | |
] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BizRules.Definitions; | |
using BizRules.Domain.Core; | |
using BizRules.Domain.Objects; | |
using BizRules.Filters; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using Newtonsoft.Json; | |
using NRules.Fluent.Dsl; | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using static BizRules.Builder.RuleAction; | |
namespace BizRules.Builder | |
{ | |
public class RoslynBuilder | |
{ | |
public Assembly Build() | |
{ | |
var dlls = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES").ToString().Split(new char[] { ';' }); | |
List<MetadataReference> metadataReferences = new List<MetadataReference>() | |
{ | |
MetadataReference.CreateFromFile(typeof(Rule).Assembly.Location), | |
MetadataReference.CreateFromFile(typeof(IRuleDomainObject).Assembly.Location) | |
}; | |
foreach (var platformDLL in dlls) | |
{ | |
metadataReferences.Add(MetadataReference.CreateFromFile(platformDLL)); | |
} | |
List<CompilationUnitSyntax> units = new List<CompilationUnitSyntax> | |
{ | |
new RoslynConfigurationBuilder<Customer>(SampleRuleDefinitions.GetCustomerRuleFromConfig()).GetCompilationUnitSyntax(), | |
new RoslynConfigurationBuilder<Customer>(SampleRuleDefinitions.GetCustomerRuleFromCode()).GetCompilationUnitSyntax() | |
}; | |
CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, | |
optimizationLevel: OptimizationLevel.Debug, | |
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, | |
allowUnsafe: true); | |
compilationOptions.WithUsings(RuleSections.GetNamespaces()); | |
var compilation = CSharpCompilation.Create(assemblyName: "rules", | |
syntaxTrees: units.Select(x => x.SyntaxTree).ToArray(), // new SyntaxTree[] { unit.SyntaxTree }, | |
references: metadataReferences, | |
options: compilationOptions); | |
var stream = new MemoryStream(); | |
var emitResult = compilation.Emit(stream); | |
if (emitResult.Success) | |
{ | |
stream.Seek(0, SeekOrigin.Begin); | |
return Assembly.Load(stream.ToArray()); | |
} | |
else | |
{ | |
foreach (var item in emitResult.Diagnostics) | |
{ | |
Debug.WriteLine(item.ToString()); | |
Console.WriteLine(item.ToString()); | |
} | |
} | |
throw new Exception("Rules could not be built"); | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BizRules.Domain.Classifiers; | |
using BizRules.Domain.Core; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using NRules.Fluent.Dsl; | |
using System.Diagnostics; | |
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | |
namespace BizRules.Builder | |
{ | |
public class RoslynConfigurationBuilder<T> where T : IRuleDomainObject | |
{ | |
public RoslynConfigurationBuilder(RuleConfiguration<T> configuration) | |
{ | |
Configuration = configuration; | |
} | |
public RuleConfiguration<T> Configuration { get; } | |
public CompilationUnitSyntax GetCompilationUnitSyntax() | |
{ | |
var domainObjectType = typeof(T); | |
var unit = CompilationUnit(); | |
foreach (var ns in RuleSections.GetNamespaces()) | |
{ | |
unit = unit.AddUsings(UsingDirective(SyntaxFactory.ParseName(ns)).NormalizeWhitespace()); | |
} | |
var defineMethod = MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier(RuleSections.DEFINE)) | |
.AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)); | |
defineMethod = defineMethod.AddBodyStatements(DefineLocalVariable<T>(), | |
GetWhenMatchExistsStatement<T>(Configuration), | |
GetThenDoStatement<T>(Configuration)) | |
.NormalizeWhitespace(); | |
// class RuleName | |
var classDeclaration = ClassDeclaration(Configuration.RuleName); | |
// public class RuleName | |
classDeclaration = classDeclaration.AddModifiers(Token(SyntaxKind.PublicKeyword)); | |
classDeclaration = classDeclaration.AddBaseListTypes( | |
SimpleBaseType(ParseTypeName(typeof(Rule).FullName)) | |
); | |
// Add Rules Attributes | |
classDeclaration = classDeclaration.AddAttributeLists(GetRuleAttributes(Configuration)); | |
// Add Method | |
classDeclaration = classDeclaration.AddMembers(defineMethod); | |
// define class | |
unit = unit.AddMembers(classDeclaration) | |
.NormalizeWhitespace(); | |
Debug.WriteLine($"Generated code for rule {Configuration.RuleName} \r\n {unit.ToFullString()}"); | |
return unit; | |
} | |
private AttributeListSyntax GetRuleAttributes<T>(RuleConfiguration<T> configuration) where T : IRuleDomainObject | |
{ | |
var attributeList = new SeparatedSyntaxList<AttributeSyntax>(); | |
attributeList = attributeList.Add(GetAttribute<ProductAttribute>(configuration.Product)); | |
if (!string.IsNullOrEmpty(configuration.RuleName)) | |
{ | |
attributeList = attributeList.Add(GetAttribute<NameAttribute>(configuration.RuleName)); | |
} | |
if (!string.IsNullOrEmpty(configuration.Description)) | |
{ | |
attributeList = attributeList.Add(GetAttribute<DescriptionAttribute>(configuration.Description)); | |
} | |
attributeList = attributeList.Add(GetAttribute<PriorityAttribute>(configuration.Priority)); | |
foreach (var tag in configuration.Tags) | |
{ | |
attributeList = attributeList.Add(GetAttribute<TagAttribute>(tag)); | |
} | |
return SyntaxFactory.AttributeList(attributeList); | |
} | |
private static AttributeSyntax GetAttribute<T>(string value) | |
{ | |
var name = SyntaxFactory.ParseName(typeof(T).FullName); | |
var arguments = SyntaxFactory.ParseAttributeArgumentList($"(\"{value}\")"); | |
var attribute = SyntaxFactory.Attribute(name, arguments); | |
return attribute; | |
} | |
private static AttributeSyntax GetAttribute<T>(int value) | |
{ | |
var name = SyntaxFactory.ParseName(typeof(T).FullName); | |
var arguments = SyntaxFactory.ParseAttributeArgumentList($"({value.ToString()})"); | |
var attribute = SyntaxFactory.Attribute(name, arguments); | |
return attribute; | |
} | |
private static ExpressionStatementSyntax GetWhenMatchExistsStatement<T>(RuleConfiguration<T> configuration) where T : IRuleDomainObject | |
{ | |
ExpressionStatementSyntax output = null; | |
var MatchMethodCallWithGeneric = CreateMethodNameWithGenerics<T>(RuleSections.MATCH); | |
var invocationWhen = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, | |
CreateMethodCall(RuleSections.WHEN), | |
MatchMethodCallWithGeneric); | |
var finalExpression = InvocationExpression(invocationWhen).WithArgumentList(CreateExpression(configuration.IfExpression)); | |
if (!string.IsNullOrEmpty(configuration.ExistExpression)) | |
{ | |
GenericNameSyntax ExistsMethodName = CreateMethodNameWithGenerics<T>(RuleSections.EXISTS); | |
var invocationExists = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, finalExpression, ExistsMethodName); | |
finalExpression = InvocationExpression(invocationExists).WithArgumentList(CreateExpression(configuration.ExistExpression)); | |
} | |
output = ExpressionStatement(finalExpression).NormalizeWhitespace(); | |
return output; | |
} | |
private static ExpressionStatementSyntax GetThenDoStatement<T>(RuleConfiguration<T> configuration) where T : IRuleDomainObject | |
{ | |
MemberAccessExpressionSyntax invocation = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, | |
CreateMethodCall(RuleSections.THEN), IdentifierName(Identifier(RuleSections.DO))); | |
InvocationExpressionSyntax finalExpression = null; | |
foreach (var action in configuration.Actions) | |
{ | |
//TODO: Validate if the Method actually exists on T object | |
var expression = $"_ => {PropertyMapper.GetPropertyNameFor<T>()}.{action.Name}()"; | |
if (finalExpression == null) // first call | |
{ | |
finalExpression = InvocationExpression(invocation).WithArgumentList(CreateExpression(expression)); | |
} | |
else | |
{ | |
//IdentifierName(MissingToken(SyntaxKind.IdentifierToken)) | |
invocation = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, | |
finalExpression, IdentifierName(Identifier(RuleSections.DO))); | |
finalExpression = InvocationExpression(invocation).WithArgumentList(CreateExpression(expression)); | |
} | |
} | |
ExpressionStatementSyntax output = ExpressionStatement(finalExpression); | |
Debug.WriteLine(output.ToFullString()); | |
return output; | |
} | |
private static InvocationExpressionSyntax CreateMethodCall(string methodName) | |
{ | |
return InvocationExpression(IdentifierName(methodName)); | |
} | |
private static GenericNameSyntax CreateMethodNameWithGenerics<T>(string methodName) where T : IRuleDomainObject | |
{ | |
var typeSyntax = TypeArgumentList(SingletonSeparatedList<TypeSyntax>(IdentifierName(typeof(T).Name))); | |
return GenericName(Identifier(methodName)).WithTypeArgumentList(typeSyntax); | |
} | |
private static ArgumentListSyntax CreateExpression(string lambdaExpression) | |
{ | |
var existsExpression = SyntaxFactory.ParseExpression(lambdaExpression); | |
return ArgumentList(SingletonSeparatedList<ArgumentSyntax>(Argument(existsExpression))); | |
} | |
private static LocalDeclarationStatementSyntax DefineLocalVariable<T>() where T : IRuleDomainObject | |
{ | |
return LocalDeclarationStatement( | |
VariableDeclaration(IdentifierName(typeof(T).Name)) | |
.WithVariables(SingletonSeparatedList( | |
VariableDeclarator(Identifier(PropertyMapper.GetPropertyNameFor<T>())) | |
.WithInitializer(EqualsValueClause(DefaultExpression(IdentifierName(typeof(T).Name))))))) | |
.NormalizeWhitespace(); | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BizRules.Domain.Classifiers; | |
using BizRules.Domain.Core; | |
using System.Collections.Generic; | |
namespace BizRules.Builder | |
{ | |
public class RuleConfiguration<T> where T : IRuleDomainObject | |
{ | |
public string RuleName { get; set; } | |
public string Product { get; set; } | |
public string[] Tags { get; set; } | |
public string IfExpression { get; set; } | |
public string ExistExpression { get; set; } | |
public List<RuleAction> Actions { get; set; } | |
public string Version { get; set; } | |
public bool IsActive { get; set; } | |
public string Description { get; set; } | |
public int Priority { get; set; } | |
public RuleConfiguration() | |
{ | |
Product = ProductAttribute.Default; | |
Priority = 100; | |
IsActive = true; | |
Version = "1.0"; | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
namespace BizRules.Builder | |
{ | |
public static class RuleSections | |
{ | |
private static string[] DefaultNamespaces = new string[] | |
{ | |
"BizRules.Domain.Objects", | |
"System", | |
"System.Collections.Generic", | |
"System.Text", | |
"NRules.Fluent", | |
"NRules.Fluent.Dsl", | |
"System.Linq" | |
}; | |
public const string WHEN = "When"; | |
public const string MATCH = "Match"; | |
public const string DO = "Do"; | |
public const string THEN = "Then"; | |
public const string EXISTS = "Exists"; | |
public const string DEFINE = "Define"; | |
public static string [] GetNamespaces() | |
{ | |
return DefaultNamespaces; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment