实现一个对象验证库系列 -- 3) Fluent以及扩展方法实现 (请大神批评)

前情回顾:

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

时间: 2024-10-21 15:47:47

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

实现一个对象验证库系列 -- 2) 验证器实现 (请大神批评)

前情回顾: 上一篇 1) 接口介绍以及总体思路概述 简单描述了下库的代码结构 本文将说说验证器实现,欢迎大神指导更快更好的方案 2) 验证器实现 我们首先从用户调用的验证器开始实现,所以我们应该首先这样做 public class Validator : IValidator { public IValidateResult Validate(ValidateContext context) { } } 但是我们的验证器不是没有规则嘛?回忆一下我们好像是在为 Fluent 设计时添加了一个验证器

jQuery.Validate验证库

一.用前必备 官方网站:http://bassistance.de/jquery-plugins/jquery-plugin-validation/ API: http://jquery.bassistance.de/api-browser/plugins.html 当前版本:1.5.5 需要JQuery版本:1.2.6+, 兼容 1.3.2 二.默认校验规则 (1)required:true 必输字段 (2)remote:"check.php" 使用ajax方法调用check.php

7Python标准库系列之requests模块

Python标准库系列之requests模块 Requests is the only Non-GMO HTTP library for Python, safe for human consumption. 官方文档:http://docs.python-requests.org/en/master/ 安装Requests模块 Requests模块官方提供了两种方式安装: pip方式安装 pip install requests 源码方式安装 git clone git://github.co

标准库系列之xml模块

Python标准库系列之xml模块 Python's interfaces for processing XML are grouped in the xml package. 带分隔符的文件仅有两维的数据:行和列.如果你想在程序之间交换数据结构,需要一种方法把层次结构.序列.集合和其他的结构编码成文本. XML是最突出的处理这种转换的标记(markup)格式,它使用标签(tag)分个数据,如下面的实例文件menu.xml所示: <?xml version="1.0" encod

python第三方库系列之十七--multiprocessing库

说到并发,我们想到多线程和多进程. 那到底使用多进程还是多线程呢?这得看情况.我们的程序一般分为: 1)耗网络的(有很大一部分时间是在网络交互): 2)耗CPU的(得充分利用多核) 在第一种情况,时间大部分被网络延时占用,所以使用多线程和多进程都差不多. 在第二种情况,时间的长短由CPU是否被充分利用决定,看<python第三方库系列之十二--多线程threading库>可知,多线程会出现锁机制,所以多进程更胜一筹,充分利用了CPU,节省了时间. 以下是一个多进程例子: #coding=utf

22Python标准库系列之Redis模块

Python标准库系列之Redis模块 What is redis? Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, b

1Python标准库系列之模块介绍

Python标准库系列之模块介绍 Python的模块其实就是封装了一个或者多个功能的代码集合,以便于重用,模块可以是一个文件也可以是一个目录,目录的形式称作包. 模块分类 内置模块 内置模块可以理解成当你安装好python环境之后,直接可以使用import导入的就是内置模块,默认模块路径为:C:\Python35\lib,你也可以通过以下方式获取内置模块的路径:  # 导入sys模块  >>> import sys  # 最后一个目录就是内置模块的路径  >>> for

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

前面两篇文章学习到了,服务端验证,和客户端的验证,但大家有没有发现,这两种验证各自都有弊端,服务器端的验证,验证的逻辑和代码的逻辑混合在一起了,如果代码量很大的话,以后维护扩展起来,就不是很方便.而客户端的验证,必须要启用客户端验证,也就是在配置文件中配置相应的节点,并且还要引入Jquery插件.如果人为的在浏览器上,禁用了js脚本,那么客户端验证就不起作用了,所以在这里,我将继续学习另外一个验证,也就是Fluent Validation. Fluent Validation是一个开源的.NET

python第三方库系列之十四--集群化部署定时任务apscheduler库

如果将定时任务部署在一台服务器上,那么这个定时任务就是整个系统的单点,这台服务器出现故障的话会影响服务.对于可以冗余的任务(重复运行不影响服务),可以部署在多台服务器上,让他们同时执行,这样就可以很简单的避免单点.但是如果任务不允许冗余,最多只能有一台服务器执行任务,那么前面的方法显然行不通.本篇文章就向大家介绍如何避免这种互斥任务的单点问题,最后再介绍一下基于APScheduler的分布式定时任务框架,这个框架是通过多个项目的实践总结而成的. 对于运行在同一台服务器上的两个进程,可以通过加锁实