Проверка объекта на основе внешних факторов (т.е.уникальность хранилища данных)
-
19-09-2019 - |
Вопрос
Описание
В моем решении есть следующие проекты:
- DAL = Модифицированная структура сущностей
- DTO = Объекты передачи данных, способные проверять себя
- БЛ = Сервисы бизнес-уровня
- ПАУТИНА = презентация Asp.net Приложение MVC
DAL, BL и WEB ссылаются на DTO, и это здорово.
Процесс обычно выполняется таким образом:
- Веб-запрос отправляется в ИНТЕРНЕТ
- В ИНТЕРНЕТЕ публикуются DTO
- DTO автоматически проверяются с помощью пользовательского ActionFilter
- ошибки проверки собираются автоматически
- (Проверка в порядке) ВЕБ-вызовы в BL, предоставляющие DTO
- BL вызывает DAL с помощью DTO (может либо передавать их, либо просто использовать)
Тогда проблема с проверкой DTO...
Мои DTO могут проверять себя на основе своего собственного состояния (значений свойств).Но прямо сейчас я столкнулся с проблемой, когда это не так.Мне нужно, чтобы они проверялись с помощью BL (и, следовательно, DAL).
Мой пример из реальной жизни:Пользователь регистрируется, и WEB получает пользовательский DTO, который проходит проверку.Проблемная часть заключается в следующем username
проверка подлинности.Его уникальность должна быть проверена на соответствие хранилищу данных.
Как я должен это сделать?
Есть дополнительная информация о том, что все DTO реализуют интерфейс (т.е. User
Инструменты DTO IUser
) для целей МоК и TDD.И то, и другое является частью Проект DTO.
Невозможные попытки
- Я не могу ссылаться на BL в DTO, потому что я получу циклическую ссылку.
Compilation error
- Я не могу создать дополнительный проект DTO.Val, который ссылался бы на частичные классы DTO и реализовывал бы там их проверку (они ссылались бы на BL + DTO).
Partial classes can't span assemblies.
Возможные попытки
- Создайте особый
ActionFilter
это позволило бы проверить объект на соответствие внешним условиям.Этот был бы создан внутри ВЕБ-проект таким образом, видя DTO и BL, которые будут использоваться здесь. - Поместите DTO в BL и сохраните интерфейсы DTO как фактические DTO, на которые ссылаются другие проекты, и реорганизуйте весь код, чтобы использовать интерфейсы вместо конкретных классов.
- Не обрабатывайте внешнюю зависимую проверку и позвольте внешним зависимостям выдавать исключение - вероятно самое худшее решение этой проблемы
Что бы вы предложили?
Решение 4
Результирующее решение
В итоге я использовал фильтр действий контроллера, который смог проверить объект на соответствие внешним факторам, которые не могут быть получены из самого объекта.
Я создал фильтр, который принимает имя параметра действия для проверки и тип валидатора, который будет проверять этот конкретный параметр.Конечно, этот валидатор должен реализовать определенный интерфейс, чтобы сделать все это многоразовым.
[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)
валидатор должен реализовать этот простой интерфейс
public interface IExternalValidator<T>
{
bool IsValid(T instance);
}
Это простое и эффективное решение, казалось бы, сложной проблемы.
Другие советы
Я бы предложил эксперимент, который я опробовал только на прошлой неделе или около того.
Основанный на это вдохновение я создаю 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 проверяется автоматически - Мне нужно иметь возможность поддерживать сложные сценарии проверки (я думаю, в этом весь смысл вашего вопроса):Я хочу иметь возможность проверять несколько свойств одного и того же объекта (т.е.Дата начала и дата завершения);свойства из разных / нескольких / связанных объектов, как у меня было бы в графе объектов;и даже о других вещах, о которых я еще не подумал.
- Мне нужно поддержать идею об ошибке, применимой к нескольким свойствам
- В рамках моего путешествия по TDD и DDD я хочу, чтобы объекты моего домена больше описывали мой "домен", чем мои методы уровня обслуживания, поэтому включение этих сложных условий в объекты модели (не DTO), по-видимому, позволяет достичь этого
Я думаю, такой подход даст мне то, что я хочу, и, возможно, вам тоже.
Я бы предположил, что если ты согласишься со мной в этом вопросе, мы будем довольно "одиноки", но, возможно, оно того стоит.Я читал о новые возможности проверки в MVC 2 но он по-прежнему не соответствует приведенному выше списку пожеланий без пользовательских изменений.
Надеюсь, это поможет.
Архитектура 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/
У меня была точно такая же проблема, и после долгих попыток найти обходной путь в течение нескольких дней я в итоге объединил свои DTO, DAL и BL в одну библиотеку.Я сохранил свой уровень презентации отдельно.Не уверен, подходит ли вам такой вариант или нет.Что касается меня, то я полагал, что мои шансы когда-либо сменить хранилище данных были очень невелики, и поэтому отдельный уровень на самом деле не был нужен.
Я также внедрил блок приложений Microsoft Validation Application для всех моих проверок DTO.У них есть метод "Самоконтроля", который позволяет выполнять сложные проверки.