Tôi đã xây dựng một công cụ quy tắc có cách tiếp cận khác với cách bạn nêu trong câu hỏi của bạn, nhưng tôi nghĩ bạn sẽ thấy nó linh hoạt hơn nhiều so với cách tiếp cận hiện tại của bạn.
Cách tiếp cận hiện tại của bạn dường như tập trung vào một thực thể duy nhất, "Người dùng" và các quy tắc liên tục của bạn xác định "propertyname", "toán tử" và "value". Mẫu của tôi, thay vào đó lưu trữ mã C # cho một vị từ (Func <T, bool>) trong cột "Biểu thức" trong cơ sở dữ liệu của tôi. Trong thiết kế hiện tại, sử dụng việc tạo mã, tôi đang truy vấn "quy tắc" từ cơ sở dữ liệu của mình và biên dịch một tập hợp với các loại "Quy tắc", mỗi loại có một phương pháp "Thử nghiệm". Đây là chữ ký cho giao diện được triển khai mỗi Quy tắc:
public interface IDataRule<TEntity>
{
/// <summary>
/// Evaluates the validity of a rule given an instance of an entity
/// </summary>
/// <param name="entity">Entity to evaluate</param>
/// <returns>result of the evaluation</returns>
bool Test(TEntity entity);
/// <summary>
/// The unique indentifier for a rule.
/// </summary>
int RuleId { get; set; }
/// <summary>
/// Common name of the rule, not unique
/// </summary>
string RuleName { get; set; }
/// <summary>
/// Indicates the message used to notify the user if the rule fails
/// </summary>
string ValidationMessage { get; set; }
/// <summary>
/// indicator of whether the rule is enabled or not
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Represents the order in which a rule should be executed relative to other rules
/// </summary>
int SortOrder { get; set; }
}
"Biểu thức" được biên dịch thành phần thân của phương thức "Thử nghiệm" khi ứng dụng lần đầu tiên thực thi. Như bạn có thể thấy các cột khác trong bảng cũng được hiển thị dưới dạng các thuộc tính hạng nhất theo quy tắc để nhà phát triển có thể linh hoạt tạo ra trải nghiệm về cách người dùng được thông báo về thất bại hoặc thành công.
Tạo một cụm trong bộ nhớ là một lần xuất hiện trong ứng dụng của bạn và bạn có được hiệu suất đạt được bằng cách không phải sử dụng sự phản chiếu khi đánh giá các quy tắc của bạn. Biểu thức của bạn được kiểm tra trong thời gian chạy vì tập hợp sẽ không tạo chính xác nếu tên thuộc tính bị sai chính tả, v.v.
Các cơ chế tạo một bộ nhớ trong như sau:
- Tải quy tắc của bạn từ DB
- Lặp lại các quy tắc và cho mỗi quy tắc, sử dụng StringBuilder và một số nối chuỗi, viết Văn bản đại diện cho một lớp kế thừa từ IDataRule
- biên dịch bằng CodeDOM - thêm thông tin
Điều này thực sự khá đơn giản bởi vì phần lớn mã này là các cài đặt thuộc tính và khởi tạo giá trị trong hàm tạo. Bên cạnh đó, mã duy nhất khác là Biểu thức.
LƯU Ý: có một hạn chế là biểu thức của bạn phải là .NET 2.0 (không có lambdas hoặc các tính năng C # 3.0 khác) do giới hạn trong CodeDOM.
Đây là một số mã mẫu cho điều đó.
sb.AppendLine(string.Format("\tpublic class {0} : SomeCompany.ComponentModel.IDataRule<{1}>", className, typeName));
sb.AppendLine("\t{");
sb.AppendLine("\t\tprivate int _ruleId = -1;");
sb.AppendLine("\t\tprivate string _ruleName = \"\";");
sb.AppendLine("\t\tprivate string _ruleType = \"\";");
sb.AppendLine("\t\tprivate string _validationMessage = \"\";");
/// ...
sb.AppendLine("\t\tprivate bool _isenabled= false;");
// constructor
sb.AppendLine(string.Format("\t\tpublic {0}()", className));
sb.AppendLine("\t\t{");
sb.AppendLine(string.Format("\t\t\tRuleId = {0};", ruleId));
sb.AppendLine(string.Format("\t\t\tRuleName = \"{0}\";", ruleName.TrimEnd()));
sb.AppendLine(string.Format("\t\t\tRuleType = \"{0}\";", ruleType.TrimEnd()));
sb.AppendLine(string.Format("\t\t\tValidationMessage = \"{0}\";", validationMessage.TrimEnd()));
// ...
sb.AppendLine(string.Format("\t\t\tSortOrder = {0};", sortOrder));
sb.AppendLine("\t\t}");
// properties
sb.AppendLine("\t\tpublic int RuleId { get { return _ruleId; } set { _ruleId = value; } }");
sb.AppendLine("\t\tpublic string RuleName { get { return _ruleName; } set { _ruleName = value; } }");
sb.AppendLine("\t\tpublic string RuleType { get { return _ruleType; } set { _ruleType = value; } }");
/// ... more properties -- omitted
sb.AppendLine(string.Format("\t\tpublic bool Test({0} entity) ", typeName));
sb.AppendLine("\t\t{");
// #############################################################
// NOTE: This is where the expression from the DB Column becomes
// the body of the Test Method, such as: return "entity.Prop1 < 5"
// #############################################################
sb.AppendLine(string.Format("\t\t\treturn {0};", expressionText.TrimEnd()));
sb.AppendLine("\t\t}"); // close method
sb.AppendLine("\t}"); // close Class
Ngoài ra, tôi đã tạo một lớp mà tôi gọi là "DataRuleCollection", đã triển khai ICollection>. Điều này cho phép tôi tạo ra khả năng "TestAll" và một bộ chỉ mục để thực hiện quy tắc cụ thể theo tên. Dưới đây là các triển khai cho hai phương pháp đó.
/// <summary>
/// Indexer which enables accessing rules in the collection by name
/// </summary>
/// <param name="ruleName">a rule name</param>
/// <returns>an instance of a data rule or null if the rule was not found.</returns>
public IDataRule<TEntity, bool> this[string ruleName]
{
get { return Contains(ruleName) ? list[ruleName] : null; }
}
// in this case the implementation of the Rules Collection is:
// DataRulesCollection<IDataRule<User>> and that generic flows through to the rule.
// there are also some supporting concepts here not otherwise outlined, such as a "FailedRules" IList
public bool TestAllRules(User target)
{
rules.FailedRules.Clear();
var result = true;
foreach (var rule in rules.Where(x => x.IsEnabled))
{
result = rule.Test(target);
if (!result)
{
rules.FailedRules.Add(rule);
}
}
return (rules.FailedRules.Count == 0);
}
MÃ SỐ HƠN: Có một yêu cầu về mã liên quan đến việc tạo mã. Tôi đã gói gọn chức năng trong một lớp có tên 'RulesAssuggingGenerator' mà tôi đã đưa vào dưới đây.
namespace Xxx.Services.Utils
{
public static class RulesAssemblyGenerator
{
static List<string> EntityTypesLoaded = new List<string>();
public static void Execute(string typeName, string scriptCode)
{
if (EntityTypesLoaded.Contains(typeName)) { return; }
// only allow the assembly to load once per entityType per execution session
Compile(new CSharpCodeProvider(), scriptCode);
EntityTypesLoaded.Add(typeName);
}
private static void Compile(CodeDom.CodeDomProvider provider, string source)
{
var param = new CodeDom.CompilerParameters()
{
GenerateExecutable = false,
IncludeDebugInformation = false,
GenerateInMemory = true
};
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
var root_Dir = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Bin");
param.ReferencedAssemblies.Add(path);
// Note: This dependencies list are included as assembly reference and they should list out all dependencies
// That you may reference in your Rules or that your entity depends on.
// some assembly names were changed... clearly.
var dependencies = new string[] { "yyyyyy.dll", "xxxxxx.dll", "NHibernate.dll", "ABC.Helper.Rules.dll" };
foreach (var dependency in dependencies)
{
var assemblypath = System.IO.Path.Combine(root_Dir, dependency);
param.ReferencedAssemblies.Add(assemblypath);
}
// reference .NET basics for C# 2.0 and C#3.0
param.ReferencedAssemblies.Add(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll");
param.ReferencedAssemblies.Add(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll");
var compileResults = provider.CompileAssemblyFromSource(param, source);
var output = compileResults.Output;
if (compileResults.Errors.Count != 0)
{
CodeDom.CompilerErrorCollection es = compileResults.Errors;
var edList = new List<DataRuleLoadExceptionDetails>();
foreach (CodeDom.CompilerError s in es)
edList.Add(new DataRuleLoadExceptionDetails() { Message = s.ErrorText, LineNumber = s.Line });
var rde = new RuleDefinitionException(source, edList.ToArray());
throw rde;
}
}
}
}
Nếu có bất kỳ khác thắc mắc hoặc ý kiến hoặc yêu cầu về mẫu mã hơn nữa, cho tôi biết.