Переполнение стека при использовании проверки Microsoft EnterpriseLibrary

У меня два класса:

[HasSelfValidation]
class Country : DomainObject
{
    [NotNullValidator]
    [StringLengthValidator(2, 10)]
    public string Name { get; set; }

    [ObjectCollectionValidator(typeof(Region))]
    public List<Region> Regions { get; set; }
}

а также

[HasSelfValidation]
class Region : DomainObject
{
    [NotNullValidator]
    [ObjectValidator]
    public Country Country { get; set; }

    [NotNullValidator]
    [StringLengthValidator(2, 20)]
    public string Name { get; set; }
}

Где DomainObject имеет метод:

public virtual ValidationResults Validate()
{
    Validator validator = 
        ValidationFactory.CreateValidator(this.GetType());
    ValidationResults results = new ValidationResults();
    validator.Validate(this, results);
    return results;
}

Я использую Microsoft Enterprise Library 4.1 - октябрь 2008 г. / .NET 3.5 SP1 / Vista.

Если я вызываю Validate вновь созданный объект Country с пустым значением в качестве списка регионов, я получаю исключение StackOverflow. Если я удалю [ObjectCollectionValidator (typeof (Region))] для свойства Country.Regions, все будет работать нормально. Думаю, ссылка Страна - Регион - Страна - причина сбоя. Однако я не хочу удалять проверку коллекции Regions; ни удаление [ObjectValidator] из региона не вариант для меня. Могу ли я что-нибудь сделать для поддержки всех этих атрибутов проверки без исключения StackOverflow?

Спасибо,

Люциан

Ответов (3)

Я столкнулся с этой же проблемой буквально на днях и смог решить ее, реализовав собственный класс валидатора, который работает так же, как ObjectValidator, за исключением того, что он задерживает оценку оцениваемого свойства до фактического метода DoValidate. , и не продолжает создавать валидаторы, если свойство имеет значение null.

using System;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Validation.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Validation.Properties;

namespace Microsoft.Practices.EnterpriseLibrary.Validation.Validators
{
    /// <summary>
    /// Performs validation on objects by applying the validation rules specified for a supplied type at RUNTIME.
    /// This validator can be used to get past StackOverflowExceptions that can be thrown as a result of the design
    /// of the ObjectValidator attribute
    /// </summary>
    /// <seealso cref="ValidationFactory"/>
    public class RuntimeObjectValidator : Validator
    {
        private Type targetType;
        private string targetRuleset;

        /// <summary>
        /// <para>Initializes a new instance of the <see cref="RuntimeObjectValidator"/> for a target type.</para>
        /// </summary>
        /// <param name="targetType">The target type</param>
        /// <remarks>
        /// The default ruleset for <paramref name="targetType"/> will be used.
        /// </remarks>
        /// <exception cref="ArgumentNullException">when <paramref name="targetType"/> is <see langword="null"/>.</exception>
        public RuntimeObjectValidator(Type targetType)
            : this(targetType, string.Empty)
        { }

        /// <summary>
        /// <para>Initializes a new instance of the <see cref="RuntimeObjectValidator"/> for a target type
        /// using the supplied ruleset.</para>
        /// </summary>
        /// <param name="targetType">The target type</param>
        /// <param name="targetRuleset">The ruleset to use.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="targetType"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException">when <paramref name="targetRuleset"/> is <see langword="null"/>.</exception>
        public RuntimeObjectValidator(Type targetType, string targetRuleset)
            : base(null, null)
        {
            if (targetType == null)
            {
                throw new ArgumentNullException("targetType");
            }
            if (targetRuleset == null)
            {
                throw new ArgumentNullException("targetRuleset");
            }

            this.targetType = targetType;
            this.targetRuleset = targetRuleset;
        }

