1

我们正在使用 4.1 版的验证应用程序块。我对它比较陌生,所以我想知道它是否有能力抽象出配置的命名空间和程序集,或者以其他方式对它们的存在提供适当的验证?

我们最近遇到了一个问题,有人移动了一个类并且没有使用新的命名空间更新验证配置文件。结果,验证不再应用于对象。应用程序块似乎只是忽略了差异。不幸的是,这在正常的 QA 周期中没有被发现。有没有内置的方法来保护我们自己免受未来这种类型的变化?我在此期间所做的是加载配置 xml,提取所有程序集和定义的命名空间并验证它们是否都存在。

4

3 回答 3

0

我想到的一种可能的解决方案是使用AOP编程概念。简而言之,例如在您的情况下,您使用一些属性标记“脆弱”代码,并在编译时检查type, member function, property... 是否处于您想要的状态。

就像一个参考:

CSharpCornerArticle-2009 (旧但仍然不错)

PostSharp(可能是目前市场上最好的 AOP 工具)

Rolsyn(MS 提供的编译器即服务。您可以编写自己的小型解析器C#VB.NET代码并将其注入您的 CI 环境中。

希望这可以帮助。

于 2012-01-17T19:44:44.650 回答
0

我有一个类似的问题,所以我为企业库验证编写了一个包装器,它在运行时查找部署中的所有验证器并为我注册它们。然后我很高兴地删除了我的 XML 配置。

我在这里写了一篇关于它的博客。

于 2012-01-17T20:13:00.493 回答
0

编写一组单元测试以查看对象是否得到正确验证。

另一种选择是以流畅的方式动态构建 vab 配置。这将为您提供编译时支持和重构安全性。

这只有一个问题:vab 不支持开箱即用。你必须自己写这个。多年来,创建具有此类功能的 VAB contrib 项目已经在我的待办事项列表上,但我从来没有时间这样做。不过这并不难。这是我几年前写的代码(但从来没有时间完成它):

public interface IValidationConfigurationMemberCreator<TEntity>
{
    ValidationConfigurationBuilderRuleset<TEntity> BuilderRuleset { get; }
}

public static class ValidationConfigurationBuilderExtensions
{
    public static ValidationConfigurationBuilderProperty<TEntity, TField> ForField<TEntity, TField>(
        this IValidationConfigurationMemberCreator<TEntity> memberCreator,
        Expression<Func<TEntity, TField>> fieldSelector)
    {
        string fieldName = ExtractFieldName(fieldSelector);

        return new ValidationConfigurationBuilderProperty<TEntity, TField>(memberCreator.BuilderRuleset,
            fieldName);
    }

    public static ValidationConfigurationBuilderProperty<TEntity, TProperty>
        ForProperty<TEntity, TProperty>(
        this IValidationConfigurationMemberCreator<TEntity> memberCreator,
        Expression<Func<TEntity, TProperty>> propertySelector)
    {
        string propertyName = ExtractPropertyName(propertySelector);

        return new ValidationConfigurationBuilderProperty<TEntity, TProperty>(memberCreator.BuilderRuleset,
            propertyName);
    }


    private static string ExtractPropertyName(LambdaExpression propertySelector)
    {
        if (propertySelector == null)
        {
            throw new ArgumentNullException("propertySelector");
        }

        var body = propertySelector.Body as MemberExpression;

        if (body == null || body.Member.MemberType != MemberTypes.Property)
        {
            throw new ArgumentException("The given expression should return a property.",
                "propertySelector");
        }

        return body.Member.Name;
    }

    private static string ExtractFieldName(LambdaExpression fieldSelector)
    {
        if (fieldSelector == null)
        {
            throw new ArgumentNullException("fieldSelector");
        }

        var body = fieldSelector.Body as MemberExpression;

        if (body == null || body.Member.MemberType != MemberTypes.Field)
        {
            throw new ArgumentException("The given expression should return a field.",
                "fieldSelector");
        }

        return body.Member.Name;
    }

    public static ValidationConfigurationBuilderMember<TEntity, TMember> AddRangeValidator<TEntity, TMember>(
       this ValidationConfigurationBuilderMember<TEntity, TMember> memberBuilder,
      RangeData<TMember> rangeData) where TMember : IComparable
    {
        memberBuilder.AddValidator(rangeData.CreateValidator());

        return memberBuilder;
    }
}

public class ValidationConfigurationBuilder : IConfigurationSource
{
    private readonly ValidationSettings settings;
    private readonly HashSet<string> alternativeRulesetNames = new HashSet<string>(StringComparer.Ordinal);
    private string defaultRulesetName;

    public ValidationConfigurationBuilder()
    {
        this.settings = new ValidationSettings();
    }

    public event EventHandler<ConfigurationSourceChangedEventArgs> SourceChanged;

    public void RegisterDefaultRulesetForAllTypes(string rulesetName)
    {
        if (string.IsNullOrEmpty(rulesetName))
        {
            throw new ArgumentNullException("rulesetName");
        }

        if (this.settings.Types.Count > 0)
        {
            throw new InvalidOperationException("Registeringen rulesets for all types is not possible " +
             "after types are registered.");
        }

        this.defaultRulesetName = rulesetName;
    }

    public void RegisterAlternativeRulesetForAllTypes(string rulesetName)
    {
        if (string.IsNullOrEmpty(rulesetName))
        {
            throw new ArgumentNullException("rulesetName");
        }

        if (this.settings.Types.Count > 0)
        {
            throw new InvalidOperationException("Registeringen rulesets for all types is not possible " +
             "after types are registered.");
        }

        if (this.alternativeRulesetNames.Contains(rulesetName))
        {
            throw new InvalidOperationException("There already is a ruleset with this name: " +
                rulesetName);
        }

        this.alternativeRulesetNames.Add(rulesetName);
    }

    public ValidationConfigurationBuilderType<T> ForType<T>()
    {
        ValidatedTypeReference typeReference;

        if (this.settings.Types.Contains(typeof(T).FullName))
        {
            typeReference = this.settings.Types.Get(typeof(T).FullName);
        }
        else
        {
            typeReference = new ValidatedTypeReference(typeof(T))
            {
                AssemblyName = typeof(T).Assembly.GetName().FullName,
            };

            if (this.defaultRulesetName != null)
            {
                typeReference.Rulesets.Add(new ValidationRulesetData(this.defaultRulesetName));
                typeReference.DefaultRuleset = this.defaultRulesetName;
            }

            foreach (var alternativeRulesetName in this.alternativeRulesetNames)
            {
                typeReference.Rulesets.Add(new ValidationRulesetData(alternativeRulesetName));
            }

            this.settings.Types.Add(typeReference);
        }

        return new ValidationConfigurationBuilderType<T>(this, typeReference);
    }

    ConfigurationSection IConfigurationSource.GetSection(string sectionName)
    {
        if (sectionName == ValidationSettings.SectionName)
        {
            return this.settings;
        }

        return null;
    }

    #region IConfigurationSource Members

    void IConfigurationSource.Add(string sectionName, ConfigurationSection configurationSection)
    {
        throw new NotImplementedException();
    }

    void IConfigurationSource.AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
    {
        throw new NotImplementedException();
    }

    void IConfigurationSource.Remove(string sectionName)
    {
        throw new NotImplementedException();
    }

    void IConfigurationSource.RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
    {
        throw new NotImplementedException();
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
    }
}

public class ValidationConfigurationBuilderType<TEntity>
    : IValidationConfigurationMemberCreator<TEntity>
{
    internal ValidationConfigurationBuilderType(ValidationConfigurationBuilder builder,
        ValidatedTypeReference typeReference)
    {
        this.Builder = builder;
        this.TypeReference = typeReference;
    }

    ValidationConfigurationBuilderRuleset<TEntity> IValidationConfigurationMemberCreator<TEntity>.BuilderRuleset
    {
        get { return this.ForDefaultRuleset(); }
    }

    internal ValidationConfigurationBuilder Builder { get; private set; }

    internal ValidatedTypeReference TypeReference { get; private set; }

    public ValidationConfigurationBuilderRuleset<TEntity> ForDefaultRuleset()
    {
        if (string.IsNullOrEmpty(this.TypeReference.DefaultRuleset))
        {
            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
                "There hasn't been an default ruleset registered for the type {0}.", typeof(TEntity).FullName));
        }

        var defaultRuleset = this.TypeReference.Rulesets.Get(this.TypeReference.DefaultRuleset);

        if (defaultRuleset == null)
        {
            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
                "The default ruleset with name {0} is missing from type {1}.",
                this.TypeReference.DefaultRuleset, typeof(TEntity).FullName));
        }

        return new ValidationConfigurationBuilderRuleset<TEntity>(this, defaultRuleset);
    }

    public ValidationConfigurationBuilderRuleset<TEntity> ForRuleset(string rulesetName)
    {
        var ruleset = this.TypeReference.Rulesets.Get(rulesetName);

        if (ruleset == null)
        {
            ruleset = new ValidationRulesetData(rulesetName);

            this.TypeReference.Rulesets.Add(ruleset);

            //throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
            //    "The ruleset with name '{0}' has not been registered yet for type {1}.",
            //    rulesetName, this.TypeReference.Name));
        }

        return new ValidationConfigurationBuilderRuleset<TEntity>(this, ruleset);
    }

    internal void CreateDefaultRuleset(string rulesetName)
    {
        if (this.TypeReference.Rulesets.Get(rulesetName) != null)
        {
            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                "The ruleset with name '{0}' has already been registered yet for type {1}.",
                rulesetName, this.TypeReference.Name));
        }

        if (!string.IsNullOrEmpty(this.TypeReference.DefaultRuleset))
        {
            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                "The type {0} already has a default ruleset.", this.TypeReference.Name));
        }

        var ruleset = new ValidationRulesetData(rulesetName);

        this.TypeReference.Rulesets.Add(ruleset);
        this.TypeReference.DefaultRuleset = rulesetName;
    }

    internal void CreateAlternativeRuleset(string rulesetName)
    {
        if (this.TypeReference.Rulesets.Get(rulesetName) != null)
        {
            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                "The ruleset with name '{0}' has already been registered yet for type {1}.",
                rulesetName, this.TypeReference.Name));
        }

        var ruleset = new ValidationRulesetData(rulesetName);

        this.TypeReference.Rulesets.Add(ruleset);
    }
}

