在ASP.NET MVC ContactsManager教程中是否有一种很好/正确的方法来解决依赖注入循环问题?

时间:2009-09-21 06:40:55

标签: asp.net-mvc dependency-injection inversion-of-control unity-container

如果您不知道我在说什么,请通过the tutorial并尝试自己添加依赖注入,或者试试我对该问题的解释。

注意:此问题不在ASP.NET原始教程的范围内。本教程仅建议使用的模式是依赖注入友好的。

问题基本上是Controller,ModelStateWrapper和ContactManagerService之间存在依赖循环。

  1. ContactController构造器采用IContactManagerService。
  2. ContactManagerService构造函数采用IContactManagerRepository (不重要)和IValidationDictionary (ModelStateWrapper实现)
  3. ModelStateWrapper构造函数采用ModelStateDictionary (控制器上名为“ModelState”的属性)
  4. 所以依赖循环是这样的:控制器>服务> ModelStateWrapper>控制器

    如果您尝试向此添加依赖项注入,它将失败。所以我的问题是;我该怎么办呢?其他人已经发布了这个问题,但答案很少,不同,所有看起来都有点像“黑客”。

    我目前的解决方案是从IService构造函数中删除IModelStateWrapper并添加一个Initialize方法,如下所示:

    public class ContactController : Controller
    {
        private readonly IContactService _contactService;
    
        public ContactController(IContactService contactService)
        {
            _contactService = contactService;
            contactService.Initialize(new ModelStateWrapper(ModelState));
        }
    
        //Class implementation...
    }
    
    public class ContactService : IContactService
    {
        private IValidationDictionary _validationDictionary;
        private readonly IContactRepository _contactRepository;
    
        public ContactService(IContactRepository contactRepository)
        {
            _contactRepository = contactRepository;
        }
    
        private void Initialize(IValidationDictionary validationDictionary)
        {
            if(validationDictionary == null)
                throw new ArgumentNullException("validationDictionary");
    
            _validationDictionary = validationDictionary;
        }
    
        //Class implementation...
    }
    
    public class ModelStateWrapper : IValidationDictionary
    {
        private readonly ModelStateDictionary _modelState;
    
        public ModelStateWrapper(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }
    
        //Class implementation...
    }
    

    使用这个结构,我可以像这样配置我的统一容器:

    public static void ConfigureUnityContainer()
    {
        IUnityContainer container = new UnityContainer();
    
        // Registrations
        container.RegisterTypeInHttpRequestLifetime<IContactRepository, EntityContactRepository>();
        container.RegisterTypeInHttpRequestLifetime<IContactService, ContactService>();
    
        ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container));
    }
    

    不幸的是,这意味着服务上的“Initialize”方法必须由控制器构造函数手动调用 。有没有更好的办法?也许我在我的统一配置中以某种方式包含IValidationDictionary?我应该切换到另一个DI容器吗?我错过了什么吗?

3 个答案:

答案 0 :(得分:11)

作为一般考虑,循环依赖表示设计缺陷 - 我想我可以安全地说这个,因为你不是代码的原作者:)

我不认为Initialize方法是一个很好的解决方案。除非您正在处理加载项方案(您不是),否则Method Injection不是正确的解决方案。你几乎已经想到了这一点,因为你发现你需要手动调用它是不能令人满意的,因为你的DI容器不能。

除非我完全弄错,否则在调用Action方法之前,ContactController不需要IValidationDictionary实例?

如果这是真的,那么最简单的解决方案可能是定义一个IValidationDictionaryFactory接口并使ContactController构造函数接受这个接口的实例。

此界面可以这样定义:

public interface IValidationDictionaryFactory
{
    IValidationDictionary Create(Controller controller);
}

控制器上需要IValidationDictionary实例的任何Action方法都可以调用Create方法来获取实例。

默认实现看起来像这样:

public class DefaultValidationDictionaryFactory : IValidationDictionaryFactory
{
    public IValidationDictionary Create(Controller controller)
    {
        return controller.ModelState;
    }
}

答案 1 :(得分:2)

如何将设计稍微改变/改进为:http://forums.asp.net/t/1486130.aspx

答案 2 :(得分:1)

每个控制器都有一个虚拟方法 Initialize 来做类似的事情。

我认为没有更好的方法,因为IValidationDictionary是当前请求/ controller / modelstate和IContactService之间的抽象层。将控制器模型状态注入服务然后使用构造函数注入将服务注入控制器是不可能的。一个必须是第一个。

可能有一种方法使用属性注入?但我认为这也会很复杂。