1

我正在尝试从 Ninject 迁移到 Simple Injector,但是在尝试复制与 Ninject 一起使用的功能时遇到了一个奇怪的问题。

在 Ninject 我有一项服务,其中包含:

private readonly ICollection<Message> messages;

该服务注册为

Bind<INotificationService>().To<NotificationService>()
    .InRequestScope();

此服务允许将消息(UI 和错误)传递回 MVC 站点。

该服务被注入到 ActionFilterAttribute 中:

kernel.BindFilter<CriticalErrorAttribute>(FilterScope.Last, 1)
    .When((context, ad) => 
        !string.IsNullOrEmpty(ad.ActionName) &&
        ad.ControllerDescriptor.ControllerName.ToLower() != "navigation");

并在 OnActionExecuted 中使用。

因为服务是通过 InRequestScope 注册到 Ninject 的,所以推送到消息队列的任何项目都可以在 Actionfiter 中使用。这允许在必要时重定向到错误页面(显示严重错误)。

我试图用simpleinjector复制它:

container.RegisterPerWebRequest<INotificationService, NotificationService>();

container.RegisterInitializer<CriticalErrorAttribute>(handler =>
{
    handler.NotificationService =
        container.GetInstance<INotificationService>();
});

注入工作正常,但即使消息集合在进入 ActionFilter 之前包含消息,一旦进入过滤器,消息集合就是空的。就像 RegisterPerWebRequest 被忽略一样。

解决此问题的任何帮助将不胜感激。

4

1 回答 1

3

更新:

在 Simple Injector 2.5 中,新的RegisterMvcIntegratedFilterProvider扩展方法已添加到 MVC 集成包中,以替换旧的RegisterMvcAttributeFilterProvider. 这个新RegisterMvcIntegratedFilterProvider的包含SimpleInjectorFilterAttributeFilterProvider下面给出的行为,并允许将属性更好地集成到 Simple Injector 管道中。然而,这确实意味着默认情况下不会注入任何属性,但这可以通过实现自定义 IPropertySelectionBehavior来扩展。建议使用新方法而RegisterMvcIntegratedFilterProvider不是旧RegisterMvcAttributeFilterProvider方法,这将[Obsolete]在将来的版本中标记。


使用RegisterMvcAttributeFilterProvider扩展方法时,Simple Injector 不会在 MVC 属性上调用任何已注册的初始化程序。如果您在注入的匿名委托中设置断点,NotificationService您将看到它永远不会被命中。

然而,Simple Injector 确实调用了container.InjectPropertiesMVC 属性上的方法,但InjectProperties会进行隐式属性注入,这意味着它会尝试注入类型上的所有公共属性,但如果无法注入属性(无论出于何种原因),则会跳过它。

我敢打赌,该CriticalErrorAttribute.NotificationService属性的类型是 ofNotificationService而不是INotificationService. 由于您没有NotificationService显式注册,因此容器将为您创建一个临时实例,这意味着您将获得一个CriticalErrorAttribute与应用程序其余部分不同的实例。

快速修复:将属性类型更改为INotificationService.

老实说,我很遗憾曾经为 Simple Injector 实现了 MVC 集成包来使用该InjectProperties方法。隐式属性注入是非常邪恶的,因为当配置错误时它不会很快失败,我什至考虑InjectProperties在未来取消对它的支持。然而,问题是许多开发人员都依赖InjectProperties. 要么直接调用它,要么间接地让容器在 MVC 属性上注入属性。

InjectProperties不运行任何初始化程序。这是设计使然,还有其他构造允许在不是由容器创建的对象上运行完整的初始化过程。然而问题是,添加它可能会破坏现有客户端,因为这可能导致属性被多次注入。

在你的情况下,我建议一个不同的解决方案:

container.RegisterMvcAttributeFilterProvider()防止在应用程序的启动路径中调用。这将注册一个特殊FilterAttributeFilterProvider的内部调用InjectProperties。您不想使用隐式属性注入,而是想要更明确(和完整)的行为。而是注册以下类:

internal sealed class SimpleInjectorFilterAttributeFilterProvider
    : FilterAttributeFilterProvider
{
    private readonly ConcurrentDictionary<Type, Registration> registrations =
        new ConcurrentDictionary<Type, Registration>();

    private readonly Func<Type, Registration> registrationFactory;

    public SimpleInjectorFilterAttributeFilterProvider(Container container)
        : base(false)
    {
        this.registrationFactory = type => 
            Lifestyle.Transient.CreateRegistration(type, container);
    }

    public override IEnumerable<Filter> GetFilters(
        ControllerContext context,
        ActionDescriptor descriptor)
    {
        var filters = base.GetFilters(context, descriptor).ToArray();

        foreach (var filter in filters)
        {
            object instance = filter.Instance;

            var registration = registrations.GetOrAdd(
                instance.GetType(), this.registrationFactory);

            registration.InitializeInstance(instance);                
        }

        return filters;
    }
}

您可以使用以下代码注册此自定义提供程序:

var filterProvider = 
    new SimpleInjectorFilterAttributeFilterProvider(container);

container.RegisterSingle<IFilterProvider>(filterProvider);

var providers = FilterProviders.Providers
    .OfType<FilterAttributeFilterProvider>().ToList();

providers.ForEach(provider => FilterProviders.Providers.Remove(provider));

FilterProviders.Providers.Add(filterProvider);

此自定义SimpleInjectorFilterAttributeFilterProvider调用Registration.InitializeInstance方法。此方法允许初始化已创建的类型,并将通过(除其他外)调用类型初始化器委托来初始化它。

有关使用属性的更多信息,请阅读以下讨论

于 2013-07-28T15:56:36.057 回答