Использование простого инжектора с SignalR

Я думал, что использовать мой собственный IoC будет довольно просто с SignalR, и, возможно, так оно и есть; скорее всего я что то не так делаю. Вот мой код, который у меня есть:

private static void InitializeContainer(Container container)
{

   container.Register<IMongoHelper<UserDocument>, MongoHelper<UserDocument>>();
   // ... registrations like about and then:
   var resolver = new SimpleInjectorResolver(container);
   GlobalHost.DependencyResolver = resolver;
}

а затем мой класс:

public class SimpleInjectorResolver : DefaultDependencyResolver
{
    private Container _container;
    public SimpleInjectorResolver(Container container)
    {
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        return _container.GetInstance(serviceType) ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType);
    }
}

В конечном итоге я получаю сообщение об ошибке, что IJavaScriptProxyGenerator не может быть разрешен, поэтому я думаю, что я добавлю регистрацию:

container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);

но есть еще куча других! Я добираюсь до:

container.Register<IDependencyResolver, SimpleInjectorResolver>();
container.Register<IJavaScriptMinifier, NullJavaScriptMinifier>();
container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);
container.Register<IHubManager, DefaultHubManager>();
container.Register<IHubActivator, DefaultHubActivator>();
container.Register<IParameterResolver, DefaultParameterResolver>();
container.Register<IMessageBus, InProcessMessageBus>(ConstructorSelector.MostParameters);

Что по-прежнему дает мне «Не удалось найти регистрацию для типа ITraceManager». ... но теперь мне интересно, правильно ли я делаю это, поскольку я надеюсь, что мне не нужно будет повторно подключать все, что делает SignalR ... верно? С надеждой? Если нет, я буду продолжать плыть, но я новичок в SignalR и Simple Injector, поэтому подумал, что сначала спрошу. :)

Дополнительно: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88, поскольку в SignalR было несколько конструкторов.


person rball    schedule 11.05.2012    source источник


Ответы (6)


Что ж, вчера попробовал и нашел решение. По моему мнению, единственный момент, когда я хочу внедрения зависимостей в SignalR, - это мои концентраторы: меня не волнует, как SignalR работает внутри! Поэтому вместо замены DependencyResolver я создал свою собственную реализацию IHubActivator:

public class SimpleInjectorHubActivator : IHubActivator
{
    private readonly Container _container;

    public SimpleInjectorHubActivator(Container container)
    {
        _container = container;
    }

    public IHub Create(HubDescriptor descriptor)
    {
        return (IHub)_container.GetInstance(descriptor.HubType);
    }
}

Что я могу зарегистрировать вот так (в Application_Start):

var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
RouteTable.Routes.MapHubs();
person Nathanael Marchand    schedule 02.04.2013
comment
Думаю, я предпочитаю ваше решение своему (+1). - person Steven; 28.08.2013
comment
Я так много раз изучал этот вопрос и почему-то пропустил этот ответ, пока не нашел его где-то еще. Больше голосов за! - person Facio Ratio; 22.04.2014
comment
Еще более простая реализация, передача ее в MVC Dependency Resolver: return (IHub)DependencyResolver.Current.GetService(descriptor.HubType); - person Chris; 12.08.2014
comment
Могу я спросить, что вы называете RouteTable.Routes.MapHubs();? Эту строчку я не понимаю. Если я его опущу, это тоже сработает как шарм. - person ChristianMurschall; 07.05.2018
comment
GlobalHost больше не существует в AspNetCore 3 - person William Jockusch; 13.02.2020
comment
Я не знаю, чего вы пытаетесь достичь, но этот ответ (с 2014 года) совершенно бесполезен для SignalR в AspNetCore. В отличие от SignalR с ASP.Net MVC, в ASP.Net Core все было разработано для внедрения зависимостей (с той, которая предоставляется фреймворком). Я предлагаю вам открыть новую проблему, если у вас возникли проблемы с внедрением зависимостей в SignalR с помощью ASP.Net Core. - person Nathanael Marchand; 14.02.2020

