Проблемы с моим шаблоном репозитория MVC и StructureMap

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

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


person Community    schedule 25.05.2009    source источник


Ответы (3)


Похоже, что циклическая ссылка была связана с тем фактом, что уровень сервиса зависел от 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 (). Все, что осталось сделать, - это вызвать экземпляр службы проверки, чтобы получить средство проверки, вызвать метод проверки объекта и записать все ошибки в 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);
        }

        ...
    }

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

person Community    schedule 29.05.2009

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

Интерфейс для валидации словаря

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

Реализация словаря проверки без ссылки на состояние модели или что-либо еще, чтобы карта структуры могла легко его создать.

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);
    }
}
person cedd    schedule 19.12.2010

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

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

Еще раз спасибо за отличный пост!

Ллойд

person Community    schedule 31.07.2009
comment
Цвет синтаксиса в StackOverflow немного отключен :) TEntity - это общий тип, а не объект класса. В Visual Studio он просто черный. Я использую TEntity, чтобы просто передать тип объекта, который я хочу проверить. Я согласен с вами относительно проверки в контроллере. Я не мог найти лучшего способа проверки, который дал бы мне хорошую гибкость. Делая это таким образом, я могу использовать любую структуру проверки, которую я хочу, в моем валидаторе сущностей. - person ; 31.07.2009
comment
Я не особо задумывался об этом, но что могло бы сработать, так это использование шаблона адаптера для написания адаптера для ModelState. По сути, вы должны проверить свою сущность, получить объект ValidationState и использовать адаптер для преобразования его в ModelState. Если вы достаточно абстрагируетесь, вы можете использовать StructureMap для внедрения адаптера по вашему выбору. Я не уверен, насколько хорошо он будет связан с вашим контроллером, но я уверен, что вы могли бы довольно хорошо отделить его. Таким образом, ваша служба проверки, вероятно, может использоваться в ваших бизнес-моделях / объектах или на уровне обслуживания. Я посмотрю, смогу ли я что-нибудь с этим поделать. - person ; 31.07.2009
comment
Я смотрел на это, asp.net/Learn/mvc/tutorial -38-cs.aspx. Первоначально я отказался от этого варианта, так как считал, что он будет слишком тесно связывать мой уровень обслуживания с MVC, но на данный момент, ради продвижения моего проекта, это выглядит как лучшее из плохой группы. Однако я очень хочу найти более свободную альтернативу. Если вы что-нибудь придумаете, напишите мне на электронную почту lloyd phillips all one word at xtra dot co dot nz. С уважением, Ллойд - person ; 03.08.2009