






















如何把字段校验从业务代码里抽离出来,做成一条可复用、可扩展、可缓存的“校验切面”。
适用场景:
核心链路可以概括为:
ValidationAttributeRuleBase.Apply 里统一触发字段校验ValidationContainer 负责获取/缓存校验委托ValidationBuilder 扫描属性特性并构建表达式树Validation 委托后执行,返回 Result这本质上就是 AOP 思想:
把“字段校验”作为横切关注点,从业务规则中切出来,通过统一入口织入。
classDiagram class Program { +Main() } class IUserProfile { <<interface>> +string Name +string EnglishName +string Position } class DemoProfileModel { +string Name +string EnglishName +string Position } class RuleBase~TModel~ { +bool IgnoreFieldCheck +Result Apply(TModel model) #Result OnApply(TModel model) } class ProfileUpdateRule { +Result OnApply(IUserProfile model) } class ValidationContainer { +Validation Get(Type type) } class ValidationBuilder { +Validation Build(Type type) } class StringLengthExAttribute { +string Name +bool Trim +bool IsValid(object value) +string FormatErrorMessage(string name) } class Result { +bool Ok +Exception Error +string Message +Success() +Failure(Exception error) } Program --> ProfileUpdateRule : 调用 ProfileUpdateRule --|> RuleBase~IUserProfile~ : 继承 DemoProfileModel ..|> IUserProfile : 实现 RuleBase~TModel~ --> ValidationContainer : 获取校验委托 ValidationContainer --> ValidationBuilder : Build(type) ValidationBuilder --> StringLengthExAttribute : 调用 IsValid RuleBase~TModel~ --> Result : 返回
OnApplyConcurrentDictionary<Type, Validation> 缓存委托GetOrAdd 简化并发逻辑,避免重复构建ValidationAttributeStringLengthAttribute 基础上增强Trimpublic interface IUserProfile
{
[StringLengthEx("姓名", 10)]
string Name { get; set; }
[StringLengthEx("英文名", 8)]
string EnglishName { get; set; }
}
public Result Apply(TModel model)
{
if (!IgnoreFieldCheck)
{
if (model == null)
return Result.Failure(new ArgumentNullException(nameof(model)));
Validation func = ValidationContainer.Instance.Get(typeof(TModel));
Result checkResult = func(model);
if (!checkResult.Ok)
return checkResult;
}
return OnApply(model);
}
private readonly ConcurrentDictionary<Type, Validation> _container =
new ConcurrentDictionary<Type, Validation>();
public Validation Get(Type type)
{
return _container.GetOrAdd(type, x => ValidationBuilder.Instance.Build(x));
}
public Validation Build(Type type)
{
if (!type.IsCustomClassType())
return _ => Result.Success();
ParameterExpression modelExpr = Expression.Parameter(typeof(object), "model");
ParameterExpression resultExpr = Expression.Variable(typeof(Result), "result");
LabelTarget returnLabel = Expression.Label(typeof(Result));
BlockExpression body = Expression.Block(
new[] { resultExpr },
BuildModelValidateExpr(type, modelExpr, returnLabel),
Expression.Return(returnLabel, Expression.Call(typeof(Result), "Success", Type.EmptyTypes)),
Expression.Label(returnLabel, resultExpr));
return Expression.Lambda<Validation>(body, modelExpr).Compile();
}
表达式树构建后的代码
.Lambda #Lambda1<ValidationDemo.Validation>(System.Object $model) {
.Block(ValidationDemo.Result $result) {
.Block() {
.Block() {
.Block() {
.If (
.IsFalse(.Call .Constant<ValidationDemo.StringLengthExAttribute>(ValidationDemo.StringLengthExAttribute).IsValid((System.Object)((ValidationDemo.IUserProfile)$model).Name)
)
) {
.Return #Label1 { .Call ValidationDemo.Result.Failure(.New ValidationDemo.FieldException(
"Name",
"姓名长度不能超过10")) }
} .Else {
.Default(System.Void)
}
};
.Block() {
.If (
.IsFalse(.Call .Constant<ValidationDemo.StringLengthExAttribute>(ValidationDemo.StringLengthExAttribute).IsValid((System.Object)((ValidationDemo.IUserProfile)$model).EnglishName)
)
) {
.Return #Label1 { .Call ValidationDemo.Result.Failure(.New ValidationDemo.FieldException(
"EnglishName",
"英文名长度不能超过8")) }
} .Else {
.Default(System.Void)
}
};
.Block() {
.If (
.IsFalse(.Call .Constant<ValidationDemo.StringLengthExAttribute>(ValidationDemo.StringLengthExAttribute).IsValid((System.Object)((ValidationDemo.IUserProfile)$model).Position)
)
) {
.Return #Label1 { .Call ValidationDemo.Result.Failure(.New ValidationDemo.FieldException(
"Position",
"职位长度不能超过20")) }
} .Else {
.Default(System.Void)
}
}
}
};
.Return #Label1 { .Call ValidationDemo.Result.Success() };
.Label
$result
.LabelTarget #Label1:
}
}
Result result;
if (!StringLengthExAttribute.IsValid(((IUserProfile)model).Name))
{
return Result.Failure(new FieldException("Name", "姓名长度不能超过10"));
}
if (!StringLengthExAttribute.IsValid(((IUserProfile)model).EnglishName))
{
return Result.Failure(new FieldException("EnglishName", "英文名长度不能超过8"));
}
if (!StringLengthExAttribute.IsValid(((IUserProfile)model).Position))
{
return Result.Failure(new FieldException("Position", "职位长度不能超过20"));
}
return Result.Success();
public override bool IsValid(object value)
{
if (Trim && value is string stringValue)
value = stringValue.Trim();
return base.IsValid(value);
}
private static readonly Regex TokenRegex =
new Regex(@"\{([A-Za-z_][A-Za-z0-9_]*)\}", RegexOptions.Compiled);
string message = TokenRegex.Replace(attr.ErrorMessage, match =>
{
string token = match.Groups[1].Value;
if (dict.TryGetValue(token, out object tokenValue))
return Convert.ToString(tokenValue);
return match.Value;
});
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
namespace ValidationDemo
{
/// <summary>
/// 入口
/// </summary>
/// <remarks>
/// 演示规则触发、Attribute 校验和统一结果返回这条链路
/// </remarks>
public static class Program
{
public static void Main()
{
ProfileUpdateRule rule = new ProfileUpdateRule(); // 规则对象:负责触发字段校验
DemoProfileModel successModel = new DemoProfileModel
{
Name = " 张三 ", // 前后空格会在特性中 Trim 后校验
EnglishName = "Tom", // 合法长度
Position = "Engineer" // 合法长度
};
Result success = rule.Apply(successModel); // 走 RuleBase.Apply -> ValidationContainer -> ValidationBuilder
Console.WriteLine($"Success Case => Ok:{success.Ok}, Message:{success.Message}");
DemoProfileModel failedModel = new DemoProfileModel
{
Name = " 这是一个超长姓名这是一个超长姓名 ", // 超出 Name 最大长度 10
EnglishName = "ThisEnglishNameIsTooLong", // 该字段也超长,但会被 Name 先拦截(当前实现失败即返回)
Position = "Senior Engineer" // 不会执行到此字段校验
};
Result failed = rule.Apply(failedModel); // 预期失败并返回 FieldException
Console.WriteLine($"Failed Case => Ok:{failed.Ok}, Message:{failed.Message}");
if (failed.Error != null)
Console.WriteLine($"Failed Error Type => {failed.Error.GetType().FullName}");
Console.WriteLine("Done.");
}
}
/// <summary>
/// 扩展版长度特性
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
public class StringLengthExAttribute : StringLengthAttribute
{
/// <summary>
/// 字段显示名
/// </summary>
public string Name { get; }
/// <summary>
/// 是否先 Trim
/// </summary>
public bool Trim { get; set; } = true;
/// <summary>
/// 初始化特性
/// </summary>
public StringLengthExAttribute(string name, int maximumLength)
: base(maximumLength)
{
Name = name;
}
/// <summary>
/// 执行校验
/// </summary>
public override bool IsValid(object value)
{
if (Trim)
{
if (value is string stringValue)
value = stringValue.Trim();
}
return base.IsValid(value);
}
/// <summary>
/// 格式化错误消息
/// </summary>
/// <remarks>
/// 如果没有显式配置 ErrorMessage,就使用默认中文模板
/// </remarks>
public override string FormatErrorMessage(string name)
{
if (string.IsNullOrWhiteSpace(ErrorMessage))
{
ErrorMessage = MinimumLength == 0
? $"{Name}长度不能超过{{1}}"
: $"{Name}长度不能少于{{2}}或超过{{1}}";
}
return base.FormatErrorMessage(name);
}
}
/// <summary>
/// 字段校验异常
/// </summary>
public class FieldException : Exception
{
/// <summary>
/// 失败字段名
/// </summary>
public string Field { get; private set; }
public FieldException(string field, string message)
: base(message)
{
Field = field;
}
public FieldException() : base()
{
}
public FieldException(string message) : base(message)
{
}
public FieldException(string message, Exception innerException) : base(message, innerException)
{
}
}
/// <summary>
/// 统一返回结果
/// </summary>
public class Result
{
/// <summary>
/// 是否成功
/// </summary>
public bool Ok { get; private set; }
/// <summary>
/// 错误对象
/// </summary>
public Exception Error { get; private set; }
/// <summary>
/// 可读消息
/// </summary>
public string Message { get; private set; }
/// <summary>
/// 创建成功结果
/// </summary>
public static Result Success()
{
return new Result
{
Ok = true,
Message = "OK"
};
}
/// <summary>
/// 创建失败结果
/// </summary>
public static Result Failure(Exception error)
{
return new Result
{
Ok = false,
Error = error,
Message = error?.Message
};
}
}
/// <summary>
/// 验证委托
/// </summary>
public delegate Result Validation(object model);
/// <summary>
/// 字段校验入口
/// </summary>
public static class FieldCheckHelper
{
/// <summary>
/// 执行字段校验
/// </summary>
/// <remarks>
/// 按运行时类型取缓存委托并执行
/// </remarks>
public static Result CheckFields(object model)
{
Validation func = ValidationContainer.Instance.Get(model.GetType());
return func(model);
}
}
/// <summary>
/// 校验委托容器
/// </summary>
/// <remarks>
/// 按类型缓存委托,首次构建,后续复用
/// </remarks>
public class ValidationContainer
{
private readonly ConcurrentDictionary<Type, Validation> _container = new ConcurrentDictionary<Type, Validation>();
public static ValidationContainer Instance { get; } = new ValidationContainer();
/// <summary>
/// 获取校验委托
/// </summary>
public Validation Get(Type type)
{
return _container.GetOrAdd(type, x => ValidationBuilder.Instance.Build(x));
}
}
/// <summary>
/// 校验表达式构建器
/// </summary>
/// <remarks>
/// 扫描属性上的 ValidationAttribute,拼表达式树并编译成委托
/// </remarks>
public class ValidationBuilder
{
private static readonly Regex TokenRegex = new Regex(@"\{([A-Za-z_][A-Za-z0-9_]*)\}", RegexOptions.Compiled);
public static ValidationBuilder Instance { get; } = new ValidationBuilder();
/// <summary>
/// 构建校验委托
/// </summary>
public Validation Build(Type type)
{
if (!type.IsCustomClassType())
return _ => Result.Success(); // 非对象类型直接放行
ParameterExpression modelExpr = Expression.Parameter(typeof(object), "model");
ParameterExpression resultExpr = Expression.Variable(typeof(Result), "result");
LabelTarget returnLabel = Expression.Label(typeof(Result));
BlockExpression body = Expression.Block(
new[] { resultExpr },
BuildModelValidateExpr(type, modelExpr, returnLabel),
Expression.Return(returnLabel, Expression.Call(typeof(Result), "Success", Type.EmptyTypes)),
Expression.Label(returnLabel, resultExpr));
return Expression.Lambda<Validation>(body, modelExpr).Compile();
}
/// <summary>
/// 生成模型校验表达式
/// </summary>
/// <remarks>
/// 类只校验自身属性;接口会把继承链上的接口属性一并校验
/// </remarks>
private Expression BuildModelValidateExpr(Type type, Expression modelExpr, LabelTarget returnLabel)
{
Expression validateExpr = BuildSimpleModelValidateExpr(type, modelExpr, returnLabel);
if (!type.IsInterface)
return validateExpr;
IEnumerable<Type> interfaces = type.GetInterfaces();
IEnumerable<Expression> expressions = interfaces.Select(x => BuildSimpleModelValidateExpr(x, modelExpr, returnLabel));
return Expression.Block(expressions.Concat(new Expression[] { validateExpr }));
}
/// <summary>
/// 生成类型属性校验表达式
/// </summary>
private Expression BuildSimpleModelValidateExpr(Type type, Expression modelExpr, LabelTarget returnLabel)
{
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (properties.Length == 0)
return Expression.Empty();
IEnumerable<Expression> expressions = properties.Select(property =>
{
Attribute[] attrs = Attribute.GetCustomAttributes(property, typeof(ValidationAttribute), true);
MemberExpression propertyExpr = Expression.Property(Expression.Convert(modelExpr, type), property.Name);
return BuildPropValidateExpr(attrs, property, propertyExpr, returnLabel);
});
return Expression.Block(expressions);
}
/// <summary>
/// 生成属性校验表达式
/// </summary>
/// <remarks>
/// 把一个属性上的多个 ValidationAttribute 依次拼成表达式
/// </remarks>
private Expression BuildPropValidateExpr(Attribute[] attrs, PropertyInfo property, Expression propertyExpr, LabelTarget returnLabel)
{
if (attrs == null || attrs.Length == 0)
return Expression.Empty();
IEnumerable<Expression> expressions = attrs.Select(attr =>
{
ValidationAttribute validationAttr = attr as ValidationAttribute;
Expression isValidExpr = Expression.Call(
Expression.Constant(validationAttr),
"IsValid",
Type.EmptyTypes,
Expression.Convert(propertyExpr, typeof(object)));
// 校验失败时立即 Return
return Expression.IfThen(
Expression.IsFalse(isValidExpr),
Expression.Return(
returnLabel,
Expression.Call(
typeof(Result),
"Failure",
Type.EmptyTypes,
Expression.New(
typeof(FieldException).GetConstructor(new Type[] { typeof(string), typeof(string) }),
Expression.Constant(property.Name),
BuildErrorMsgExpr(validationAttr, property)))));
});
return Expression.Block(expressions);
}
/// <summary>
/// 构造错误消息
/// </summary>
/// <remarks>
/// 优先用默认格式化文案;若配置了模板则做占位符替换
/// </remarks>
private Expression BuildErrorMsgExpr(ValidationAttribute attr, PropertyInfo property)
{
if (string.IsNullOrWhiteSpace(attr.ErrorMessage))
return Expression.Constant(attr.FormatErrorMessage(property.Name));
Dictionary<string, object> dict = attr.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(x => x.Name, x => x.GetValue(attr), StringComparer.OrdinalIgnoreCase);
string message = TokenRegex.Replace(attr.ErrorMessage, match =>
{
string token = match.Groups[1].Value;
if (dict.TryGetValue(token, out object tokenValue))
return Convert.ToString(tokenValue);
return match.Value;
});
return Expression.Constant(message);
}
}
/// <summary>
/// 类型扩展
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// 判断是否可做对象校验
/// </summary>
/// <remarks>
/// 支持类和接口,排除 string
/// </remarks>
public static bool IsCustomClassType(this Type type)
{
return type != null
&& (type.IsClass || type.IsInterface)
&& type != typeof(string);
}
/// <summary>
/// 判断是否可枚举类型
/// </summary>
/// <remarks>
/// 当前 demo 未使用,预留给集合校验场景
/// </remarks>
public static bool IsEnumerableType(this Type type)
{
if (type == null || type == typeof(string))
return false;
return typeof(IEnumerable).IsAssignableFrom(type);
}
}
/// <summary>
/// 规则基类
/// </summary>
/// <remarks>
/// 在 Apply 里先做字段校验,校验通过后再执行具体业务
/// </remarks>
public abstract class RuleBase<TModel>
{
/// <summary>
/// 是否跳过字段校验
/// </summary>
public bool IgnoreFieldCheck { get; set; }
protected RuleBase(bool ignoreFieldCheck = false)
{
IgnoreFieldCheck = ignoreFieldCheck;
}
/// <summary>
/// 执行规则
/// </summary>
public Result Apply(TModel model)
{
if (!IgnoreFieldCheck)
{
if (model == null)
return Result.Failure(new ArgumentNullException(nameof(model))); // 空模型直接失败
Validation func = ValidationContainer.Instance.Get(typeof(TModel)); // 按声明模型类型获取委托(与主工程一致)
Result checkResult = func(model);
if (!checkResult.Ok)
return checkResult; // 字段校验失败:不中断返回业务层,直接退出
}
return OnApply(model);
}
/// <summary>
/// 业务扩展点
/// </summary>
protected abstract Result OnApply(TModel model);
}
/// <summary>
/// 接口模型
/// </summary>
public interface IUserProfile
{
[StringLengthEx("姓名", 10)]
string Name { get; set; }
[StringLengthEx("英文名", 8)]
string EnglishName { get; set; }
[StringLengthEx("职位", 20)]
string Position { get; set; }
}
/// <summary>
/// 实现模型
/// </summary>
public class DemoProfileModel : IUserProfile
{
public string Name { get; set; }
public string EnglishName { get; set; }
public string Position { get; set; }
}
/// <summary>
/// 更新规则示例
/// </summary>
/// <remarks>
/// 继承 RuleBase 后,字段校验会在 Apply 里自动执行
/// </remarks>
public class ProfileUpdateRule : RuleBase<IUserProfile>
{
protected override Result OnApply(IUserProfile model)
{
return Result.Success(); // demo 中业务处理省略
}
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。