Хочу добавить сюда мои 2 цента с другими ответами, которые могут быть полезны для поиска вашего собственного пути с внедрением зависимостей в SignalR, используя SimpleInjector или другой IoC.

Используя @ ответ Стивена

Если вы решите использовать ответ Стивена, убедитесь, что вы зарегистрировали маршруты концентратора, прежде чем составлять корень. Метод расширения SignalRRouteExtensions.MapHubs (он же routes.MapHubs()) будет вызывать Register(Type, Func<object>) на GlobalHost.DependencyResolver при сопоставлении маршрутов концентратора, поэтому, если вы замените DefaultDependencyResolver на SimpleInjectorResolver Стивена до сопоставления маршрутов, вы столкнетесь с его NotSupportedException.

Использование ответа @Nathanael Marchand

Это моя любимая. Почему?

  1. Меньше кода, чем у SimpleInjectorDependencyResolver.
  2. Не нужно заменять DefaultDependencyResolver (он же GlobalHost.DependencyResolver), что означает еще меньше кода.
  3. Вы можете создать корень до или после сопоставления маршрутов концентратора, поскольку вы не заменяете DefaultDependencyResolver, он будет «просто работать».

Однако, как сказал Натанаэль, это только в том случае, если вы заботитесь о зависимостях ваших Hub классов, что, вероятно, будет иметь место для большинства. Если вы хотите повозиться с внедрением других зависимостей в SignalR, вы можете пойти с ответом Стивена.

Проблемы с зависимостями веб-запросов в Hub

В SignalR есть интересная вещь ... когда клиент отключается от концентратора (например, закрывая окно браузера), он создает новый экземпляр класса Hub для вызова OnDisconnected(). В этом случае HttpContext.Current равно нулю. Поэтому, если у этого Hub есть какие-либо зависимости, зарегистрированные для каждого веб-запроса, вероятно, что-то пойдет не так.

В Ninject

Я опробовал внедрение зависимостей SignalR с помощью Ninject и преобразователя зависимостей ninject signalr на nuget < / а>. В этой конфигурации привязанные .InRequestScope() зависимости будут создаваться временно при вводе в Hub во время события отключения. Поскольку HttpContext.Current имеет значение null, я полагаю, что Ninject просто решает игнорировать его и создавать временные экземпляры, не сообщая вам об этом. Возможно, была настройка конфигурации, чтобы сообщить ninject о предупреждении об этом, но это не было по умолчанию.

В SimpleInjector

SimpleInjector, с другой стороны, вызовет исключение, когда Hub зависит от экземпляра, зарегистрированного с WebRequestLifestlyle:

Зарегистрированный делегат для типа NameOfYourHub выдал исключение. Зарегистрированный делегат для типа NameOfYourPerRequestDependency выдал исключение. YourProject.Namespace.NameOfYourPerRequestDependency зарегистрирован как PerWebRequest, но экземпляр запрашивается вне контекста HttpContext (HttpContext.Current имеет значение null). Убедитесь, что экземпляры, использующие этот образ жизни, не разрешаются на этапе инициализации приложения и при работе в фоновом потоке. Для разрешения экземпляров в фоновых потоках попробуйте зарегистрировать этот экземпляр как «Per Lifetime Scope»: https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped.

... обратите внимание, это исключение всплывет только тогда, когда HttpContext.Current == null, что, насколько я могу судить, происходит только тогда, когда SignalR запрашивает экземпляр Hub для вызова OnDisconnected().

Решения для зависимостей веб-запросов в Hub

Обратите внимание, что ни один из них не идеален, все будет зависеть от требований вашего приложения.

В Ninject

Если вам нужны непреходящие зависимости, просто не переопределяйте OnDisconnected() и не делайте там ничего особенного с зависимостями классов. Если вы это сделаете, каждая зависимость на графике будет отдельным (временным) экземпляром.

В SimpleInjector

