我还有另一个版本的验证扩展。我重新设计了它,并增加了一些新特性。它不再在表达式树上中继,但作为补偿,相同的扩展可以用于单元测试。
基类仍然是ValidationContext:
public class ValidationContext<TArg>
{
private string _memberName;
public TArg Argument { get; internal set; }
public string MemberName
{
get
{
if (!string.IsNullOrEmpty(_memberName)) return _memberName;
var memberExpression = Expression().Body as MemberExpression;
return memberExpression.Member.Name;
}
set { _memberName = value; }
}
internal Func<Expression<Func<TArg>>> Expression { get; set; }
public virtual ValidationContext<TArg> Validate<TException>(
Func<TArg, bool> predicate,
params object[] args)
where TException : Exception
{
if (!predicate(Argument))
{
throw (TException)Activator.CreateInstance(typeof(TException), args);
}
return this;
}
}然而,在此版本中,在检查验证规则后抛出异常的是该版本。Validate方法可以在派生类中被覆盖,该类允许为单元测试创建一个新的验证上下文。现在,它能够抛出单元测试环境会注意到的另一种异常。它将原始异常作为内部异常转发。
public class UnitTestingValidationContext<TArg> : ValidationContext<TArg>
{
public override ValidationContext<TArg> Validate<TException>(
Func<TArg, bool> predicate,
params object[] args)
{
try
{
if (!predicate(Argument))
{
throw (TException)Activator.CreateInstance(typeof(TException), args);
}
return this;
}
catch (Exception inner)
{
// cannot throw this in linqpad
//throw new AssertFailedException
throw new Exception("This is a test.", inner);
}
}
}因此,Validations扩展发生了一些变化,得到了一个新的Test方法和重构的验证:
public static class Validations
{
// crates validation context for normal usage
public static ValidationContext<TArg> Validate<TArg>(
this TArg arg,
string memberName)
{
return new ValidationContext<TArg>()
{
Argument = arg,
MemberName = memberName
};
}
// create validation context for unit-testing
public static UnitTestingValidationContext<TArg> Test<TArg>(
this TArg arg,
string memberName)
{
return new UnitTestingValidationContext<TArg>()
{
Argument = arg,
MemberName = memberName
};
}
// validations don't throw exceptions anymore but tell the context how to do it
public static ValidationContext<TArg> IsNotNull<TArg>(
this ValidationContext<TArg> context)
{
return context.Validate<ArgumentNullException>(arg =>
arg != null, context.MemberName, "Test message.");
}
public static ValidationContext<string> IsNotNullOrEmpty(
this ValidationContext<string> context)
{
return context.Validate<ArgumentNullException>(arg =>
!string.IsNullOrEmpty(context.Argument), context.MemberName, "Test message.");
}
}示例:
var foo = (string)null;
// normal usage like for method parameters etc.
foo.Validate("foo").IsNotNullOrEmpty(); // bam!
// in unit-testing
foo.Test("foo").IsNotNullOrEmpty(); // bam!一些更多的示例展示了我是如何使用扩展的(这是一个特例,我将验证包装在Action中,因为当所有测试看起来相同时,它看起来很好):
[TestMethod]
public void IsNullPasses()
{
new Action(() => ((string)null).Validate().IsNull()).Test().DoesNotThrow();
}
[TestMethod]
public void IsNullThrows()
{
new Action(() => "".Validate().IsNull()).Test().Throws<ArgumentException>();
}发布于 2016-06-18 20:27:26
我对上面的解决方案并不满意,因此我对其进行了一些重构,以获得以下结果:
验证上下文有太多的响应性,因此它失去了它的Validate方法:
public class ValidationContext<TArg>
{
public ValidationContext(TArg argument, string memberName)
{
Argument = argument;
MemberName = memberName;
}
public TArg Argument { get; }
public string MemberName { get; }
}相反,我创建了一个Validation类来存储结果,并在必要时抛出一个异常:
public class Validation<T>
{
public Validation(ValidationContext<T> context, bool success)
{
Context = context;
Success = success;
}
public ValidationContext<T> Context { get; }
public bool Success { get; set; }
public ValidationContext<T> Throw<TException>(Func<ValidationContext<T>, string> getMessage) where TException : Exception
{
if (Success)
{
return Context;
}
var message = getMessage(Context)?.Format(Context);
throw (TException)Activator.CreateInstance(typeof(TException), message);
}
}以及执行验证并将结果作为Validation对象返回的验证生成器:
public static class ValidationBuilder
{
public static Validation<TArg> If<TArg>(this ValidationContext<TArg> context, Func<TArg, bool> predicate)
{
return new Validation<TArg>(context, predicate(context.Argument));
}
}它的用法没有多大变化:
public static ValidationContext<TArg> IsNotNull<TArg>
(
this ValidationContext<TArg> context, Func<ValidationContext<TArg>,
string> getMessage = null
)
{
return context
.If(arg => arg == null)
.Throw<ValidationException>(
getMessage ?? (ctx => $"'{ctx.MemberName}' must not be null."));
}还有一个真实的用法示例:
public static bool IsStatic(this Type type)
{
type.Validate(nameof(type)).IsNotNull();
return type.IsAbstract && type.IsSealed;
}https://codereview.stackexchange.com/questions/131959
复制相似问题