Проверка объекта на основе внешних факторов (т.е. Уникальность хранилища данных)

Описание

Мое решение имеет следующие проекты:

  • DAL= Измененная структура сущностей
  • DTO= объекты передачи данных, которые могут проверять себя
  • BL= службы бизнес-уровня
  • WEB= презентация Приложение ASP.NET MVC

DAL, BL и WEB все ссылки DTO, которые великолепны.
Процесс обычно выполняется следующим образом:

  • Веб-запрос делается на WEB
  • WEB получает сообщения DTO
    • DTO получают автоматическую проверку через пользовательский ActionFilter
    • ошибки проверки автоматически собираются
  • (валидация в порядке) WEB-вызовы в BL, предоставляющие DTO
  • BL вызывает DAL с помощью DTO (может либо передать их, либо просто использовать)

Проблема проверки DTO, затем...

Мои DTO могут проверять себя на основе собственного состояния (значения свойств). Но сейчас я сталкиваюсь с проблемой, когда это не так. Мне нужно, чтобы они проверяли использование BL (и, следовательно, DAL).

Мой пример в реальной жизни: пользовательские регистры и WEB получают Пользовательский DTO, который проверяется. Проблематичной частью является проверка username. Его уникальность следует проверить против хранилища данных.
Как я должен это делать?

Имеется дополнительная информация о том, что все DTO реализуют интерфейс (т.е. User DTO реализует IUser) для целей IoC и TDD. Оба являются частью проекта DTO.

Невозможные попытки

  • Я не могу ссылаться на BL в DTO, потому что получаю круговую ссылку.
    Compilation error
  • Я не могу создать дополнительный проект DTO.Val, который будет ссылаться на частичные классы DTO и выполнять их проверку там (они будут ссылаться на BL + DTO).
    Partial classes can't span assemblies.

Возможные попытки

  • Создайте специальный ActionFilter, который будет проверять объект на внешние условия. Это будет создано в рамках WEB-проекта, таким образом, DTO и BL, которые будут использоваться здесь.
  • Поместите DTO в BL и сохраните интерфейсы DTO в качестве реальных DTO, на которые ссылаются другие проекты, и реорганизуйте весь код для использования интерфейсов вместо конкретных классов.
  • Не обрабатывайте внешнюю зависимую проверку и пусть внешние зависимости вызывают исключение - возможно, худшее решение этой проблемы

Что бы вы предложили?

Ответ 1

Результирующее решение

Я закончил использование фильтра действий контроллера, который смог проверить объект против внешних факторов, которые не могут быть получены из самого объекта.

Я создал фильтр, который берет имя параметра действия для проверки и типа валидатора, который будет проверять этот конкретный параметр. Конечно, этот валидатор должен реализовать определенный интерфейс, чтобы сделать его все повторно используемым.

[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)

валидатор должен реализовать этот простой интерфейс

public interface IExternalValidator<T>
{
    bool IsValid(T instance);
}

Это простое и эффективное решение по-видимому сложной проблемы.

Ответ 2

Я бы предложил эксперимент, который я тестировал только на прошлой неделе или около того.

Основываясь на это вдохновение, я создаю DTO, которые немного отличаются от подхода DataAnnotations. Пример DTO:

public class Contact : DomainBase, IModelObject
{
    public int ID { get; set; }
    public string Name { get; set; }
    public LazyList<ContactDetail> Details { get; set; }
    public DateTime Updated { get; set; }


    protected override void ConfigureRules()
    {
        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "name" },
            Description = "A Name is required but must not exceed 300 characters in length and some special characters are not allowed",
            validator = () => this.Name.IsRequired300LenNoSpecial()
        });

        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "updated" },
            Description = "required",
            validator = () => this.Updated.IsRequired()
        });
    }
}

Это может выглядеть более эффективно, чем DataAnnotations, и хорошо, что это так, но это не огромно. Я думаю, что это более презентабельно в классе (у меня есть некоторые действительно уродливые классы DTO теперь с атрибутами DataAnnotations - вы даже не можете видеть свойства больше). И сила анонимных делегатов в этом приложении почти достойна книги (так что я обнаруживаю).

Базовый класс:

public partial class DomainBase : IDataErrorInfo
{
    private IList<ValidationRule> _rules = new List<ValidationRule>();

    public DomainBase()
    {
        // populate the _rules collection
        this.ConfigureRules();
    }

    protected virtual void ConfigureRules()
    {
        // no rules if not overridden
    }

    protected void AddRule(ValidationRule rule)
    {
        this._rules.Add(rule);
    }





    #region IDataErrorInfo Members

    public string Error
    {
        get { return String.Empty; }    // Validation should call the indexer so return "" here
    }                                   // ..we dont need to support this property.

