0

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

IEntityRepository where TEntity : class 定义基本的 CRUD 成员

MyEntityRepository : IEntityRepository 实现 CRUD 成员

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

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

4

3 回答 3

8

看起来循环引用与服务层依赖于控制器的模型状态和控制器依赖于服务层的事实有关。

我必须重写我的验证层才能让它工作。这就是我所做的。

定义通用验证器接口,如下所示:

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

        ...
    }

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

于 2009-05-29T01:38:38.777 回答
1

我使用了一个类似的解决方案,其中涉及 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-12-19T19:52:46.610 回答
0

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

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

再次感谢您的精彩帖子!

劳埃德

于 2009-07-31T05:49:06.660 回答