public class ValidationConfigurationBuilderRuleset<TEntity> : IValidationConfigurationMemberCreator<TEntity>
{
    internal ValidationConfigurationBuilderRuleset(
        ValidationConfigurationBuilderType<TEntity> builderType,
        ValidationRulesetData ruleset)
    {
        this.BuilderType = builderType;
        this.Ruleset = ruleset;
    }

    internal ValidationConfigurationBuilderType<TEntity> BuilderType { get; private set; }

    ValidationConfigurationBuilderRuleset<TEntity>
        IValidationConfigurationMemberCreator<TEntity>.BuilderRuleset { get { return this; } }

    internal ValidationRulesetData Ruleset { get; private set; }

    internal ValidationConfigurationBuilderRuleset<TEntity> AddValidator(ValidatorData validator)
    {
        if (validator == null)
        {
            throw new ArgumentNullException("validator");
        }

        // 'Name' is the default value when the validator has not been given a name.
        if (validator.Name == "Name")
        {
            // In that case we set the name to something more specific.
            validator.Name = typeof(TEntity).Name + "_" + validator.Type.Name;
        }

        var validators = this.Ruleset.Validators;

        // When that specific name already exist, we add a number to that name to ensure uniqueness.
        if (validators.Contains(validator.Name))
        {
            validator.Name += "_" + validators.Count.ToString();
        }

        validators.Add(validator);

        return this;
    }
}

