0

У меня есть шаблон репозитория, который я создал поверх структуры сущности ado.net. Когда я попытался реализовать StructureMap, чтобы отделить мои объекты, я продолжал получать StackOverflowException (бесконечный цикл?). Вот как выглядит картина, как:Проблемы с моим шаблоном репозитория MVC и StructureMap

IEntityRepository где TEntity: класс Определяет основные элементы CRUD

MyEntityRepository: IEntityRepository Реализует членов Crud

IEntityService где TEntity: класс Определяет пользователей 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 ссылки на объект, в этом случае первого конструктор контроллера будет удален, и объект службы будет иметь только один конструктор, который принимает интерфейс репозитория в качестве параметра.

Я ценю любую помощь с этим :) Спасибо.

ответ

8

Похоже, что круговая ссылка связана с тем, что сервисный уровень зависел от 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; 
    } 
} 

Здесь я инъекционный мой уровень сервиса и экземпляры validaton услуг с использованием StructureMap. Поэтому нам необходимо зарегистрироваться как в реестре StructureMap:

ForRequestedType<IValidationService>() 
     .TheDefaultIsConcreteType<ValidationService>(); 

    ForRequestedType<IValidator<Post>>() 
      .TheDefaultIsConcreteType<PostValidator>(); 

Всё. Я не показываю, как я реализую свой 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); 
     } 

     ... 
    } 

Надежда я помог кому-то с этим :)

0

Просто быстро запроса об этом. Это очень помогло мне, так что спасибо за ответ, но я подумал, в каком пространстве имен существует TEntity? Я вижу, что Colletion (TEntity) нуждается в System.Collections.ObjectModel. Мой файл компилируется без каких-либо дополнительных действий, но я вижу, что ссылка на TEntity выделена синим цветом, что указывает на то, что у него тип класса, у меня черный в Visual Studio. Надеюсь, ты поможешь. Я очень хочу, чтобы это работало.

Вы нашли какой-либо способ разделить валидацию на сервисный уровень? Моя кишка говорит мне, что проверка в контроллере немного вонючая, но я выглядел высоко и низко, чтобы найти способ передать сообщения об ошибках проверки на контроллер без жесткой привязки уровня сервиса к контроллеру и ничего не найти. :(

Опять же, спасибо за большой пост!

Lloyd

+0

с синтаксисом окраски немного не на StackOverflow :) TEntity это общий тип , а не объект класса. Он просто черный в Visual Studio. Я использую TEntity, чтобы просто передать тип объекта, который я хочу проверить. Я согласен с вами при проверке в контроллере. Я не мог найти лучшего способа проверить, что даст мне большую гибкость. Выполняя это таким образом, я могу использовать любую фреймворк проверки, который я хочу, в моем валидаторе объектов. – 2009-07-31 18:11:37

+0

Теперь я не думал об этом, но что может работать с использованием шаблона адаптера для написания адаптера для ModelState. Таким образом, вы бы подтвердили свою сущность, получили объект ValidationState и использовали адаптер для преобразования его в ModelState. Если вы достаточно абстрактны, вы можете использовать StructureMap для инъекции адаптера по вашему выбору. Я не уверен, насколько хорошо он будет связан с вашим контроллером, но я уверен, что вы могли бы разделить его довольно красиво. Таким образом, ваша служба проверки может, вероятно, использоваться в ваших бизнес-моделях/сущностях или уровне обслуживания. Я посмотрю, смогу ли я с этим что-то сделать. – 2009-07-31 18:12:18

+0

Я смотрел после этого, http://www.asp.net/Learn/mvc/tutorial-38-cs.aspx. Первоначально я отклонил этот вариант, так как считал, что он будет слишком сильно связывать мой сервисный уровень с MVC, но в тот момент, ради того, чтобы мой проект продвигался, он выглядит как лучший из плохой группы. Однако я очень хочу найти более свободную альтернативу. Если вы придумаете что-нибудь, напишите мне письмо на lloyd phillips на одно слово в xtra dot co dot nz. С уважением Lloyd – 2009-08-02 22:32:22

1

я использовал аналогичное решение с участием родового реализатор IValidationDictionary использует StringDictionary, а затем копируется ошибки из этого обратно в модельном состояние в контроллер.

Интерфейс для validationdictionary

public interface IValidationDictionary 
    { 
     bool IsValid{get;} 
     void AddError(string Key, string errorMessage); 
     StringDictionary errors { get; } 
    } 

Осуществление проверки Dictionar у без ссылки моделировать состояние или что-нибудь еще так StructureMap может создать его легко

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); 
    } 
}