前情回顾:

上一篇 2) 验证器实现 简单描述了下验证器的简单实现

本文将说说Fluent方式的实现,欢迎大神们指点指点

3) 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: 大神们快快给我些批评吧,冰天雪地跪求了

实现一个对象验证库系列 -- 3) Fluent以及扩展方法实现 (请大神批评)的更多相关文章

  1. C# 9.0新特性详解系列之二:扩展方法GetEnumerator支持foreach循环

    1.介绍 我们知道,我们要使一个类型支持foreach循环,就需要这个类型满足下面条件之一: 该类型实例如果实现了下列接口中的其中之一: System.Collections.IEnumerable ...

  2. MVC学习系列12---验证系列之Fluent Validation

    前面两篇文章学习到了,服务端验证,和客户端的验证,但大家有没有发现,这两种验证各自都有弊端,服务器端的验证,验证的逻辑和代码的逻辑混合在一起了,如果代码量很大的话,以后维护扩展起来,就不是很方便.而客 ...

  3. jQuery.Validate验证库详解

    一.用前必备官方网站:http://bassistance.de/jquery-plugins/jquery-plugin-validation/ API: http://jquery.bassist ...

  4. jQuery.Validate验证库

    一.用前必备官方网站:http://bassistance.de/jquery-plugins/jquery-plugin-validation/ API: http://jquery.bassist ...

  5. 前端工具 - 15个最佳的 JavaScript 表单验证库

    客户端验证在任何项目都是不够的,因为 JavaScript 可以直接忽略,人们可以提交请求到服务器. 然而这并不意味着客户端验证都没必要了,很多时候我们需要在用户提交到服务器之前给予提示.JavaSc ...

  6. 15个最佳的 JavaScript 表单验证库

    客户端验证在任何项目都是不够的,因为 JavaScript 可以直接忽略,人们可以提交请求到服务器. 然而这并不意味着客户端验证都没必要了,很多时候我们需要在用户提交到服务器之前给予提示.JavaSc ...

  7. jQuery前端数据通用验证库,解放你的双手

    这个简易的验证库,应该能完成90%的基本验证,包括失去焦点时的验证,以及点击提交按钮时的验证.后端的那我就无能为办了,只能是谁用就谁自个儿去写了:). 先上一段调用的代码吧,JS代码说少也不少了,就不 ...

  8. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

  9. golang常用库:字段参数验证库-validator

    背景 在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作.比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全 ...

随机推荐

  1. 《JavaScript 闯关记》之语句

    表达式在 JavaScript 中是短语,那么语句就是整句命令.表达式用来计算出一个值,语句用来执行以使某件事发生.从本质上看,语句定义了 JavaScript 中的主要语法,语句通常使用一或多个关键 ...

  2. CAS SSO

    1.      CAS 简介 1.1.  What is CAS ? CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨 ...

  3. oracle使用exp与imp在本地导入导出数据

    导出: exp user/password owner=user file=你要输出的目录以及文件名,后缀为dmpexp IOTMON/iotmon owner=QSMES file=/home/or ...

  4. Spring-----6、Spring3.0提供的Java配置管理

    转载自:http://blog.csdn.net/hekewangzi/article/details/45646279

  5. Apache的httpd命令详解

    Apache的httpd命令详解 来源:全栈开发者 发布时间:2012-01-03 阅读次数:10965 4   httpd.exe为Apache HTTP服务器程序.直接执行程序可启动服务器的服务. ...

  6. Android icons集合

    Android icons集合: Be aware that the style changes occur fairly regularly with each major release, so ...

  7. 关于微信小程序的一些思考

    ### 怎么样理解小程序? * 微信的重点产品* 一个事实OS,目前并不知道小程序的入口在哪里?* 小程序的入口可能在如下三个地方: 1. 发现入口 2. 扫码 3. 搜索框 * 小程序没有关注, 意 ...

  8. MSSQL 导入导出文本文件

    exec master..xp_cmdshell'net use \\192.168.1.8\testfolder "0123" /user:192.168.1.8\wjp' EX ...

  9. 自动化高效css开发,畅谈less的灵活变化

    css是一种让html与样式分离解析而出现的代码,它的出现大大提高了程序员的工作效率,和后期进行维护的效率.但是发展至今,由于起死板单调的写法,越来越不能满足程序员们灵活的思维,很多时候是种恨铁不成钢 ...

  10. ActionResult 之HttpGet HttpPost

    GET一般用于获取和查询数据. 当浏览器发送HTTP GET 请求的时候,会找到使用HttpGet限定的对应Action. POST 一般用于更新数据. 当Action上面没有限定的时候,浏览器发送任 ...