Проверка объекта на основе внешних факторов (т.е. Уникальность хранилища данных)
Описание
Мое решение имеет следующие проекты:
- 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", который позволяет выполнять сложные проверки.