public abstract class ValidationConfigurationBuilderMember<TEntity, TMember>
    : IValidationConfigurationMemberCreator<TEntity>
{
    private readonly ValidationConfigurationBuilderRuleset<TEntity> builderRuleset;

    internal ValidationConfigurationBuilderMember(
        ValidationConfigurationBuilderRuleset<TEntity> builderRuleset, string memberName)
    {
        this.builderRuleset = builderRuleset;
        this.MemberName = memberName;
    }

    ValidationConfigurationBuilderRuleset<TEntity>
        IValidationConfigurationMemberCreator<TEntity>.BuilderRuleset { get { return this.builderRuleset; } }

    internal ValidationConfigurationBuilderRuleset<TEntity> BuilderRuleset
    {
        get { return this.builderRuleset; }
    }

    internal string MemberName { get; private set; }

    public ValidationConfigurationBuilderMember<TEntity, TMember> AddValidator(Validator validator)
    {
        if (validator == null)
        {
            throw new ArgumentNullException("validator");
        }

        return AddValidator(new SingletonValidatorData<TEntity>(validator));
    }

    public ValidationConfigurationBuilderMember<TEntity, TMember> AddValidator(ValidatorData validatorData)
    {
        if (validatorData == null)
        {
            throw new ArgumentNullException("validatorData");
        }

        var memberReference = this.GetOrCreateMemberReference();

        // 'Name' is the default value when the validator has not been given a name.
        if (validatorData.Name == "Name")
        {
            // In that case we set the name to something more specific.
            validatorData.Name = typeof(TEntity).Name + "_" +
                this.BuilderRuleset.Ruleset.Name + "_" + validatorData.Type.Name;
        }

        // When that specific name already exist, we add a number to that name to ensure uniqueness.
        if (memberReference.Validators.Contains(validatorData.Name))
        {
            validatorData.Name += "_" + memberReference.Validators.Count.ToString();
        }

        memberReference.Validators.Add(validatorData);

        return this;
    }

    internal abstract ValidatedMemberReference GetOrCreateMemberReference();
}

