Хочу добавить сюда мои 2 цента с другими ответами, которые могут быть полезны для поиска вашего собственного пути с внедрением зависимостей в SignalR, используя SimpleInjector или другой IoC.
Если вы решите использовать ответ Стивена, убедитесь, что вы зарегистрировали маршруты концентратора, прежде чем составлять корень. Метод расширения SignalRRouteExtensions.MapHubs
(он же routes.MapHubs()
) будет вызывать Register(Type, Func<object>)
на GlobalHost.DependencyResolver
при сопоставлении маршрутов концентратора, поэтому, если вы замените DefaultDependencyResolver
на SimpleInjectorResolver
Стивена до сопоставления маршрутов, вы столкнетесь с его NotSupportedException
.
Это моя любимая. Почему?
- Меньше кода, чем у
SimpleInjectorDependencyResolver
.
- Не нужно заменять
DefaultDependencyResolver
(он же GlobalHost.DependencyResolver
), что означает еще меньше кода.
- Вы можете создать корень до или после сопоставления маршрутов концентратора, поскольку вы не заменяете
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