        /// <summary>
        /// Validates by applying the validation rules for the target type specified for the receiver.
        /// </summary>
        /// <param name="objectToValidate">The object to validate.</param>
        /// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>
        /// <param name="key">The key that identifies the source of <paramref name="objectToValidate"/>.</param>
        /// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>
        /// <remarks>
        /// If <paramref name="objectToValidate"/> is <see langword="null"/> validation is ignored.
        /// <para/>
        /// A referece to an instance of a type not compatible with the configured target type
        /// causes a validation failure.
        /// </remarks>
        protected internal override void DoValidate(object objectToValidate,
            object currentTarget,
            string key,
            ValidationResults validationResults)
        {
            if (objectToValidate != null)
            {
                if (this.targetType.IsAssignableFrom(objectToValidate.GetType()))
                {
                    validationResults.AddAllResults(
                        ValidationFactory.CreateValidator(objectToValidate.GetType()).Validate(objectToValidate));
                }
                else
                {
                    // unlikely
                    this.LogValidationResult(validationResults, Resources.ObjectValidatorInvalidTargetType, currentTarget, key);
                }
            }
        }

        /// <summary>
        /// Gets the message template to use when logging results no message is supplied.
        /// </summary>
        protected override string DefaultMessageTemplate
        {
            get { return null; }
        }

        #region test only properties

        internal Type TargetType
        {
            get { return this.targetType; }
        }

        internal string TargetRuleset
        {
            get { return this.targetRuleset; }
        }

        #endregion
    }
}

И, конечно же, вам также необходимо создать класс RuntimeObjectValidatorAttribute, чтобы вы могли сделать это:

public class AClassThatReferencesItself
    {
        private AClassThatReferencesItself _other;

        private string myString;

        [NotNullValidator]
        public string MyString
        {
            get { return myString; }
            set { myString = value; }
        }


        [RuntimeObjectValidator]
        [NotNullValidator]
        public AClassThatReferencesItself Other
        {
            get { return _other; }
            set { _other = value; }
        }

    }

Эта ошибка находится в системе отслеживания ошибок EntLib на codeplex. И я не уверен, что это легко исправить. И я не нашел хорошего обходного пути для этого :(.

Хитрость заключается в том, чтобы на самом деле не использовать какие - либо ObjectValidator и ObjectCollectionValidator атрибуты. Вы можете проверить все объекты самостоятельно. Это не всегда работает, но особенно хорошо работает в контексте сценариев O / RM, когда инфраструктура O / RM знает, какие объекты являются новыми или изменились.

Взгляните, например, на этот пример, где проверка запускается непосредственно перед отправкой изменений в базу данных с помощью Entity Framework:

public partial class NorthwindEntities
{
    partial void OnContextCreated()
    {
        // Adding validation support when saving.
        this.SavingChanges += (sender, e) =>
        {
            // Throws an exception when invalid.
            EntityValidator.Validate(
                this.GetChangedEntities());
        }
    }

    private IEnumerable<object> GetChangedEntities()
    {
        const EntityState AddedAndModified =
            EntityState.Added | EntityState.Modified;

        var entries = this.ObjectStateManager
            .GetObjectStateEntries(AddedAndModified);

        return
            from entry in entries
            where entry.Entity != null
            select entry.Entity;
    }
}

Кодовые крючки на ObjectContext «S SavingChanges события.

Это EntityValidator настраиваемый класс, который позволяет проверять группу объектов с помощью блока приложения проверки. Когда проверка завершается неудачно, генерируется кастом, ValidationException который обертывает ValidationResults коллекцию.

static class EntityValidator
{
    public static void Validate(IEnumerable<object> entities)
    {
        ValidationResults[] invalidResults = (
            from entity in entities
            let type = entity.GetType()
            let validator = ValidationFactory.CreateValidator(type)
            let results = validator.Validate(entity)
            where !results.IsValid
            select results).ToArray();

        // Throw an exception when there are invalid results.
        if (invalidResults.Length > 0)
        {
            throw new ValidationException(invalidResults);
        }
    }
}

Больше информации здесь .

Надеюсь, это поможет.