使用 Simple Injector abd WebFormsMVP 将运行时值传递给构造函数 [英] Pass runtime value to constructor using Simple Injector abd WebFormsMVP

查看:20
本文介绍了使用 Simple Injector abd WebFormsMVP 将运行时值传递给构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将 SimpleInjector 与 WebFormsMvp 结合起来.

I'm trying to combine SimpleInjector with WebFormsMvp.

为了方便 DI WebFormsMvp 提供了 IPresenterFactory 接口.
它包含 Create 方法,该方法提供要解析的 presenter 类型视图实例.
我需要将视图实例注入构造函数演示者.
演示者其他依赖项需要由容器创建.

To facilitate DI WebFormsMvp provides the IPresenterFactory interface.
It contains the Create method which provides the presenter type to resolve and the view instance.
I need to inject the view instance into the constructor of the presenter.
The presenter also has other dependencies that need creating by the container.

这是我目前得到的,但并不理想.
问题的正确解决方案是什么?

This is what I got so far, but it is not ideal.
What's the correct solution for the problem?

演示者构造函数:

public FooPresenter(IFooView view, IClientFactory clientFactory) : base(view)

工厂:

public class SimpleInjectorPresenterFactory : IPresenterFactory
{
    private readonly Container _container;
    private IView _currentView;

    public SimpleInjectorPresenterFactory()
    {
        _container = new Container();

        Func<Type, bool> isIView = 
            type => typeof(IView).IsAssignableFrom(type);

        _container.ResolveUnregisteredType += (s, e) => {
            if (isIView(e.UnregisteredServiceType))
                e.Register(() => _currentView);
        };
    }

    public IPresenter Create(Type presenterType, Type viewType, IView viewInstance)
    {
        lock (_currentView)
        {
            _currentView = viewInstance;
            return _container.GetInstance(presenterType) as IPresenter;
        }
    }
}

推荐答案

WebFormsMvp 强制您在演示者的构造函数中获取视图,但这会触发循环引用.如果您查看不同容器的工厂实现,您会发现对于每个容器,它们都使用不同的技巧来解决设计中的这个怪癖.例如,通过 unity,他们创建一个子容器并在子容器中注册该视图,并使用该子容器解析演示者.很奇怪,性能很重.

WebFormsMvp forces you to take the view in the constructor of the presenter, but this triggers a circular reference. If you look in the factory implementations for the different container's, you'll see that for each container they do a different trick to work around this quirk in the design. For instance, with unity, they create a child container and register that view in the child container, and resolve the presenter using that child container. Quite bizarre and performance heavy.

WebFormsMvp 的设计者应该在 IPresenter 接口上将 View 设为可写属性,而不是在 Presenter 的构造函数中使用视图.这将使在演示者上设置视图变得非常容易.像这样:

Instead of taking the view in the constructor of the presenter, the designers of WebFormsMvp should have made the View a writable property on the IPresenter interface. This would make it embarrassingly easy to set the view on the presenter. Something like this:

public IPresenter Create(Type presenterType, IView view)
{
    var presenter = (IPresenter)_container.GetInstance(presenterType);
    presenter.View = view;
    return presenter;
}   

不幸的是,他们没有这样做,并且不可能扩展设计以允许这样做(不使用反射做非常讨厌的事情).

Unfortunately they didn't do this, and it is impossible to extend the design to allow this (without doing really nasty things using reflection).

Simple Injector 不支持向 GetInstance() 方法提供构造函数参数.有充分的理由,因为这通常会导致服务定位器反模式,您始终可以通过更改设计来解决此问题.在你的情况下,你没有做出那个古怪的设计,所以你不能改变它.

Simple Injector does not support supplying constructor arguments to the GetInstance() method. For good reason, since this would typically lead to the Service Locator anti-pattern, and you can always go around this by changing the design. In your case, you didn't make that quirky design, so you can't change it.

您对 ResolveUnregisteredType 所做的非常聪明.我自己不会考虑这个.由于我是 Simple Injector 背后的首席开发人员,我可以说你所做的真的很聪明 :-)

