【问题标题】:Dependency Injection for PresenterPresenter 的依赖注入
【发布时间】:2009-12-18 05:19:24
【问题描述】:

我有一个 Presenter,它在其构造函数中将 Service 和 View Contract 作为参数:

public FooPresenter : IFooPresenter {
    private IFooView view;
    private readonly IFooService service;

    public FooPresenter(IFooView view, IFooService service) {
        this.view = view;
        this.service = service;
    }
}

我使用 Autofac 解决我的服务:

private ContainerProvider BuildDependencies() {
    var builder = new ContainerBuilder();
    builder.Register<FooService>().As<IFooService>().FactoryScoped();  

    return new ContainerProvider(builder.Build());  
}

在我的 ASPX 页面中(查看实现):

public partial class Foo : Page, IFooView {
    private FooPresenter presenter;

    public Foo() {
        // this is straightforward but not really ideal
        // (IoCResolve is a holder for how I hit the container in global.asax)
        this.presenter = new FooPresenter(this, IoCResolve<IFooService>());

        // I would rather have an interface IFooPresenter so I can do
        this.presenter = IoCResolve<IFooPresenter>();
        // this allows me to add more services as needed without having to 
        // come back and manually update this constructor call here
    }
}

问题是 FooPresenter 的构造函数需要特定的页面,而不是容器创建一个新页面。

我能否仅针对此分辨率向容器提供视图的特定实例(当前页面)?这样做有意义吗,还是我应该以另一种方式这样做?

【问题讨论】:

    标签: c# dependency-injection inversion-of-control autofac mvp


    【解决方案1】:

    解决在 Autofac 中解析依赖项时传递我喜欢调用的 data 参数的方法是使用生成的工厂

    (更新:this question 讨论了同样的问题,my article 展示了如何避免大量的工厂委托)。

    您的问题的解决方案如下所示:

    首先,声明一个接受数据参数的工厂委托:

    public delegate IFooPresenter FooPresenterFactory(IFooView view);
    

    您的演示者保持不变:

    public FooPresenter : IFooPresenter {
        private IFooView view;
        private readonly IFooService service;
    
        public FooPresenter(IFooView view, IFooService service) {
            this.view = view;
            this.service = service;
        }
    }
    

    接下来是 Autofac 容器设置:

    var builder = new ContainerBuilder();
    builder.Register<FooService>().As<IFooService>().FactoryScoped();  
    builder.Register<FooPresenter>().As<IFooPresenter>().FactoryScoped();  
    builder.RegisterGeneratedFactory<FooPresenterFactory>();
    

    现在在您的页面中,您可以在两行代码中首先获取工厂,然后调用工厂为您解析演示者:

    public partial class Foo : Page, IFooView {
        private FooPresenter presenter;
    
        public Foo() {
            var factory = IoCResolve<FooPresenterFactory>();
            this.presenter = factory(this);
        }
    }
    

    【讨论】:

    • 优秀的解决方案。感谢您指出。直到现在我才真正掌握生成工厂。
    • 我正在研究如何在抽象层面上做到这一点。我当前的框架允许使用[Presenter(typeof(FooPresenter))] 装饰视图(因此消除了样板解析代码)。使用生成的工厂,如果没有更多元数据,我将不知道要解析哪种特定的工厂类型。我可能会在扩展方法RegisterPresenter&lt;T&gt; 后面使用builder.RegisterGeneratedFactory&lt;Func&lt;TView, TPresenter&gt;&gt;();,它通过反射发现视图类型。不管怎样,谢谢你的灵感!
    • 您也可以试用我的通用工厂示例(链接到我的示例中的文章)。您只需注册一个可以动态构建任何演示者的通用工厂。甚至应该可以将它与您的 Presenter 属性结合起来,从而最小化样板解析代码和样板容器设置:)
    • +1 这看起来简洁明了,我稍后会检查它,看看它是如何进行的。感谢您的回复。
    • 我阅读了您的文章。我想验证这个委托签名:public delegate T ServiceFactory() where T:class;T 需要在某处声明,这意味着它应该是 ServiceFactory&lt;T&gt;()。这将更改CustomerController 的构造函数以使用ServiceFactory&lt;CustomerService&gt; 作为参数类型。听起来对吗?
    【解决方案2】:

    我实际上解决了这个确切的问题并围绕它构建了一个框架。我使用Autofac parameters 将现有视图传递给演示者解析调用。

    首先,我定义了一个源自 Autofac 的自定义解析接口:

    public interface IMvpContext : IContext
    {
        T View<T>();
    }
    

    这允许我注册一个解决视图的演示者:

    builder.RegisterPresenter(c => new FooPresenter(
        c.View<IFooView>(),
        c.Resolve<IFooService>()));
    

    使用将 Autofac 的 IContext 包装在 IMvpContext 的实现中的扩展方法:

    public static IConcreteRegistrar RegisterPresenter<T>(
        this ContainerBuilder builder,
        Func<IMvpContext, T> creator)
    {
        return builder
            .Register((context, parameters) => creator(new MvpContext(context, parameters)))
            .FactoryScoped();
    }
    

    我定义了一个代表视图参数的参数类型:

    public class MvpViewParameter : NamedParameter
    {
        public static readonly string ParameterName = typeof(MvpViewParameter).AssemblyQualifiedName;
    
        public MvpViewParameter(object view) : base(ParameterName, view)
        {}
    }
    

    它使用自己的程序集限定类型名称作为参数名称。这与合法参数发生冲突的可能性非常低。

    MvpContext 将所有标准分辨率调用传递给基本上下文。对于视图,它使用众所周知的名称解析参数:

    public sealed class MvpContext : IMvpContext
    {
        private IContext _context;
        private IEnumerable<Parameter> _resolutionParameters;
    
        public MvpContext(IContext context, IEnumerable<Parameter> resolutionParameters)
        {
            _context = context;
            _resolutionParameters = resolutionParameters;
        }
    
        #region IContext
    
        // Pass through all calls to _context
    
        #endregion
    
        #region IMvpContext
    
        public T View<T>()
        {
            return _resolutionParameters.Named<T>(MvpViewParameter.ParameterName);
        }
        #endregion
    }
    

    解析presenter的调用提供了view参数:

    public partial class Foo : Page, IFooView
    {
        private readonly FooPresenter presenter;
    
        public Foo()
        {
            this.presenter = IoCResolve<IFooPresenter>(new MvpViewParameter(this));
        }
    }
    

    【讨论】:

    • 这太复杂了。使用内置生成的工厂内容执行此操作,您可以删除整个 MvpContext 参数。
    • (有关此方法的进一步讨论,请参阅@Peter 回答中的 cmets。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 2014-06-12
    • 2013-04-30
    • 1970-01-01
    • 2021-11-19
    相关资源
    最近更新 更多