上一篇 2) 驗證器實現 簡單描述了下驗證器的簡單實現
本文將說說Fluent方式的實現,歡迎大神們指點指點
我們按照之前 Fluent 的設想以及我們解耦的方式,所以我們先實現一個創建驗證器創建者的靜態類:
public static class Validation { public static IValidatorBuilder<T> NewValidatorBuilder<T>() // 創建驗證器創建者 { return Container.Resolve<IValidatorBuilder<T>>(); } public static ValidateContext CreateContext(object validateObject, ValidateOption option = ValidateOption.StopOnFirstFailure, params string[] ruleSetList) // 創建驗證數據上下文參數 { var result = Container.Resolve<ValidateContext>(); result.Option = option; result.RuleSetList = ruleSetList; result.ValidateObject = validateObject; return result; } }
我們接著實現 IValidatorBuilder
public class ValidatorBuilder<T> : IValidatorBuilder<T> { public ObservableCollection<IValidateRuleBuilder> Builders { get; set; } public ValidatorBuilder() { Builders = new ObservableCollection<IValidateRuleBuilder>(); } public IValidator Build() // 最終build 方法 { var result = Container.Resolve<IValidatorSetter>(); result.SetRules(Builders.Select(i => i.Build())); return result; } public IFluentRuleBuilder<T, TProperty> RuleFor<TProperty>(Expression<Func<T, TProperty>> expression) // 驗證規則創建者方法 { ParamHelper.CheckParamNull(expression, "expression", "Can't be null"); var builder = Container.Resolve<IRuleBuilder<T, TProperty>>(); builder.SetValueGetter(expression); Builders.Add(builder as IValidateRuleBuilder); return builder; } public void RuleSet(string ruleSet, Action<IValidatorBuilder<T>> action) // 規則分組標志設置方法 { ParamHelper.CheckParamEmptyOrNull(ruleSet, "ruleSet", "Can't be null"); ParamHelper.CheckParamNull(action, "action", "Can't be null"); var upRuleSet = ruleSet.ToUpper(); var updateRuleSet = new NotifyCollectionChangedEventHandler<IValidateRuleBuilder>((o, e) => { if (e.Action != NotifyCollectionChangedAction.Add) return; foreach (var item in e.NewItems) { item.RuleSet = upRuleSet; } }); Builders.CollectionChanged += updateRuleSet; action(this); Builders.CollectionChanged -= updateRuleSet; } // 規則分組標志設置方法這樣實現可以簡化設置的格式,讓代碼更清晰 // 比如 // var builder = Validation.NewValidatorBuilder<Student>(); // builder.RuleSet("A", b => // { // b.RuleFor(i => i.Name).NotNull() // .Must(i=>i.Length > 10) // .OverrideName("student name") // .OverrideError("no name") // .ThenRuleFor(i => i.Age) // .Must(i => i >= 0 && i <= 18) // .OverrideName("student age") // .OverrideError("not student"); // }); }
接著我們實現 IRuleBuilder:
public class RuleBuilder<T, TValue> : IRuleBuilder<T, TValue> { public string RuleSet { get; set; } public Func<object, TValue> ValueGetter { get; protected set; } public Expression<Func<T, TValue>> ValueExpression { get; protected set; } public string ValueName { get; set; } public string Error { get; set; } public IValidateRuleBuilder NextRuleBuilder { get; set; } public Func<ValidateContext, bool> Condition { get; set; } public Func<ValidateContext, string, string, IValidateResult> ValidateFunc { get; set; } public void SetValueGetter(Expression<Func<T, TValue>> expression) // 設置獲取值的方法 { ValueExpression = expression; var stack = new Stack<MemberInfo>(); var memberExp = expression.Body as MemberExpression; while (memberExp != null) { stack.Push(memberExp.Member); memberExp = memberExp.Expression as MemberExpression; } var p = Expression.Parameter(typeof(object), "p"); var convert = Expression.Convert(p, typeof(T)); Expression exp = convert; if (stack.Count > 0) { while (stack.Count > 0) { exp = Expression.MakeMemberAccess(exp, stack.Pop()); } ValueName = exp.ToString().Replace(convert.ToString() + ".", ""); // 設置默認的屬性名 } else { ValueName = string.Empty; } ValueGetter = Expression.Lambda<Func<object, TValue>>(exp, p).Compile(); // 用表達式生成動態獲取不同對象的值的方法 } public IFluentRuleBuilder<T, TProperty> ThenRuleFor<TProperty>(Expression<Func<T, TProperty>> expression) // 創建子級規則接口方法 { var builder = Utils.RuleFor(expression); NextRuleBuilder = builder as IValidateRuleBuilder; return builder; } public IValidateRule Build() // 規則創建方法 { var rule = Container.Resolve<IValidateRule>(); rule.ValueName = ValueName; rule.Error = Error; rule.ValidateFunc = ValidateFunc; rule.Condition = Condition; rule.RuleSet = RuleSet; var nextBuilder = NextRuleBuilder; if (nextBuilder != null) rule.NextRule = nextBuilder.Build(); return rule; } }
貌似我們完成了大部分了,但是好像哪裡不對,
回憶一下,好像這個持有如何驗證邏輯方法的屬性沒有相關代碼處理
public class ValidateRule : IValidateRule { public Func<ValidateContext, string, string, IValidateResult> ValidateFunc { get; set; } }
好吧,我們來建立一個基類先:
public abstract class BaseChecker<T, TProperty> { public virtual IRuleMessageBuilder<T, TProperty> SetValidate(IFluentRuleBuilder<T, TProperty> builder) // 設置驗證規則邏輯方法 { ParamHelper.CheckParamNull(builder, "builder", "Can't be null"); var build = builder as IRuleBuilder<T, TProperty>; build.ValidateFunc = (context, name, error) => { var value = build.ValueGetter(context.ValidateObject); var result = Container.Resolve<IValidateResult>(); return Validate(result, value, name, error); }; return build as IRuleMessageBuilder<T, TProperty>; } public IValidateResult GetResult() // 獲取驗證結果實例對象 { return Container.Resolve<IValidateResult>(); } public void AddFailure(IValidateResult result, string name, object value, string error) // 添加錯誤信息 { result.Failures.Add(new ValidateFailure() { Name = name, Value = value, Error = error }); } public abstract IValidateResult Validate(IValidateResult result, TProperty value, string name, string error); // 驗證規則邏輯接口 }
再接著我們實現一個Must check 類:
public class MustChecker<T, TProperty> : BaseChecker<T, TProperty> { private Func<TProperty, bool> m_MustBeTrue; public MustChecker(Func<TProperty, bool> func) { ParamHelper.CheckParamNull(func, "func", "Can't be null"); m_MustBeTrue = func; } public override IValidateResult Validate(IValidateResult result, TProperty value, string name, string error) { if (!m_MustBeTrue(value)) { AddFailure(result, name, value, error); } return result; } }
然後我們接口綁定加上:
public static class Container { public static ILifetimeScope CurrentScope { get; set; } public static void Init(Action<ContainerBuilder> action) { ParamHelper.CheckParamNull(action, "action", "Can't be null"); Clear(); var builder = new ContainerBuilder(); action(builder); var container = builder.Build(); CurrentScope = container.BeginLifetimeScope(); } public static void Init() { Init(builder => { builder.RegisterType<RuleSelector>().As<IRuleSelector>().SingleInstance(); builder.RegisterGeneric(typeof(RuleBuilder<,>)).As(typeof(IRuleBuilder<,>)).InstancePerDependency(); builder.Register(c => new ValidateContext() { RuleSelector = c.Resolve<IRuleSelector>() }); builder.RegisterType<ValidateRule>().As<IValidateRule>().InstancePerDependency(); builder.RegisterType<ValidateResult>().As<IValidateResult>().InstancePerDependency(); builder.RegisterGeneric(typeof(ValidatorBuilder<>)).As(typeof(IValidatorBuilder<>)).InstancePerDependency(); builder.RegisterType<Validator>().As<IValidatorSetter>().InstancePerDependency(); }); } public static void Clear() { var scope = CurrentScope; if (scope != null) scope.Dispose(); } public static T Resolve<T>() { return CurrentScope.Resolve<T>(); } }
再然後我們添加 must 的擴展方法:
public static class Syntax { public static IRuleMessageBuilder<T, TProperty> Must<T, TProperty>(this IFluentRuleBuilder<T, TProperty> builder, Func<TProperty, bool> func) { return new MustChecker<T, TProperty>(func).SetValidate(builder); } }
我們再添加一些消息設置相關的擴展方法:
public static class Syntax { .... public static IRuleMessageBuilder<T, TProperty> When<T, TProperty>(this IRuleMessageBuilder<T, TProperty> builder, Func<TProperty, bool> func) { ParamHelper.CheckParamNull(func, "func", "Can't be null"); var ruleBuilder = builder as IRuleBuilder<T, TProperty>; ruleBuilder.Condition = (context) => { var value = ruleBuilder.ValueGetter(context.ValidateObject); return func(value); }; return builder; } public static IRuleMessageBuilder<T, TProperty> OverrideName<T, TProperty>(this IRuleMessageBuilder<T, TProperty> builder, string name) { (builder as IValidateRuleBuilder).ValueName = name; return builder; } public static IRuleMessageBuilder<T, TProperty> OverrideError<T, TProperty>(this IRuleMessageBuilder<T, TProperty> builder, string error) { (builder as IValidateRuleBuilder).Error = error; return builder; } }
大功告成,我們現在就可以這樣使用了:
Container.Init(); var builder = Validation.NewValidatorBuilder<Student>(); builder.RuleSet("A", b => { b.RuleFor(i => i.Name).Must(i=>i.Length > 10) .OverrideName("student name") .OverrideError("no name") .ThenRuleFor(i => i.Age) .Must(i => i >= 0 && i <= 18) .OverrideName("student age") .OverrideError("not student"); }); var v = builder.Build(); var student = new BigStudent() { Age = 13, Name = "v" }; var context = Validation.CreateContext(student); var result = v.Validate(context); Assert.IsNotNull(result); Assert.True(result.IsValid); Assert.True(result.Failures.Count == 0);
最後代碼和dll可以通過如下方法獲取:
nuget:https://www.nuget.org/packages/ObjectValidator/
github:https://github.com/fs7744/ObjectValidator
PS: 大神們快快給我些批評吧,冰天雪地跪求了