Вам нужен гибридный образ жизни между WebRequestLifestlye и Lifestyle.Transient, Lifestyle.Singleton или LifetimeScopeLifestyle. Когда HttpContext.Current не равно нулю, зависимости будут существовать только столько времени, сколько веб-запрос, как вы обычно ожидаете. Однако, когда HttpContext.Current имеет значение null, зависимости будут вводиться либо временно, как одиночные, либо в пределах времени жизни.

var lifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => HttpContext.Current != null,
    trueLifestyle: new WebRequestLifestyle(),
    falseLifestyle: Lifestyle.Transient // this is what ninject does
    //falseLifestyle: Lifestyle.Singleton
    //falseLifestyle: new LifetimeScopeLifestyle()
);

Подробнее о LifetimeScopeLifestyle

В моем случае у меня есть зависимость EntityFramework DbContext. Это может быть сложно, потому что они могут выявить проблемы при временной регистрации или как одиночные. При временной регистрации вы можете столкнуться с исключениями при попытке работать с объектами, прикрепленными к 2 или более DbContext экземплярам. Когда вы регистрируетесь как синглтон, вы получаете более общие исключения (никогда не регистрируйте DbContext как синглтон). В моем случае мне нужно было, чтобы DbContext существовал в течение определенного времени жизни, в течение которого один и тот же экземпляр можно было бы повторно использовать во многих вложенных операциях, а это значит, что мне нужен LifetimeScopeLifestyle.

Теперь, если вы использовали приведенный выше гибридный код со строкой falseLifestyle: new LifetimeScopeLifestyle(), вы получите другое исключение при выполнении вашего пользовательского метода IHubActivator.Create:

Зарегистрированный делегат для типа NameOfYourHub выдал исключение. NameOfYourLifetimeScopeDependency регистрируется как LifetimeScope, но экземпляр запрашивается вне контекста области действия. Убедитесь, что вы сначала вызываете container.BeginLifetimeScope ().

То, как вы настроили зависимость с ограниченным сроком действия, выглядит следующим образом:

using (simpleInjectorContainer.BeginLifetimeScope())
{
    // resolve solve dependencies here
}

Любые зависимости, зарегистрированные в области действия, должны быть разрешены в этом блоке using. Более того, если какая-либо из этих зависимостей реализует IDisposable, они будут удалены в конце блока using. Не поддавайтесь соблазну сделать что-то вроде этого:

public IHub Create(HubDescriptor descriptor)
{
    if (HttpContext.Current == null)
        _container.BeginLifetimeScope();
    return _container.GetInstance(descriptor.HubType) as IHub;
}

Я спросил об этом Стивена (который также является автором SimpleInjector, если вы не знали), и он сказал:

Что ж ... Если вы не утилизируете LifetimeScope, у вас будут большие проблемы, поэтому убедитесь, что они утилизированы. Если вы не избавитесь от областей видимости, они навсегда останутся в ASP.NET. Это связано с тем, что области могут быть вложенными и ссылаться на свою родительскую область. Таким образом, поток поддерживает самую внутреннюю область видимости (с ее кешем), а эта область поддерживает свою родительскую область (с ее кешем) и так далее. ASP.NET объединяет потоки и не сбрасывает все значения при захвате потока из пула, поэтому это означает, что все области остаются активными, и в следующий раз, когда вы захватите поток из пула и запустите новую область жизни, вы просто создание новой вложенной области видимости, и она будет накапливаться. Рано или поздно вы получите исключение OutOfMemoryException.

Вы не можете использовать IHubActivator для определения зависимостей, потому что он не существует до тех пор, пока Hub экземпляр, который он создает. Таким образом, даже если вы заключите метод BeginLifetimeScope() в блок using, ваши зависимости будут удалены сразу после создания экземпляра Hub. Что вам действительно нужно, так это еще один уровень косвенного обращения.

