实现一个对象验证库系列 -- 3) Fluent以及扩展方法实现 (请大神批评)
前情回顾:
上一篇 2) 验证器实现 简单描述了下验证器的简单实现
本文将说说Fluent方式的实现,欢迎大神们指点指点
3) Fluent以及扩展方法实现
我们按照之前 Fluent 的设想以及我们解耦的方式,所以我们先实现一个创建验证器创建者的静态类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 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; } } |
貌似我们完成了大部分了,但是好像哪里不对,
回忆一下,好像这个持有如何验证逻辑方法的属性没有相关代码处理
1 2 3 4 | public class ValidateRule : IValidateRule { public Func<ValidateContext, string , string , IValidateResult> ValidateFunc { get ; set ; } } |
好吧,我们来建立一个基类先:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } } |
然后我们接口绑定加上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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 的扩展方法:
1 2 3 4 5 6 7 | 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); } } |
我们再添加一些消息设置相关的扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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; } } |
大功告成,我们现在就可以这样使用了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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: 大神们快快给我些批评吧,冰天雪地跪求了
分类:
c#
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