Подтвердить что ты не робот

Использование простого инжектора с 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 newb, поэтому подумал, что сначала спрошу.:)

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

4b9b3361

Ответ 1

Хорошо, я попробовал вчера, и я нашел решение. По мне, единственный момент, когда я хочу, чтобы инъекция зависимостей в 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();

Ответ 2

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

Используя @Steven answer

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

Используя @Ответ Nathanael Marchand

Это мой любимый. Почему?

  • Меньше кода, чем SimpleInjectorDependencyResolver.
  • Не нужно заменять DefaultDependencyResolver (a.k.a. GlobalHost.DependencyResolver), что означает еще меньше кода.
  • Вы можете составить корневой каталог до или после маршрутизации маршрутов, так как вы не заменяете DefaultDependencyResolver, он будет "просто работать".

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

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

Интересно, что SignalR... когда клиент отключается от концентратора (например, закрывая окно браузера), он создаст новый экземпляр класса Hub, чтобы вызвать OnDisconnected(). Когда это произойдет, HttpContext.Current имеет значение null. Поэтому, если этот 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://bit.ly/N1s8hN.

... обратите внимание, что это исключение будет всплывать только тогда, когда 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, вы будете в большом беда, поэтому убедитесь, что они удалены. Если вы не располагаете scopes, они будут вешать навсегда в ASP.NET. Это потому что области могут быть вложенными и ссылаться на их родительскую область. Итак, поток сохраняет свою внутреннюю область (с ее кешем), и эта область сохраняет оживить его родительскую область (с ее кешем) и так далее. Пулы ASP.NET threads и doesnt reset все значения, когда он захватывает поток из пул, поэтому это означает, что все области остаются в живых, и в следующий раз возьмите поток из пула и запустите новую сферу жизни, вы будете просто создавая новую вложенную область, и это будет продолжаться. Рано или поздно вы получите исключение OutOfMemoryException.

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

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

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

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

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

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);

Ответ 3

UPDATE. Этот ответ был обновлен для версии SignalR версии 1.0

Вот как построить SignalR IDependencyResolver для простого инжектора:

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.

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

Ответ 4

Из 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()));

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

Ответ 5

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

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);
    }
}