В итоге я получил, благодаря помощи Стивена, декоратор команд (и декоратор запросов). Hub не может зависеть от экземпляров каждого веб-запроса, а должен зависеть от другого интерфейса, реализация которого зависит от экземпляров каждого запроса. Реализация, которая вводится в конструктор Hub, украшается (с помощью простого инжектора) оберткой, которая начинается и удаляет область существования.

public class CommandLifetimeScopeDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly Func<ICommandHandler<TCommand>> _handlerFactory;
    private readonly Container _container;

    public CommandLifetimeScopeDecorator(
        Func<ICommandHandler<TCommand>> handlerFactory, Container container)
    {
        _handlerFactory = handlerFactory;
        _container = container;
    }

    [DebuggerStepThrough]
    public void Handle(TCommand command)
    {
        using (_container.BeginLifetimeScope())
        {
            var handler = _handlerFactory(); // resolve scoped dependencies
            handler.Handle(command);
        }
    }
}

... это декорированные экземпляры ICommandHandler<T>, которые зависят от экземпляров каждого веб-запроса. Для получения дополнительной информации об используемом шаблоне прочтите это и это.

Пример регистрации

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);
person danludwig    schedule 28.08.2013
comment
это очень интересно, если честно, как вы регистрируете свои зависимости с помощью handlerFactory? - person pedrommuller; 11.09.2013
comment
Вам не нужно делать ничего особенного, чтобы зарегистрировать зависимости для использования handlerFactory. Я просто регистрирую обработчики команд с помощью RegisterManyForOpenGeneric, а затем украшаю их RegisterDecorator, передавая CommandLifetimeScopeDecorator. Фактически вы можете изменить приведенный выше код, чтобы использовать ICommandHandler<TCommand> вместо Func<ICommandHandler<TCommand>>, и он все равно будет внедряться. Он просто внедрит экземпляр вместо делегата, чтобы лениво внедрить экземпляр. Я обновил ответ примером. - person danludwig; 11.09.2013
comment
как мне реализовать ваш LifeScope в хабе? - person pedrommuller; 11.09.2013
comment
Вся информация здесь или вы можете найти ее сами. Если это слишком сложная тема, попробуйте другое решение или задайте другой вопрос. - person danludwig; 11.09.2013
comment
без проблем покопаюсь попробую разобраться спасибо - person pedrommuller; 11.09.2013
comment
Теперь, когда у SI есть ExecutionContext образ жизни, вам все еще нужен гибридный образ жизни? Тем не менее, это хорошее чтение! - person Mrchief; 12.05.2014
comment
Перефразируя этот ответ: если вы используете Simple Injector и НЕ используете причудливые Command / QueryHandlers, вы просто не можете внедрить свой репозиторий (с зависимостью от DbContext, который настроен с образом жизни для каждого веб-запроса) в ваш концентратор SignalR ... верно @ Стивен? - person Ilja S.; 07.08.2015
comment
@IljaS. Нет, ты ошибаешься. Решение простое: либо не используйте OnDisconnected, либо создайте область и разрешите новые экземпляры во время вызова OnDisconnected. Но обратите внимание, что это недостаток дизайна в SignalR, который затрагивает даже пользователей Ninject, потому что использование временных экземпляров DbContext в графе объектов может серьезно повредить ваше приложение. - person Steven; 07.08.2015
comment
@ Стивен - Похоже, я что-то упустил. Я описал это отдельным потоком, но Короче говоря, похоже, что это не работает в сценарии веб-API ... Я был бы очень признателен, если бы вы могли взглянуть. Спасибо! - person Ilja S.; 08.08.2015

ОБНОВЛЕНИЕ. Этот ответ был обновлен для SignalR версии 1.0

Вот как создать SignalR IDependencyResolver для Simple Injector:

