【问题标题】:Issues with my MVC repository pattern and StructureMap我的 MVC 存储库模式和 StructureMap 的问题
【发布时间】:2010-10-28 15:58:24
【问题描述】:

我在 ado.net 实体框架之上创建了一个存储库模式。当我尝试实现 StructureMap 来解耦我的对象时,我不断收到 StackOverflowException(无限循环?)。这是模式的样子:

IEntityRepository where 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 对象引用,一切正常,在这种情况下,第一个控制器构造函数将被删除,并且服务对象将只有一个接受存储库接口作为参数的构造函数。

感谢您对此的任何帮助 :) 谢谢。

【问题讨论】:

    标签: model-view-controller structuremap repository-pattern


    【解决方案1】:

    看起来循环引用与服务层依赖于控制器的 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);
            }
    
            ...
        }
    

    希望我能帮助别人解决这个问题 :)

    【讨论】:

      【解决方案2】:

      对此进行快速查询。它对我帮助很大,所以感谢您提出答案,但我想知道 TEntity 存在于哪个命名空间中?我看到Colletion(TEntity)需要System.Collections.ObjectModel。我的文件无需进一步编译,但我看到您的 TEntity 引用以蓝色突出显示,这表明它具有类类型,我的在 Visual Studio 中是黑色。希望你能帮忙。我非常渴望得到这个工作。

      您有没有找到任何方法将验证分离到服务层?我的直觉告诉我,在控制器中进行验证有点臭,但我一直在寻找一种方法来将验证错误消息传递回控制器,而无需将服务层与控制器紧密耦合,并且找不到任何东西。 :(

      再次感谢您的精彩帖子!

      劳埃德

      【讨论】:

      • StackOverflow 上的语法着色有点偏离 :) TEntity 是泛型类型,而不是类对象。它在 Visual Studio 中只是黑色的。我使用 TEntity 来简单地传递我想要验证的对象类型。我同意你在控制器中的验证。我找不到任何更好的方法来验证这会给我带来很大的灵活性。这样做我可以在我的实体验证器中使用我想要的任何验证框架。
      • 现在,我并没有考虑太多,但是可以使用适配器模式为 ModelState 编写适配器。因此,基本上您将验证您的实体,获取 ValidationState 对象并使用适配器将其转换为 ModelState。如果你足够抽象它,你可以使用 StructureMap 来注入你选择的适配器。我不确定它与你的控制器的配合程度如何,但我相信你可以很好地解耦它。这样,您的验证服务可能会在您的业务模型/实体或服务层中使用。我看看能不能用这个做点什么。
      • 我正在关注这个,asp.net/Learn/mvc/tutorial-38-cs.aspx。最初我不考虑这个选项,因为我认为它会将我的服务层与 MVC 耦合得太紧,但现在,为了让我的项目继续进行,它看起来像是一堆坏东西中最好的。不过,我非常渴望找到一个更宽松的替代方案。如果您有任何想法,请在 xtra dot co dot nz 给我发一封电子邮件至劳埃德菲利普斯。劳埃德问候
      【解决方案3】:

      我使用了一个类似的解决方案,涉及 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
      }
      

      控制器中的代码将错误从字典复制到模型状态。这可能最好作为Controller的扩展功能。

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

      【讨论】:

        猜你喜欢
        • 2010-10-30
        • 1970-01-01
        • 2011-06-28
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多