Проблемы с моим шаблоном репозитория MVC и StructureMap
Автор вопроса: CommunityОтветов: 3
У меня есть шаблон репозитория, который я создал поверх структуры сущностей ado.net. Когда я пытался реализовать StructureMap для разделения моих объектов, я продолжал получать StackOverflowException (бесконечный цикл?). Вот как выглядит узор:
IEntityRepository, где TEntity: class Определяет основные элементы CRUD
MyEntityRepository: IEntityRepository реализует элементы CRUD
IEntityService, где TEntity: class Определяет элементы CRUD, которые возвращают общие типы для каждого члена.
MyEntityService: IEntityService Использует репозиторий для извлечения данных и возврата в результате общего типа (IList, bool и т. Д.)
Проблема, похоже, связана с моим уровнем обслуживания. Точнее с конструкторами.
public PostService(IValidationDictionary validationDictionary)
: this(validationDictionary, new PostRepository())
{ }
public PostService(IValidationDictionary validationDictionary, IEntityRepository<Post> repository)
{
_validationDictionary = validationDictionary;
_repository = repository;
}
Из контроллера я передаю объект, реализующий IValidationDictionary. И я явно вызываю второй конструктор для инициализации репозитория.
Так выглядят конструкторы контроллера (первый создает экземпляр объекта проверки):
public PostController()
{
_service = new PostService(new ModelStateWrapper(this.ModelState));
}
public PostController(IEntityService<Post> service)
{
_service = service;
}
Все работает, если я не передаю ссылку на объект IValidationDictionary, и в этом случае первый конструктор контроллера будет удален, а объект службы будет иметь только один конструктор, который принимает интерфейс репозитория в качестве параметра.
Похоже, что циклическая ссылка была связана с тем фактом, что уровень сервиса зависел от ModelState контроллера, а контроллер зависел от уровня сервиса.
Мне пришлось переписать свой уровень проверки, чтобы это заработало. Вот что я сделал.
Определите общий интерфейс валидатора, как показано ниже:
public interface IValidator<TEntity>
{
ValidationState Validate(TEntity entity);
}
Мы хотим иметь возможность возвращать экземпляр ValidationState, который, очевидно, определяет состояние проверки.
public class ValidationState
{
private readonly ValidationErrorCollection _errors;
public ValidationErrorCollection Errors
{
get
{
return _errors;
}
}
public bool IsValid
{
get
{
return Errors.Count == 0;
}
}
public ValidationState()
{
_errors = new ValidationErrorCollection();
}
}
Обратите внимание, что у нас есть строго типизированная коллекция ошибок, которую нам также необходимо определить. Коллекция будет состоять из объектов ValidationError, содержащих имя свойства объекта, который мы проверяем, и связанное с ним сообщение об ошибке. Это просто соответствует стандартному интерфейсу ModelState.
public class ValidationErrorCollection : Collection<ValidationError>
{
public void Add(string property, string message)
{
Add(new ValidationError(property, message));
}
}
А вот как выглядит ValidationError:
public class ValidationError
{
private string _property;
private string _message;
public string Property
{
get
{
return _property;
}
private set
{
_property = value;
}
}
public string Message
{
get
{
return _message;
}
private set
{
_message = value;
}
}
public ValidationError(string property, string message)
{
Property = property;
Message = message;
}
}
Остальное - магия StructureMap. Нам нужно создать уровень службы проверки, который будет находить объекты проверки и проверять нашу сущность. Я хотел бы определить интерфейс для этого, так как я хочу, чтобы любой, кто использует службу проверки, полностью не знал о наличии StructureMap. Кроме того, я считаю плохой идеей разбрызгивать ObjectFactory.GetInstance () где угодно, кроме логики загрузчика. Централизация - хороший способ обеспечить хорошую ремонтопригодность. В любом случае, я использую здесь шаблон декоратора:
public interface IValidationService
{
ValidationState Validate<TEntity>(TEntity entity);
}
И наконец реализуем это:
public class ValidationService : IValidationService
{
#region IValidationService Members
public IValidator<TEntity> GetValidatorFor<TEntity>(TEntity entity)
{
return ObjectFactory.GetInstance<IValidator<TEntity>>();
}
public ValidationState Validate<TEntity>(TEntity entity)
{
IValidator<TEntity> validator = GetValidatorFor(entity);
if (validator == null)
{
throw new Exception("Cannot locate validator");
}
return validator.Validate(entity);
}
#endregion
}
Я собираюсь использовать службу проверки в своем контроллере. Мы могли бы переместить его на уровень сервиса и использовать инъекцию свойства StructureMap для внедрения экземпляра ModelState контроллера в уровень сервиса, но я не хочу, чтобы уровень сервиса был связан с ModelState. Что, если мы решим использовать другой метод проверки? Вот почему я бы предпочел поместить его в контроллер. Вот как выглядит мой контроллер:
public class PostController : Controller
{
private IEntityService<Post> _service = null;
private IValidationService _validationService = null;
public PostController(IEntityService<Post> service, IValidationService validationService)
{
_service = service;
_validationService = validationService;
}
}
Здесь я внедряю свой уровень обслуживания и экземпляры службы проверки с помощью StructureMap. Итак, нам нужно зарегистрировать оба в реестре StructureMap:
Вот и все. Я не показываю, как я реализую свой PostValidator, но он просто реализует интерфейс IValidator и определяет логику проверки в методе Validate (). Все, что осталось сделать, - это вызвать экземпляр службы проверки, чтобы получить средство проверки, вызвать метод проверки объекта и записать все ошибки в ModelState.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "PostId")] Post post)
{
ValidationState vst = _validationService.Validate<Post>(post);
if (!vst.IsValid)
{
foreach (ValidationError error in vst.Errors)
{
this.ModelState.AddModelError(error.Property, error.Message);
}
return View(post);
}
...
}
Надеюсь, я помог кому-то с этим :)
Ответ 2 · рейтинг 1 · cedd
Я использовал аналогичное решение с использованием универсального разработчика IValidationDictionary, использующего StringDictionary, а затем скопировал ошибки из него обратно в состояние модели в контроллере.
Реализация словаря проверки без ссылки на состояние модели или что-либо еще, чтобы карта структуры могла легко его создать.
public class ValidationDictionary : IValidationDictionary
{
private StringDictionary _errors = new StringDictionary();
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_errors.Add(key, errorMessage);
}
public bool IsValid
{
get { return (_errors.Count == 0); }
}
public StringDictionary errors
{
get { return _errors; }
}
#endregion
}
Код в контроллере для копирования ошибок из словаря в состояние модели. Это, вероятно, было бы лучше всего в качестве функции расширения контроллера.
protected void copyValidationDictionaryToModelState()
{
// this copies the errors into viewstate
foreach (DictionaryEntry error in _service.validationdictionary.errors)
{
ModelState.AddModelError((string)error.Key, (string)error.Value);
}
}
таким образом, начальный код выглядит так
public static void BootstrapStructureMap()
{
// Initialize the static ObjectFactory container
ObjectFactory.Initialize(x =>
{
x.For<IContactRepository>().Use<EntityContactManagerRepository>();
x.For<IValidationDictionary>().Use<ValidationDictionary>();
x.For<IContactManagerService>().Use<ContactManagerService>();
});
}
и код для создания контроллеров выглядит так
public class IocControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return (Controller)ObjectFactory.GetInstance(controllerType);
}
}
Ответ 3 · Community
Просто быстрый вопрос по этому поводу. Это мне очень помогло, поэтому спасибо за ответ, но мне было интересно, в каком пространстве имен существует TEntity? Я вижу, что Colletion (TEntity) требует System.Collections.ObjectModel. Мой файл компилируется без каких-либо дополнительных действий, но я вижу, что ваша ссылка на TEntity выделена синим цветом, что говорит о том, что он имеет тип класса, мой - черный в Visual Studio. Надеюсь, ты сможешь помочь. Я очень хочу, чтобы это работало.
Вы вообще нашли способ разделить валидацию на уровне сервиса? Моя интуиция подсказывает мне, что проверка в контроллере - это немного неприятно, но я внимательно посмотрел, чтобы найти способ передать сообщения об ошибках проверки обратно в контроллер, не привязывая уровень сервиса к контроллеру и не могу ничего найти. :(