Valide assemblies e namespaces no arquivo de configuração do VAB

Estamos usando a versão 4.1 do bloco de aplicativos de validação. Eu sou relativamente novo para isso, então eu queria saber se ele tinha a capacidade de abstrair os namespaces e assemblies configurados ou fornecer validação adequada de sua existência?

Ocorreu um problema recentemente em que alguém moveu uma turma e não atualizou o arquivo de configuração de validação com o novo namespace. Como resultado, as validações não estavam mais sendo aplicadas ao object. O bloco de aplicação parece apenas ignorar as discrepâncias. Infelizmente, isso não foi detectado durante o ciclo normal de controle de qualidade. Existe alguma maneira de nos protegermos desse tipo de mudança no futuro? O que eu fiz nesse ínterim é carregar o config xml, extrair todos os assemblies e definir namespaces e validar que todos eles existem.

Escreva um conjunto de testes de unidade para ver se os objects são validados corretamente.

Outra opção é construir dinamicamente a configuração do vab de forma fluente. Isso lhe dará suporte de tempo de compilation e segurança de refatoração.

Há apenas um problema com isso: o vab não tem suporte para isso de imediato . Você terá que escrever isso sozinho. Criar um projeto de contribuição do VAB com esse recurso está na minha lista de tarefas há anos, mas nunca tive tempo para fazer isso. Não é tão difícil assim. Aqui está o código que escrevi há alguns anos (mas nunca consegui terminar):

public interface IValidationConfigurationMemberCreator { ValidationConfigurationBuilderRuleset BuilderRuleset { get; } } public static class ValidationConfigurationBuilderExtensions { public static ValidationConfigurationBuilderProperty ForField( this IValidationConfigurationMemberCreator memberCreator, Expression> fieldSelector) { string fieldName = ExtractFieldName(fieldSelector); return new ValidationConfigurationBuilderProperty(memberCreator.BuilderRuleset, fieldName); } public static ValidationConfigurationBuilderProperty ForProperty( this IValidationConfigurationMemberCreator memberCreator, Expression> propertySelector) { string propertyName = ExtractPropertyName(propertySelector); return new ValidationConfigurationBuilderProperty(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 AddRangeValidator( this ValidationConfigurationBuilderMember memberBuilder, RangeData rangeData) where TMember : IComparable { memberBuilder.AddValidator(rangeData.CreateValidator()); return memberBuilder; } } public class ValidationConfigurationBuilder : IConfigurationSource { private readonly ValidationSettings settings; private readonly HashSet alternativeRulesetNames = new HashSet(StringComparer.Ordinal); private string defaultRulesetName; public ValidationConfigurationBuilder() { this.settings = new ValidationSettings(); } public event EventHandler 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 ForType() { 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(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 : IValidationConfigurationMemberCreator { internal ValidationConfigurationBuilderType(ValidationConfigurationBuilder builder, ValidatedTypeReference typeReference) { this.Builder = builder; this.TypeReference = typeReference; } ValidationConfigurationBuilderRuleset IValidationConfigurationMemberCreator.BuilderRuleset { get { return this.ForDefaultRuleset(); } } internal ValidationConfigurationBuilder Builder { get; private set; } internal ValidatedTypeReference TypeReference { get; private set; } public ValidationConfigurationBuilderRuleset 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(this, defaultRuleset); } public ValidationConfigurationBuilderRuleset 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(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 : IValidationConfigurationMemberCreator { internal ValidationConfigurationBuilderRuleset( ValidationConfigurationBuilderType builderType, ValidationRulesetData ruleset) { this.BuilderType = builderType; this.Ruleset = ruleset; } internal ValidationConfigurationBuilderType BuilderType { get; private set; } ValidationConfigurationBuilderRuleset IValidationConfigurationMemberCreator.BuilderRuleset { get { return this; } } internal ValidationRulesetData Ruleset { get; private set; } internal ValidationConfigurationBuilderRuleset 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 : IValidationConfigurationMemberCreator { private readonly ValidationConfigurationBuilderRuleset builderRuleset; internal ValidationConfigurationBuilderMember( ValidationConfigurationBuilderRuleset builderRuleset, string memberName) { this.builderRuleset = builderRuleset; this.MemberName = memberName; } ValidationConfigurationBuilderRuleset IValidationConfigurationMemberCreator.BuilderRuleset { get { return this.builderRuleset; } } internal ValidationConfigurationBuilderRuleset BuilderRuleset { get { return this.builderRuleset; } } internal string MemberName { get; private set; } public ValidationConfigurationBuilderMember AddValidator(Validator validator) { if (validator == null) { throw new ArgumentNullException("validator"); } return AddValidator(new SingletonValidatorData(validator)); } public ValidationConfigurationBuilderMember 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 : ValidationConfigurationBuilderMember { internal ValidationConfigurationBuilderProperty( ValidationConfigurationBuilderRuleset 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 : ValidationConfigurationBuilderMember { internal ValidationConfigurationBuilderField( ValidationConfigurationBuilderRuleset 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 : 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 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; } } 

Usando este código você pode definir sua configuração fluentemente da seguinte maneira:

 const string DefaultRuleset = "Default"; const string AlternativeRuleset = "Alternative"; var builder = new ValidationConfigurationBuilder(); builder.RegisterDefaultRulesetForAllTypes(DefaultRuleset); builder.ForType() .ForProperty(p => p.Age) .AddRangeValidator(new RangeData() { 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().ForRuleset(AlternativeRuleset) .ForProperty(p => p.FirstName) .AddValidator(new NotNullValidator()) .AddValidator(new StringLengthValidatorData() { LowerBound = 1, LowerBoundType = RangeBoundaryType.Inclusive, UpperBound = 10, UpperBoundType = RangeBoundaryType.Inclusive }); 

O ValidationConfigurationBuilder é um IConfigurationSource e você pode fornecê-lo para a instância ValidationFactory.CreateValidator . Aqui está um exemplo:

 var validator = ValidationFactory.CreateValidator(builder); var instance = new Person(); var results = validator.Validate(instance); 

Uma das possíveis soluções que vem para mim é o uso de conceitos de programação AOP . Em suma, por exemplo, no seu caso, você marca o código “frágil” com algum atributo e, em tempo de compilation, verifica se o type , a member function , a property … está em um estado que você pretende que esteja.

Como uma referência:

CSharpCornerArticle-2009 (velho mas ainda bom)

PostSharp (pode ser a melhor ferramenta no mercado agora para AOP)

Rolsyn (compilador como um serviço fornecido pelo MS. Você pode escrever seu próprio pequeno analisador de código C# ou VB.NET e injetá-lo dentro de seu ambiente de CI.

Espero que isto ajude.

Eu tive um problema semelhante, então escrevi um wrapper para a validação da Enterprise Library, que procura todos os validadores na implementação em tempo de execução e os registra para mim. Eu então felizmente deletei minha configuração XML.

Eu escrevi um blog sobre isso aqui .

    Intereting Posts