Skip to content

Instantly share code, notes, and snippets.

@punitganshani
Last active May 29, 2018 01:09
Show Gist options
  • Save punitganshani/b642f883fad0cc0d304ed38f7bebd42e to your computer and use it in GitHub Desktop.
Save punitganshani/b642f883fad0cc0d304ed38f7bebd42e to your computer and use it in GitHub Desktop.
Roslyn way to generate NRules
{
"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"
]
}
]
}
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");
}
}
}
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();
}
}
}
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";
}
}
}
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