MVC应用规则授权:条件字符串转LINQ表达式方案咨询
Great question—storing dynamic authorization conditions as strings and evaluating them at runtime with custom object methods is a common challenge in rule-based systems. Here are a few practical approaches tailored to your MVC scenario:
Approach 1: Expression Trees with Custom String Parsing
This approach leverages .NET's expression trees to convert your condition strings into executable code, which works well if you need full flexibility with custom methods like Product.GetAddedBy().
Step 1: Define a Consistent Condition Syntax
First, standardize your condition strings so they're easy to parse. For example, a condition might look like:GetAddedBy() == '123' or Price > 100 && Category == 'Electronics'
Step 2: Build a Parser to Convert Strings to Expression Trees
You'll need a simple parser that takes the condition string and builds an Expression<Func<T, bool>> where T is your target type (like Product). To handle custom methods, map method names to their actual MethodInfo objects.
Here's a simplified example for your Product case:
public static Expression<Func<Product, bool>> ParseProductCondition(string condition) { // Simplified parsing logic for "GetAddedBy() == '123'" var parameter = Expression.Parameter(typeof(Product), "p"); var methodCall = Expression.Call(parameter, typeof(Product).GetMethod("GetAddedBy")); var constant = Expression.Constant("123"); var equality = Expression.Equal(methodCall, constant); return Expression.Lambda<Func<Product, bool>>(equality, parameter); }
Step 3: Evaluate in Your Authorize Method
In RulesAuthorizer.Authorise, load the user's rules, parse the condition string into an expression, compile it, and run it against the target object:
public static bool Authorise(string actionKey, string userId, object target) { var rules = GetRulesFromDb(userId, actionKey); foreach(var rule in rules) { if(rule.Type == RuleType.Deny && EvaluateCondition(rule.Condition, target)) { return false; // Deny takes precedence } if(rule.Type == RuleType.Allow && EvaluateCondition(rule.Condition, target)) { return true; // Allow rule matches } } return false; // Default to deny } private static bool EvaluateCondition(string condition, object target) { var targetType = target.GetType(); if(targetType == typeof(Product)) { var expression = ParseProductCondition(condition); var func = expression.Compile(); return func((Product)target); } throw new NotSupportedException($"Target type {targetType} not supported"); }
For complex conditions (logical operators, multiple methods), use a robust parser library like ANTLR or a dynamic LINQ implementation that supports custom method calls.
Approach 2: Use a Rule Engine Library
If building a custom parser feels too heavy, use a mature rule engine library that handles dynamic rule definition and custom methods. Libraries like NRules or EasyRules let you define rules in code or external files, abstracting the evaluation logic.
Example with NRules for your ViewProduct action:
public class ViewProductOwnerRule : Rule { public override void Define() { Product product = null; string userId = null; When() .Context.TryGet("UserId", out userId) .Match<Product>(p => p.GetAddedBy() == userId); Then() .Do(ctx => ctx.AddResult("Authorized", true)); } }
You can load rules dynamically (from XML or database) and execute them against your target objects, saving you from writing parsing logic.
Approach 3: Predefined Delegate Mapping
If your condition set is limited, map condition keys to predefined delegates for simplicity and performance.
Step 1: Store Condition Keys Instead of Full Strings
Instead of storing GetAddedBy() == '123', store a key like IsProductOwner along with parameters (e.g., user ID).
Step 2: Create a Delegate Dictionary
Define a dictionary mapping condition keys to functions that take the target object and parameters:
private static readonly Dictionary<string, Func<object, Dictionary<string, object>, bool>> _conditionDelegates = new() { { "IsProductOwner", (target, parameters) => { var product = (Product)target; var userId = parameters["UserId"].ToString(); return product.GetAddedBy() == userId; } }, // Add other conditions here };
Step 3: Evaluate Using the Delegate
In your Authorise method, look up the delegate by key and execute it:
private static bool EvaluateCondition(string conditionKey, object target, Dictionary<string, object> parameters) { if(_conditionDelegates.TryGetValue(conditionKey, out var func)) { return func(target, parameters); } throw new KeyNotFoundException($"Condition {conditionKey} not found"); }
Which Approach Should You Choose?
- Expression Trees: Best for full flexibility with arbitrary conditions and custom methods.
- Rule Engine: Ideal for complex rules or separating rule logic from application code.
- Predefined Delegates: Perfect for limited condition sets, prioritizing performance and simplicity.
Content of the question originates from Stack Exchange, asked by Dimitar Nikovski