What you did with the ResolveUnregisteredType is pretty clever. I wouldn't have thought about this myself. And since I'm the lead dev behind Simple Injector, I'm in the position to say that what you did was really clever :-)

关于您的 SimpleInjectorPresenterFactory 的两点反馈.

Just two points of feedback about your SimpleInjectorPresenterFactory.

首先,您应该提供 Container 作为构造函数参数,因为很可能您需要向容器添加其他注册,而您不想注册 <SimpleInjectorPresenterFactory 中的 code>Container.

First of all, you should supply the Container as constructor argument, since it will be very likely that you need to add other registrations to the container, and you don't want to register the Container inside the SimpleInjectorPresenterFactory.

其次,您可以使用 System.Threading.ThreadLocal 改进代码.这使您可以摆脱全局锁.锁定会阻止任何演示者同时进行,这可能会降低您的网站速度.

Secondly, you can improve the code by using a System.Threading.ThreadLocal<IView>. This allows you to get rid of the global lock. The lock prevents any presenter from being made concurrently, and this could slow down your website.

所以这是一个重构版本:

So here's a refactored version:

public class SimpleInjectorPresenterFactory : IPresenterFactory {
    private readonly Container _container;
    private ThreadLocal<IView> _currentView = new ThreadLocal<IView>();

    public SimpleInjectorPresenterFactory(Container container) {
        _container = container;

        _container.ResolveUnregisteredType += (s, e) => {
            if (typeof(IView).IsAssignableFrom(e.UnregisteredServiceType)) {
                e.Register(() => _currentView.Value);
            }
        };
    }

    public IPresenter Create(Type presenterType, Type viewType, 
        IView viewInstance)
    {
        _currentView.Value = viewInstance;

        try {
            return _container.GetInstance(presenterType) as IPresenter;
        } finally {
            // Clear the thread-local value to ensure
            // views can be disposed after the request ends.
            _currentView.Value = null;
        }
    }
}

如果您查看 UnityPresenterFactory 的实现,您会看到其中进行了大量缓存.我不知道他们为什么这样做,但从性能的角度来看,Simple Injector 根本不需要这样的东西.也许我遗漏了一些东西,但我不明白为什么应该有缓存.

If you look at the implementation of the UnityPresenterFactory, you see a lot of caching going on in there. I have no idea why they do that, but from a performance perspective, you don't need such thing for Simple Injector at all. Perhaps I'm missing something, but I don't see why there should be a cache.

但更糟糕的是,UnityPresenterFactory 中存在并发错误.看看这个方法:

But even worse, there is a concurrency bug in the UnityPresenterFactory. Take a look at this method:

private Type FindPresenterDescribedViewTypeCached(Type presenter, 
    IView view) 
{
    IntPtr handle = presenter.TypeHandle.Value;
    if (!this.cache.ContainsKey(handle)) 
    {
        lock (this.syncLock)
        {
            if (!this.cache.ContainsKey(handle))
            {
                Type viewType = CreateType(presenter, view);
                this.cache[handle] = viewType;
                return viewType;
            }
        }
    }
    return this.cache[handle];
}

乍一看这段代码看起来不错,因为实现了双重检查锁.不幸的是,缓存(字典)是从锁外部读取的,而它是在锁内部更新的.这不是线程安全的.取而代之的是,开发人员应该将整个内容封装在一个锁中,使用 ConcurrentDictionary(仅限 .net 4)或考虑 cache 不可变,这意味着您创建一个原始字典的副本,添加新值,并用新字典替换对旧字典的引用.但是,在这种情况下,我可能会锁定整个事情.

At first sight this code looks okay, since a double checked lock is implemented. Unfortunately, the cache (dictionary) is read from outside the lock, while it is updated inside the lock. This is not thread-safe. Instead, the developer should have either wrapped the whole thing in a lock, use a ConcurrentDictionary (.net 4 only) or consider the cache immutable, which means that you create a copy of the original dictionary, add the new value, and replace the reference to the old dictionary with the new. However, in this case I would probably just have locked the whole thing.

这有点离题,但只是想告诉:-)

This was a little bit off topic, but just wanted to tell :-)

这篇关于使用 Simple Injector abd WebFormsMVP 将运行时值传递给构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