Implement an object verification Library series and verification Library Series
Past Situation Review:
Previous Article 2) the validator implementation briefly describes the simple implementation of the validator
This article will talk about the implementation of the Fluent method.
3) Fluent and extension method implementation
We follow the idea of Fluent and the decoupling method, so we first implement a static class for creating the creator of the validators:
Public static class Validation {public static IValidatorBuilder <T> NewValidatorBuilder <T> () // create the validator creator {return Container. resolve <IValidatorBuilder <T> ();} public static ValidateContext CreateContext (object validateObject, ValidateOption option = ValidateOption. stopOnFirstFailure, params string [] ruleSetList) // create the verification data context parameter {var result = Container. resolve <ValidateContext> (); result. option = option; result. ruleSetList = ruleSetList; result. validateObject = validateObject; return result ;}}
We then implement IValidatorBuilder
Public class ValidatorBuilder <T>: IValidatorBuilder <T> {public ObservableCollection <IValidateRuleBuilder> Builders {get; set;} public ValidatorBuilder () {Builders = new ObservableCollection <IValidateRuleBuilder> ();} public IValidator Build () // final build method {var result = Container. resolve <IValidatorSetter> (); result. setRules (Builders. select (I => I. build (); return result;} public IFluentRuleBuilder <T, TPr Operty> RuleFor <TProperty> (Expression <Func <T, TProperty> expression) // verify the rule creator method {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) // rule group flag setting method {P AramHelper. 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! = Policycollectionchangedaction. add) return; foreach (var item in e. newItems) {item. ruleSet = upRuleSet ;}}); Builders. collectionChanged + = updateRuleSet; action (this); Builders. collectionChanged-= updateRuleSet;} // The Rule grouping flag setting method simplifies the setting format and makes the code clearer. // For example, // 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 ");//});}
Then we implement 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> Condi Tion {get; set;} public Func <ValidateContext, string, string, IValidateResult> ValidateFunc {get; set;} public void SetValueGetter (Expression <Func <T, TValue> expression) // set the method for obtaining the value {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 () + ". "," "); // set the default attribute name} else {ValueNa Me = string. empty;} ValueGetter = Expression. lambda <Func <object, TValue> (exp, p ). compile (); // method for generating dynamic values for different objects using expressions} public IFluentRuleBuilder <T, TProperty> ThenRuleFor <TProperty> (Expression <Func <T, TProperty> expression) // create sub-level Rule interface method {var builder = Utils. ruleFor (expression); NextRuleBuilder = builder as IValidateRuleBuilder; return builder;} public IValidateRule Build () // rule creation method {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 ;}}
It seems that we have completed most of the work, but it seems that something is wrong,
Recall that there is no code to process this property holding the logic method for verification.
public class ValidateRule : IValidateRule{public Func<ValidateContext, string, string, IValidateResult> ValidateFunc { get; set; }}
Okay, let's create a base class first:
Public abstract class BaseChecker <T, TProperty> {public virtual IRuleMessageBuilder <T, TProperty> SetValidate (IFluentRuleBuilder <T, TProperty> builder) // sets the logic method of the verification rule {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 () // obtain the verification result instance object {return Container. resolve <IValidateResult> ();} public void AddFailure (IValidateResult result, string name, object value, string error) // Add error message {result. failures. add (new ValidateFailure () {Name = name, Value = value, Error = error});} public abstract IValidateResult Validate (IValidateResult result, TProperty value, string name, string error ); // verify the logic interface of the Rule}
Then we implement an Must check class:
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;}}
Then we add the following to the interface binding:
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>();}}
Then we add the must extension method:
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);}}
Let's add some extension methods related to message settings:
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;}}
We can use the following code now:
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);
The final code and dll can be obtained through the following method:
Nuget:Https://www.nuget.org/packages/ObjectValidator/
Github:Https://github.com/fs7744/ObjectValidator
PS: Let's give me some criticism.