Проверка объекта на основе внешних факторов (т. Е. Уникальности хранилища данных) - PullRequest
10 голосов
/ 12 ноября 2009

Описание

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

  • DAL = Модифицированная платформа Entity Framework
  • DTO = Объекты передачи данных, которые могут проверить себя
  • BL = Услуги бизнес-уровня
  • WEB = презентация приложения Asp.net MVC

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

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

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

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

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

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

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

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

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

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

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

Ответы [ 4 ]

6 голосов
/ 16 ноября 2009

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

Исходя из этого вдохновения я создаю 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); свойства из разных / нескольких / связанных объектов, которые я бы имел в графе объектов; и даже о других вещах, о которых я еще не думал.
  • Мне нужно поддержать идею ошибки, применяемой к более чем одному свойству
  • В рамках моего путешествия по TDD и DDD я хочу, чтобы мои доменные объекты описывали больше моего «домена», чем методы уровня моего сервиса, поэтому введение этих сложных условий в объекты модели (а не в DTO), кажется, достигает этого

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

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

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

2 голосов
/ 02 декабря 2009

Архитектура 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/

1 голос
/ 04 мая 2011

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

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

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

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

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

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

Это простое и эффективное решение, казалось бы, сложной проблемы.

1 голос
/ 08 декабря 2009

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

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

...