    public string this[string columnName]
    {
        get
        {
            // get all the rules that apply to the property being validated
            var rulesThatApply = this._rules
                .Where(r => r.Properties.Contains(columnName));

            // get a list of error messages from the rules
            StringBuilder errorMessages = new StringBuilder();
            foreach (ValidationRule rule in rulesThatApply)
                if (!rule.validator.Invoke())   // if validator returns false then the rule is broken
                    if (errorMessages.ToString() == String.Empty)
                        errorMessages.Append(rule.Description);
                    else
                        errorMessages.AppendFormat("\r\n{0}", rule.Description);

            return errorMessages.ToString();
        }
    }

    #endregion
}

ValidationRule и мои функции проверки:

public class ValidationRule
{
    public string[] Properties { get; set; }
    public string Description { get; set; }
    public Func<bool> validator { get; set; }
}


/// <summary>
/// These extention methods return true if the validation condition is met.
/// </summary>
public static class ValidationFunctions
{
    #region IsRequired

    public static bool IsRequired(this String str)
    {
        return !str.IsNullOrTrimEmpty();
    }

    public static bool IsRequired(this int num)
    {
        return num != 0;
    }

    public static bool IsRequired(this long num)
    {
        return num != 0;
    }

    public static bool IsRequired(this double num)
    {
        return num != 0;
    }

    public static bool IsRequired(this Decimal num)
    {
        return num != 0;
    }

    public static bool IsRequired(this DateTime date)
    {
        return date != DateTime.MinValue;
    }

    #endregion


    #region String Lengths

    public static bool IsLengthLessThanOrEqual(this String str, int length)
    {
        return str.Length <= length;
    }

    public static bool IsRequiredWithLengthLessThanOrEqual(this String str, int length)
    {
        return !str.IsNullOrTrimEmpty() && (str.Length <= length);
    }

    public static bool IsRequired300LenNoSpecial(this String str)
    {
        return !str.IsNullOrTrimEmpty() &&
            str.RegexMatch(@"^[- \r\n\\\.!:*,@$%&""?\(\)\w']{1,300}$",
                RegexOptions.Multiline) == str;
    }

    #endregion

}

Если мой код выглядит беспорядочно, потому что я работал над этим подходом к проверке в течение последних нескольких дней. Мне нужна эта идея для удовлетворения нескольких требований:

  • Мне нужно поддерживать интерфейс IDataErrorInfo, чтобы мой MVC-уровень автоматически проверял
  • Мне нужно иметь возможность поддерживать сложные сценарии проверки (весь вопрос вашего вопроса, который я предполагаю): я хочу иметь возможность проверять несколько свойств на одном объекте (например, StartDate и FinishDate); свойства из разных/множественных/связанных объектов, подобных i, в графе объектов; и даже другие вещи, о которых я еще не думал.
  • Мне нужно поддержать идею ошибки, применяемой к нескольким свойствам
  • Как часть моих путешествий по TDD и DDD, я хочу, чтобы мои объекты домена описывали больше моего "домена", чем мои методы уровня сервиса, поэтому, по-видимому, достижение этих сложных условий в объектах модели (а не DTO) достигает этого.

Этот подход, я думаю, получит меня, что я хочу, и, возможно, вы тоже.

Я бы предположил, что если вы скажете со мной на борту, что мы будем "сами", но это может стоить того. Я читал о новых возможностях проверки в MVC 2, но он по-прежнему не соответствует указанному списку пожеланий без пользовательской модификации.

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

Ответ 3

Архитектура S # arp имеет идентификатор метода [DomainSignature], который используется с валидатором уровня [HasUniqueDomainSignature], будет выполнять эту работу. См. Пример кода ниже:

[HasUniqueDomainSignature]
public class User : Entity
{
    public User()
    {
    }

    public User(string login, string email) : this()
    {
        Login = login;
        Email = email;
    }

    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Login { get; set; }

    [DomainSignature]
    public virtual string Email { get; set; }

}

Посмотрите более подробно на http://www.sharparchitecture.net/

Ответ 4

У меня была такая же проблема, и, пытаясь найти работу в течение нескольких дней и дней и дней, я закончил слияние своих DTO, DAL и BL в одну библиотеку. Я сохранил свой слой презентации отдельно. Не уверен, что это вариант для вас или нет. Для меня я решил, что мои шансы когда-либо изменить хранилище данных были очень незначительными, и поэтому отдельный уровень не был действительно нужен.

Я также внедрил блок приложений Microsoft Validation для всех моих проверок DTO. У них есть метод "Self Validation", который позволяет выполнять сложные проверки.