首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >验证扩展v2

验证扩展v2
EN

Code Review用户
提问于 2016-06-14 10:46:47
回答 1查看 857关注 0票数 5

我还有另一个版本的验证扩展。我重新设计了它,并增加了一些新特性。它不再在表达式树上中继,但作为补偿,相同的扩展可以用于单元测试。

基类仍然是ValidationContext

代码语言:javascript
复制
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方法可以在派生类中被覆盖,该类允许为单元测试创建一个新的验证上下文。现在,它能够抛出单元测试环境会注意到的另一种异常。它将原始异常作为内部异常转发。

代码语言:javascript
复制
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方法和重构的验证:

代码语言:javascript
复制
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.");
    }
}

示例:

代码语言:javascript
复制
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中,因为当所有测试看起来相同时,它看起来很好):

代码语言:javascript
复制
[TestMethod]
public void IsNullPasses()
{
   new Action(() => ((string)null).Validate().IsNull()).Test().DoesNotThrow();
}

[TestMethod]
public void IsNullThrows()
{
    new Action(() => "".Validate().IsNull()).Test().Throws<ArgumentException>();
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2016-06-18 20:27:26

我对上面的解决方案并不满意,因此我对其进行了一些重构,以获得以下结果:

验证上下文有太多的响应性,因此它失去了它的Validate方法:

代码语言:javascript
复制
public class ValidationContext<TArg>
{
    public ValidationContext(TArg argument, string memberName)
    {
        Argument = argument;
        MemberName = memberName;
    }

    public TArg Argument { get; }

    public string MemberName { get; }
}

相反,我创建了一个Validation类来存储结果,并在必要时抛出一个异常:

代码语言:javascript
复制
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对象返回的验证生成器:

代码语言:javascript
复制
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));
    }
}

它的用法没有多大变化:

代码语言:javascript
复制
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."));
}

还有一个真实的用法示例:

代码语言:javascript
复制
public static bool IsStatic(this Type type)
{
    type.Validate(nameof(type)).IsNotNull();
    return type.IsAbstract && type.IsSealed;
}
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/131959

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档