验证 VAB 配置文件中的程序集和命名空间

发布于 2024-12-27 15:33:54 字数 258 浏览 2 评论 0原文

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

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

We are using version 4.1 of the validation application block. I am relatively new to it so I was wondering if it had the ability to either abstract out the configured namespaces and assemblies or otherwise provide proper validation of their existence?

We had an issue recently where someone moved a class and didn't update the validation configuration file with the new namespace. As a result the validations were no longer being applied to the object. The application block seems to just ignore the discrepancies. Unfortunately this was not caught during the normal QA cycle. Is there any built in way to protect ourselves from this type of change in the future? What I did in the interim is load up the config xml, extract out all the assemblies and defined namespaces and validate that they all exist.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

魂牵梦绕锁你心扉 2025-01-03 15:33:54

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

另一种选择是以流畅的方式动态构建 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);

Write a set of unit tests to see if objects get validated correctly.

Another option is to build up the vab configuration dynamically in a fluent way. This will give you compile time support and refactoring safety.

There is only one problem with this: vab has no support for this out of the box. You will have to write this yourself. Creating a VAB contrib project with such feature is on my todo list for years already, but I never had the time to do this. It isn't that hard though. Here is the code I wrote a few years back (but never got the time to finish it):

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;
    }
}

Using this code you can define your configuration fluently as follows:

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
        });

The ValidationConfigurationBuilder is a IConfigurationSource and you can supply it to the ValidationFactory.CreateValidator instance. Here's an example:

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

var instance = new Person();

var results = validator.Validate(instance);
泪痕残 2025-01-03 15:33:54

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

就像引用:

CSharpCornerArticle-2009(旧但仍然很好)

PostSharp(可能是现在市场上最好的工具对于 AOP)

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

希望这会有所帮助。

One of possible solutions that comes to me is using of AOP programming concepts.
In short, for example in your case, you mark the "fragile" code with some attibute and at compile time check if the type, member function, property... is in a state you intendt it ti be in.

Like a references:

CSharpCornerArticle-2009 (old but still good one)

PostSharp (may be the best tool on the market now for AOP)

Rolsyn (compiler as a Service provided by MS. You can write your own small parser of C# or VB.NET code and inject it inside you CI environment.

Hope this helps.

冰之心 2025-01-03 15:33:54

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

我在此处写了一篇关于它的博客。

I had a similar issue, so I wrote a wrapper for the Enterprise Library validation which looks up all the validators in the deployment at runtime and registers them for me. I then happily deleted my XML configuration.

I've written a blog about it here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文