public class ValidationConfigurationBuilderProperty<TEntity, TProperty>
    : ValidationConfigurationBuilderMember<TEntity, TProperty>
{
    internal ValidationConfigurationBuilderProperty(
        ValidationConfigurationBuilderRuleset<TEntity> builderRuleset, string propertyName)
        : base(builderRuleset, propertyName)
    {
    }

    internal override ValidatedMemberReference GetOrCreateMemberReference()
    {
        var properties = this.BuilderRuleset.Ruleset.Properties;

        var propertyReference = properties.Get(this.MemberName);

        if (propertyReference == null)
        {
            propertyReference = new ValidatedPropertyReference(this.MemberName);
            properties.Add(propertyReference);
        }

        return propertyReference;
    }
}

public class ValidationConfigurationBuilderField<TEntity, TField>
    : ValidationConfigurationBuilderMember<TEntity, TField>
{
    internal ValidationConfigurationBuilderField(
        ValidationConfigurationBuilderRuleset<TEntity> builderRuleset, string fieldName)
        : base(builderRuleset, fieldName)
    {
    }

    internal override ValidatedMemberReference GetOrCreateMemberReference()
    {
        var fields = this.BuilderRuleset.Ruleset.Fields;

        var fieldReference = fields.Get(this.MemberName);

        if (fieldReference == null)
        {
            fieldReference = new ValidatedFieldReference(this.MemberName);
            fields.Add(fieldReference);
        }

        return fieldReference;
    }
}

internal class SingletonValidatorData<TEntity> : ValidatorData
{
    private readonly Validator validator;

    public SingletonValidatorData(Validator validator)
        : base(typeof(TEntity).Name + "ValidatorData", typeof(TEntity))
    {
        this.validator = validator;
    }

    protected override Validator DoCreateValidator(Type targetType)
    {
        return this.validator;
    }
}

[DebuggerDisplay("Range (LowerBound: {LowerBound}, LowerBoundType: {LowerBoundType}, UpperBound: {UpperBound}, UpperBoundType: {UpperBoundType})")]
public class RangeData<T> where T : IComparable
{
    private T lowerBound;
    private RangeBoundaryType lowerBoundType;
    private bool lowerBoundTypeSet;
    private T upperBound;
    private RangeBoundaryType upperBoundType;
    private bool upperBoundTypeSet;