public sealed class SimpleInjectorResolver 
    : Microsoft.AspNet.SignalR.IDependencyResolver
{
    private Container container;
    private IServiceProvider provider;
    private DefaultDependencyResolver defaultResolver;

    public SimpleInjectorResolver(Container container)
    {
        this.container = container;
        this.provider = container;
        this.defaultResolver = new DefaultDependencyResolver();
    }

    [DebuggerStepThrough]
    public object GetService(Type serviceType)
    {
        // Force the creation of hub implementation to go
        // through Simple Injector without failing silently.
        if (!serviceType.IsAbstract && typeof(IHub).IsAssignableFrom(serviceType))
        {
            return this.container.GetInstance(serviceType);
        }

        return this.provider.GetService(serviceType) ?? 
            this.defaultResolver.GetService(serviceType);
    }

    [DebuggerStepThrough]
    public IEnumerable<object> GetServices(Type serviceType)
    {
        return this.container.GetAllInstances(serviceType);
    }

    public void Register(Type serviceType, IEnumerable<Func<object>> activators)
    {
        throw new NotSupportedException();
    }

    public void Register(Type serviceType, Func<object> activator)
    {
        throw new NotSupportedException();
    }

    public void Dispose()
    {
        this.defaultResolver.Dispose();
    }
}

К сожалению, есть проблема с дизайном DefaultDependencyResolver. Вот почему приведенная выше реализация не наследуется от него, а обертывает его. Я написал об этом на сайте SignalR. Вы можете прочитать об этом здесь. Хотя дизайнер согласился со мной, к сожалению, в версии 1.0 проблема не была исправлена.

Надеюсь, это поможет.

person Steven    schedule 11.05.2012
comment
Посмотрите эту ссылку: github.com/SignalR/SignalR/blob / master / SignalR / Infrastructure /. Я все еще получаю те же ошибки, когда использую ваш код, я думаю, что мне может потребоваться переопределить, хотя это Register с вызовом базового конструктора, а не GetServices. Я собираюсь попробовать. - person rball; 15.05.2012
comment
@Steven: правильно, я думаю, что DefaultDependencyResolver ошибочен. Я пробовал почти все подходы, но до сих пор не могу ввести зависимости. - person Amr Ellafy; 20.09.2012
comment
@AmrEllafy: Моя проблема была закрыта разработчиком как дубликат, но в целом он согласился с моими рассуждениями. Нет ли исправления или обновления, которое решает эту проблему? - person Steven; 20.09.2012
comment
@Steven: В итоге я ввел свои зависимости напрямую DependencyResolver.Current.GetService ‹IUserService› (); Я не мог найти более чистого способа, по крайней мере, с Spring.Net - person Amr Ellafy; 20.09.2012

Начиная с SignalR 2.0 (и бета-версии) есть новый способ настройки преобразователя зависимостей. SignalR переехал в запуск OWIN, чтобы выполнить настройку. С Simple Injector вы бы сделали это так:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HubConfiguration()
        {
            Resolver = new SignalRSimpleInjectorDependencyResolver(Container)
        };
        app.MapSignalR(config);
    }
}

public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver
{
    private readonly Container _container;
    public SignalRSimpleInjectorDependencyResolver(Container container)
    {
        _container = container;
    }
    public override object GetService(Type serviceType)
    {
        return ((IServiceProvider)_container).GetService(serviceType)
               ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType)
            .Concat(base.GetServices(serviceType));
    }
}

Вам нужно будет явно ввести свои концентраторы следующим образом:

container.Register<MessageHub>(() => new MessageHub(new EFUnitOfWork()));

Эта конфигурация работает без проблем в реальном времени на веб-сайте с высокой посещаемостью.

