diff --git a/.idea/.idea.Ruler/.idea/.gitignore b/.idea/.idea.Ruler/.idea/.gitignore
new file mode 100644
index 0000000..de3eb63
--- /dev/null
+++ b/.idea/.idea.Ruler/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/.idea.Ruler.iml
+/contentModel.xml
+/projectSettingsUpdater.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.Ruler/.idea/indexLayout.xml b/.idea/.idea.Ruler/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.Ruler/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Ruler/.idea/vcs.xml b/.idea/.idea.Ruler/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.Ruler/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ruler.sln b/Ruler.sln
new file mode 100644
index 0000000..b2d4c04
--- /dev/null
+++ b/Ruler.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ruler", "Ruler\Ruler.csproj", "{E4B8E05A-80A5-4F0D-A3DA-BB6AA377153D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RulerDev", "RulerDev\RulerDev.csproj", "{0E6DF52C-D171-4E84-8D6D-BB580DF35E51}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E4B8E05A-80A5-4F0D-A3DA-BB6AA377153D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4B8E05A-80A5-4F0D-A3DA-BB6AA377153D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4B8E05A-80A5-4F0D-A3DA-BB6AA377153D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4B8E05A-80A5-4F0D-A3DA-BB6AA377153D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E6DF52C-D171-4E84-8D6D-BB580DF35E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E6DF52C-D171-4E84-8D6D-BB580DF35E51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E6DF52C-D171-4E84-8D6D-BB580DF35E51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E6DF52C-D171-4E84-8D6D-BB580DF35E51}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Ruler/Ruler.cs b/Ruler/Ruler.cs
index e0e6a6a..06af9ef 100644
--- a/Ruler/Ruler.cs
+++ b/Ruler/Ruler.cs
@@ -59,71 +59,114 @@ public static class PredicateBuilder
public static class FilterPolicyExtensions
-{
- public enum RuleOperator {
- And,
- Or
- }
+{
+ public enum RuleOperator {
+ And,
+ Or
+ }
- // Dynamicall build an expression suitable for filtering in a Where clause
- public static Expression> GetFilterForType(string property, string value)
- {
- var parameter = Expression.Parameter(typeof(T), "x");
- var opLeft = Expression.Property(parameter, property);
+ public static Expression> AddFilterToStringProperty(
+ Expression> expression, string filter, string filterType)
+ {
- var opRight = Expression.Constant(value);
- var comparison = Expression.Equal(opLeft, opRight);
+#if DEBUG
+ if (!(filterType == "StartsWith" || filterType == "EndsWith" || filterType == "Contains"))
+ {
+ throw new Exception($"filterType must equal StartsWith, EndsWith or Contains. Passed {filterType}");
+ }
- return Expression.Lambda>(comparison, parameter);
- }
+#endif
+ // Check that the property isn't null, otherwise we'd hit null object exceptions at runtime
+ var notNull = Expression.NotEqual(expression.Body,Expression.Constant(null));
+
+ // Setup calls to EtartsWith, EndsWith, or Contains
+ // TODO expressionArgs was used to pass multiple values for case insensitive compare, wasn't
+ // mapping the method correctly when used with EF so need to revisit that
+ var expressionArgs = new Expression[] {Expression.Constant(filter)};
+ var strPredicate = Expression.Call(expression.Body, filterType, null, expressionArgs);
- // Combine a list of expressions inclusively
- public static Expression> CombineAnd(IEnumerable>> predicates)
- {
- if (predicates.Count() == 0) return null;
+ var filterExpression = Expression.AndAlso(notNull, strPredicate);
- var final = predicates.First();
- foreach (var next in predicates.Skip(1))
- final = PredicateBuilder.And(final, next);
-
- return final;
- }
+ return Expression.Lambda>(
+ filterExpression,
+ expression.Parameters);
+ }
+
+ // Dynamically build an expression suitable for filtering in a Where clause
+ public static Expression> GetFilterForType(string property, string value)
+ {
+ var parameter = Expression.Parameter(typeof(T), "x");
+ var opLeft = Expression.Property(parameter, property);
+ var opRight = Expression.Constant(value);
+ var comparison = Expression.Equal(opLeft, opRight);
+
+ // For string comparisons using wildcards, trim the wildcard characters and pass to the comparison method
+ if (opLeft.Type == typeof(string)) {
+ // Grab the object property for use in the inner expression body
+ var strParam = Expression.Lambda>(opLeft, parameter);
+
+ if (value.StartsWith("*") && value.EndsWith("*")) {
+ return AddFilterToStringProperty(strParam, value.Trim('*'), "Contains");
+ } else if (value.StartsWith("*")) {
+ return AddFilterToStringProperty(strParam, value.TrimStart('*'), "EndsWith");
+ } else if (value.EndsWith("*")) {
+ return AddFilterToStringProperty(strParam, value.TrimEnd('*'), "StartsWith");
+ } else {
+ return Expression.Lambda>(comparison, parameter);
+ }
+ }
+
+ return Expression.Lambda>(comparison, parameter);
+ }
+
+ // Combine a list of expressions inclusively
+ public static Expression>? CombineAnd(IEnumerable>> predicates)
+ {
+ if (predicates.Count() == 0) return null;
+
+ var final = predicates.First();
+ foreach (var next in predicates.Skip(1))
+ final = PredicateBuilder.And(final, next);
+
+ return final;
+ }
- // Combine a list of expressions inclusively
- public static Expression> CombineOr(IEnumerable>> predicates)
- {
- if (predicates.Count() == 0) return null;
+ // Combine a list of expressions inclusively
+ public static Expression>? CombineOr(IEnumerable>> predicates)
+ {
+ if (predicates.Count() == 0) return null;
- var final = predicates.First();
- foreach (var next in predicates.Skip(1))
- final = PredicateBuilder.Or(final, next);
+ var final = predicates.First();
+ foreach (var next in predicates.Skip(1))
+ final = PredicateBuilder.Or(final, next);
- return final;
- }
+ return final;
+ }
- // Combine a list of expressions inclusively
- public static Expression> CombinePredicates(IEnumerable>> predicates, FilterPolicyExtensions.RuleOperator op)
- {
- if (predicates.Count() == 0) return null;
+ // Combine a list of expressions inclusively
+ public static Expression>? CombinePredicates(IEnumerable>> predicates,
+ FilterPolicyExtensions.RuleOperator op)
+ {
+ if (predicates.Count() == 0) return null;
- if (op == RuleOperator.And)
- {
- return CombineAnd(predicates);
- }
- return CombineOr(predicates);
- }
+ if (op == RuleOperator.And)
+ {
+ return CombineAnd(predicates);
+ }
+ return CombineOr(predicates);
+ }
- public static Expression> GetFilterExpression(this FilterPolicy policy)
- {
- var predicates = new List>>();
- foreach (var constraints in policy.scope)
- {
- predicates.Add(GetFilterForType(constraints.Item1, constraints.Item2));
- }
-
- return CombinePredicates(predicates, policy.ruleOperator);
- }
+ public static Expression>? GetFilterExpression(this FilterPolicy policy)
+ {
+ var predicates = new List>>();
+ foreach (var constraints in policy.scope)
+ {
+ predicates.Add(GetFilterForType(constraints.Item1, constraints.Item2));
+ }
+
+ return CombinePredicates(predicates, policy.ruleOperator);
+ }
}
\ No newline at end of file
diff --git a/Ruler/Ruler.csproj b/Ruler/Ruler.csproj
index 4658cbf..bafd05b 100644
--- a/Ruler/Ruler.csproj
+++ b/Ruler/Ruler.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net6.0
enable
enable
diff --git a/RulerDev/RulerDev.csproj b/RulerDev/RulerDev.csproj
index 3a1aeab..43f7d06 100644
--- a/RulerDev/RulerDev.csproj
+++ b/RulerDev/RulerDev.csproj
@@ -1,16 +1,16 @@
-
-
+
+
-
-
+
+
Exe
- net7.0
+ net6.0
enable
enable
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..87aef9f
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "6.0.0",
+ "rollForward": "latestMajor",
+ "allowPrerelease": false
+ }
+}
\ No newline at end of file