    public T LowerBound
    {
        get
        {
            return this.lowerBound;
        }

        set
        {
            this.lowerBound = value;

            if (!this.lowerBoundTypeSet)
            {
                this.lowerBoundType = RangeBoundaryType.Inclusive;
            }
        }
    }

    public RangeBoundaryType LowerBoundType
    {
        get
        {
            return this.lowerBoundType;
        }

        set
        {
            this.lowerBoundType = value;
            this.lowerBoundTypeSet = true;
        }
    }

    public T UpperBound
    {
        get
        {
            return this.upperBound;
        }

        set
        {
            this.upperBound = value;

            if (!this.upperBoundTypeSet)
            {
                this.upperBoundType = RangeBoundaryType.Inclusive;
            }
        }
    }

    public RangeBoundaryType UpperBoundType
    {
        get
        {
            return this.upperBoundType;
        }

        set
        {
            this.upperBoundType = value;
            this.upperBoundTypeSet = true;
        }
    }

    public bool Negated { get; set; }

    public virtual string MessageTemplate { get; set; }

    public virtual string MessageTemplateResourceName { get; set; }

    public virtual string MessageTemplateResourceTypeName { get; set; }

    public virtual string Tag { get; set; }

    internal RangeValidator CreateValidator()
    {
        return new RangeValidator(this.LowerBound, this.LowerBoundType, this.UpperBound,
            this.UpperBoundType, this.GetMessageTemplate(), this.Negated)
        {
            Tag = this.Tag,
        };
    }

    internal string GetMessageTemplate()
    {
        if (!string.IsNullOrEmpty(this.MessageTemplate))
        {
            return this.MessageTemplate;
        }
        Type messageTemplateResourceType = this.GetMessageTemplateResourceType();
        if (messageTemplateResourceType != null)
        {
            return ResourceStringLoader.LoadString(messageTemplateResourceType.FullName,
                this.MessageTemplateResourceName, messageTemplateResourceType.Assembly);
        }

        return null;
    }

    private Type GetMessageTemplateResourceType()
    {
        if (!string.IsNullOrEmpty(this.MessageTemplateResourceTypeName))
        {
            return Type.GetType(this.MessageTemplateResourceTypeName);
        }
        return null;
    }
}

使用此代码,您可以流畅地定义配置,如下所示:

const string DefaultRuleset = "Default";
const string AlternativeRuleset = "Alternative";

var builder = new ValidationConfigurationBuilder();

builder.RegisterDefaultRulesetForAllTypes(DefaultRuleset);

builder.ForType<Person>()
    .ForProperty(p => p.Age)
        .AddRangeValidator(new RangeData<int>()
        {
            LowerBound = 18,
            MessageTemplate = "This is an adult system",
        })
    .ForProperty(p => p.FirstName)
        .AddValidator(new NotNullValidator())
        .AddValidator(new StringLengthValidatorData()
        {
            LowerBound = 1,
            LowerBoundType = RangeBoundaryType.Inclusive,
            UpperBound = 100,
            UpperBoundType = RangeBoundaryType.Inclusive
        })
    .ForProperty(p => p.LastName)
        .AddValidator(new NotNullValidator())
    .ForProperty(p => p.Friends)
        .AddValidator(new ObjectCollectionValidator());

builder.ForType<Person>().ForRuleset(AlternativeRuleset)
    .ForProperty(p => p.FirstName)
        .AddValidator(new NotNullValidator())
        .AddValidator(new StringLengthValidatorData()
        {
            LowerBound = 1,
            LowerBoundType = RangeBoundaryType.Inclusive,
            UpperBound = 10,
            UpperBoundType = RangeBoundaryType.Inclusive
        });

ValidationConfigurationBuilder是一个IConfigurationSource,您可以将它提供给ValidationFactory.CreateValidator实例。这是一个例子:

var validator = ValidationFactory.CreateValidator<Person>(builder);

var instance = new Person();

var results = validator.Validate(instance);
于 2012-01-18T06:42:49.157 回答