person Elger Mensonides    schedule 30.08.2013
comment
Краткая версия тела GetService () будет выглядеть так: return ((IServiceProvider) container) .GetService (serviceType) ?? base.GetService (serviceType); - person blueling; 11.10.2013
comment
Краткая версия тела GetServices () будет выглядеть так: return container.GetAllInstances (serviceType) .Concat (base.GetServices (serviceType)); - person blueling; 11.10.2013
comment
Я обновил ответ в соответствии с обратной связью @ blueling. кстати, в методе GetServices была ошибка. Код проверен на GetRegistration(serviceType, false) != null, но этот предикат обычно возвращает false, поскольку регистрация коллекции - это другая регистрация. Однако выполнение GetRegistration(typeof(IEnumerable<>).MakeGenericType(serviceType), false) != null не сработает, поскольку это никогда не вернет значение null (поскольку Simple Injector выполняет эту регистрацию за вас, если она отсутствует). Так что ответ blueling намного лучше. - person Steven; 02.05.2014
comment
Откуда вы берете экземпляр Container в своем Startup классе? - person Makotosan; 08.05.2014
comment
Я вставил это в свой класс Startup, GlobalHost.DependencyResolver = new SignalRSimpleInjectorDependencyResolver (container); - person Tony; 21.05.2014
comment
Проголосовал против этого ответа, так как неясно, где получить экземпляр Container в следующем фрагменте кода: var config = new HubConfiguration() { Resolver = new SignalRSimpleInjectorDependencyResolver(Container) }; - person Jon Pawley; 12.10.2017
comment
Я в замешательстве: если вам все еще нужно явно внедрить свои зависимости при регистрации хаба, тогда какой во всем этом смысл? - person Tom; 19.02.2018

Следующее сработало для меня. Кроме того, вам нужно будет зарегистрировать делегат в контейнере для вашего класса-концентратора перед созданием экземпляра преобразователя зависимостей.

ex: container.Register<MyHub>(() =>
        {
            IMyInterface dependency = container.GetInstance<IMyInterface>();

            return new MyHub(dependency);
        });

public class SignalRDependencyResolver : DefaultDependencyResolver
{
    private Container _container;
    private HashSet<Type> _types = new HashSet<Type>();

    public SignalRDependencyResolver(Container container)
    {
        _container = container;

        RegisterContainerTypes(_container);
    }

    private void RegisterContainerTypes(Container container)
    {
        InstanceProducer[] producers = container.GetCurrentRegistrations();

        foreach (InstanceProducer producer in producers)
        {
            if (producer.ServiceType.IsAbstract || producer.ServiceType.IsInterface)
                continue;

            if (!_types.Contains(producer.ServiceType))
            {
                _types.Add(producer.ServiceType);
            }
        }
    }

    public override object GetService(Type serviceType)
    {
        return _types.Contains(serviceType) ? _container.GetInstance(serviceType) : base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType);
    }
}
person mg712    schedule 29.03.2013

В .NET Core 3.x signalR теперь является переходным процессом. Вы можете ввести свой хаб с помощью DI. Итак, вы начинаете отображать концентратор, реализовывать интерфейс и концентратор и получать доступ к его контексту через DI.

Запускать:

app.UseSignalR(routes =>
{
    routes.MapHub<YourHub>(NotificationsRoute); // defined as string
});
services.AddSignalR(hubOptions =>
{
    // your options
})

Затем вы реализуете такой интерфейс, как:

public interface IYourHub
{
    // Your interface implementation
}

И ваш хаб:

public class YourHub : Hub<IYourHub>
{
    // your hub implementation
}

Наконец, вы вводите концентратор, например:

private IHubContext<YourHub, IYourHub> YourHub
{
    get
    {
        return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
    }
}

Также вы можете определить службу для своего хаба (промежуточное ПО) и не вводить контекст напрямую в свой класс.

Представьте, что вы определили в интерфейсе метод Message, поэтому в своем классе вы можете отправить сообщение следующим образом:

await this.YourHub.Clients.Group("someGroup").Message("Some Message").ConfigureAwait(false);

Если это не реализовано в интерфейсе, который вы просто используете:

await this.YourHub.Clients.Group("someGroup").SendAsync("Method Name", "Some Message").ConfigureAwait(false);
person Kiril1512    schedule 17.02.2020
comment
UseSignalR говорит, что он устарел. Я нашел решение здесь: github.com/simpleinjector/SimpleInjector/issues/631 - person William Jockusch; 20.02.2020
comment
@WilliamJockusch, тогда вам следует использовать app.UseEndpoints в этом случае. - person Kiril1512; 20.02.2020