Проблемы с моим шаблоном хранилища MVC и StructureMap - PullRequest
0 голосов
/ 25 мая 2009

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

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, в этом случае первый конструктор контроллера будет удален, а у объекта службы будет только один конструктор, который принимает интерфейс хранилища в качестве параметра.

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

Ответы [ 3 ]

8 голосов
/ 29 мая 2009

Похоже, что циклическая ссылка была связана с тем фактом, что уровень сервиса зависел от 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:

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

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

Вот и все. Я не показываю, как я реализую свой PostValidator, но он просто реализует интерфейс IValidator и определяет логику валидации в методе Validate (). Все, что осталось сделать, - это вызвать экземпляр службы валидации для получения валидатора, вызвать метод 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);
        }

        ...
    }

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

1 голос
/ 19 декабря 2010

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

Интерфейс для проверки правильности

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

Реализация словаря проверки без ссылки на состояние модели или чего-либо еще, так что 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);
    }
}
0 голосов
/ 31 июля 2009

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

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

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

Lloyd

...