Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Предупреждение
Эта документация не подходит для последней версии SignalR. Взгляните на ASP.NET Core SignalR.
Внедрение зависимостей — это способ удаления жестко закодированных зависимостей между объектами, что упрощает замену зависимостей объекта либо для тестирования (с помощью макетных объектов), либо для изменения поведения во время выполнения. В этом руководстве показано, как выполнять внедрение зависимостей в центрах SignalR. В нем также показано, как использовать контейнеры IoC с SignalR. Контейнер IoC — это общая платформа для внедрения зависимостей.
Что такое внедрение зависимостей?
Пропустите этот раздел, если вы уже знакомы с внедрением зависимостей.
Внедрение зависимостей (DI) — это шаблон, в котором объекты не несут ответственности за создание собственных зависимостей. Ниже приведен простой пример для мотивации DI. Предположим, у вас есть объект, который должен регистрировать сообщения. Вы можете определить интерфейс ведения журнала:
interface ILogger
{
void LogMessage(string message);
}
В вашем объекте можно создать ILogger для регистрации сообщений.
// Without dependency injection.
class SomeComponent
{
ILogger _logger = new FileLogger(@"C:\logs\log.txt");
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
Это работает, но это не лучший дизайн. Если вы хотите заменить FileLogger другой ILogger реализацией, необходимо изменить SomeComponent. Предположим, что многие другие объекты используются FileLogger, вам потребуется изменить все из них. Или, если вы решите сделать FileLogger синглтон, вам также нужно будет внести изменения во всем приложении.
Лучший подход заключается в том, чтобы внедрить объект ILogger в объект, например с помощью аргумента конструктора:
// With dependency injection.
class SomeComponent
{
ILogger _logger;
// Inject ILogger into the object.
public SomeComponent(ILogger logger)
{
if (logger == null)
{
throw new NullReferenceException("logger");
}
_logger = logger;
}
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
Теперь объект не отвечает за выбор, какой ILogger использовать. Вы можете переключать ILogger реализации, не изменяя объекты, зависящие от него.
var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);
Этот шаблон называется внедрением конструктора. Другим шаблоном является внедрение через сеттер, при котором вы задаете зависимость с помощью метода-сеттера или свойства.
Простое внедрение зависимостей в SignalR
Рассмотрим приложение Чата из руководства по началу работы с SignalR. Ниже приведен класс концентратора из этого приложения:
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
Предположим, что вы хотите хранить сообщения чата на сервере перед отправкой. Можно определить интерфейс, который абстрагирует эту функциональность и использовать di для внедрения интерфейса в ChatHub класс.
public interface IChatRepository
{
void Add(string name, string message);
// Other methods not shown.
}
public class ChatHub : Hub
{
private IChatRepository _repository;
public ChatHub(IChatRepository repository)
{
_repository = repository;
}
public void Send(string name, string message)
{
_repository.Add(name, message);
Clients.All.addMessage(name, message);
}
Единственная проблема заключается в том, что приложение SignalR не создает непосредственно центры; SignalR создает их для вас. По умолчанию SignalR ожидает, что класс концентратора имеет конструктор без параметров. Однако вы можете легко зарегистрировать функцию для создания экземпляров концентратора и использовать эту функцию для выполнения DI. Зарегистрируйте функцию, вызвав GlobalHost.DependencyResolver.Register.
protected void Application_Start()
{
GlobalHost.DependencyResolver.Register(
typeof(ChatHub),
() => new ChatHub(new ChatMessageRepository()));
RouteTable.Routes.MapHubs();
// ...
}
Теперь SignalR будет вызывать эту анонимную функцию каждый раз, когда ему потребуется создать экземпляр ChatHub.
Контейнеры IoC
Предыдущий код хорошо подходит для простых случаев. Но вы все еще должны были написать следующее:
... new ChatHub(new ChatMessageRepository()) ...
В сложном приложении со многими зависимостями может потребоваться написать много кода "связывания". Этот код может быть трудно поддерживать, особенно если зависимости имеют вложенную структуру. Кроме того, трудно выполнить модульное тестирование.
Одним из решений является использование контейнера IoC. Контейнер IoC — это программный компонент, отвечающий за управление зависимостями. Вы регистрируете типы в контейнере, а затем используете контейнер для создания объектов. Контейнер автоматически определяет отношения зависимостей. Многие контейнеры IoC также позволяют управлять такими объектами, как время существования объекта и область.
Замечание
"IoC" обозначает "инверсию управления", что является общим шаблоном, в котором фреймворк вызывает код приложения. Контейнер IoC создает объекты для вас, что "инвертирует" обычный поток управления.
Использование контейнеров IoC в SignalR
Приложение Чата, вероятно, слишком просто, чтобы воспользоваться контейнером IoC. Вместо этого давайте рассмотрим пример StockTicker .
Пример StockTicker определяет два основных класса:
-
StockTickerHub: класс концентратора, который управляет клиентскими подключениями. -
StockTicker: синглтон, который хранит котировки акций и периодически обновляет их.
StockTickerHub содержит ссылку на StockTicker одноэлементный объект, в то время как StockTicker содержит ссылку на IHubConnectionContext для объекта StockTickerHub. Он использует этот интерфейс для взаимодействия с StockTickerHub экземплярами. (Дополнительные сведения см. в разделе "Широковещательная передача сервера" с ASP.NET SignalR.)
Контейнер IoC можно использовать для немного разрешения этих зависимостей. Во-первых, давайте упростим StockTickerHub и StockTicker классы. В следующем коде я закомментировал части, которые нам не нужны.
Удалите конструктор без параметров из StockTicker. Вместо этого мы всегда будем использовать DI для создания узла.
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly StockTicker _stockTicker;
//public StockTickerHub() : this(StockTicker.Instance) { }
public StockTickerHub(StockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
// ...
Для StockTicker удалите одноэлементный экземпляр. Позже мы будем использовать контейнер IoC для управления временем существования StockTicker. Кроме того, сделайте конструктор общедоступным.
public class StockTicker
{
//private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
// () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
// Important! Make this constructor public.
public StockTicker(IHubConnectionContext clients)
{
if (clients == null)
{
throw new ArgumentNullException("clients");
}
Clients = clients;
LoadDefaultStocks();
}
//public static StockTicker Instance
//{
// get
// {
// return _instance.Value;
// }
//}
Затем мы можем рефакторизировать код, создав интерфейс для StockTicker. Мы будем использовать этот интерфейс для развязки StockTickerHub от класса StockTicker.
Visual Studio упрощает рефакторинг. Откройте файл StockTicker.cs, щелкните правой кнопкой мыши на StockTicker объявлении класса и выберите Рефакторинг ... Извлечение интерфейса.
В диалоговом окне "Извлечение интерфейса" нажмите кнопку "Выбрать все". Оставьте остальные значения по умолчанию. Нажмите кнопку ОК.
Visual Studio создает новый интерфейс с именем IStockTicker, а также изменяет StockTicker, чтобы он наследовался от IStockTicker.
Откройте файл IStockTicker.cs и измените интерфейс на общедоступный.
public interface IStockTicker
{
void CloseMarket();
IEnumerable<Stock> GetAllStocks();
MarketState MarketState { get; }
void OpenMarket();
void Reset();
}
В классе StockTickerHub измените два экземпляра StockTicker на IStockTicker:
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly IStockTicker _stockTicker;
public StockTickerHub(IStockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
Создание интерфейса IStockTicker не является строго необходимым, но я хотел показать, как DI может помочь уменьшить степень связности между компонентами в вашем приложении.
Добавление библиотеки Ninject
Существует множество контейнеров IoC с открытым кодом для .NET. В этом руководстве я буду использовать Ninject. (Другие популярные библиотеки включают Castle Windsor, Spring.Net, Autofac, Unity и StructureMap.)
Используйте диспетчер пакетов NuGet для установки библиотеки Ninject. В Visual Studio в меню "Сервис" выберитеконсоль диспетчера> пакетов NuGet. В окне консоли диспетчера пакетов введите следующую команду:
Install-Package Ninject -Version 3.0.1.10
Замена сопоставителя зависимостей SignalR
Чтобы использовать Ninject в SignalR, создайте класс, производный от DefaultDependencyResolver.
internal class NinjectSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IKernel _kernel;
public NinjectSignalRDependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
public override object GetService(Type serviceType)
{
return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType));
}
}
Этот класс переопределяет методы GetService и GetServicesdefaultDependencyResolver. SignalR вызывает эти методы для создания различных объектов во время выполнения, включая экземпляры концентраторов, а также различные сервисы, используемые внутри SignalR.
- Метод GetService создает один экземпляр типа. Переопределите этот метод, чтобы вызвать метод TryGet ядра Ninject. Если этот метод возвращает значение NULL, вернитесь к сопоставительу по умолчанию.
- Метод GetServices создает коллекцию объектов указанного типа. Переопределите этот метод, чтобы объединить результаты из Ninject с результатами из сопоставителя по умолчанию.
Настройка привязок Ninject
Теперь мы будем использовать Ninject для объявления привязок типов.
Откройте файл RegisterHubs.cs. В методе RegisterHubs.Start создайте контейнер Ninject, который Ninject называет ядро.
var kernel = new StandardKernel();
Создайте экземпляр пользовательского сопоставителя зависимостей:
var resolver = new NinjectSignalRDependencyResolver(kernel);
Создайте привязку следующим образом с помощью IStockTicker.
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
Этот код говорит о двух вещах. Во-первых, всякий раз, когда приложению требуется IStockTicker, ядро должно создать экземпляр StockTicker. Во-вторых, класс StockTicker должен быть создан как объект-одиночка. Ninject создаст один экземпляр объекта и возвращает один и тот же экземпляр для каждого запроса.
Создайте привязку для IHubConnectionContext следующим образом:
kernel.Bind<IHubConnectionContext>().ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
Этот код создает анонимную функцию, которая возвращает IHubConnection. Метод WhenInjectedInto сообщает Ninject, что эту функцию следует использовать только при создании IStockTicker инстанций. Причина заключается в том, что SignalR создает экземпляры IHubConnectionContext внутренне, и мы не хотим переопределить, как SignalR создает их. Эта функция применяется только к нашему StockTicker классу.
Передайте сопоставитель зависимостей в метод MapHubs :
RouteTable.Routes.MapHubs(config);
Теперь SignalR будет использовать сопоставитель, указанный в MapHubs, вместо сопоставителя по умолчанию.
Ниже приведен полный список кода для RegisterHubs.Start.
public static class RegisterHubs
{
public static void Start()
{
var kernel = new StandardKernel();
var resolver = new NinjectSignalRDependencyResolver(kernel);
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()
.InSingletonScope();
kernel.Bind<IHubConnectionContext>().ToMethod(context =>
resolver.Resolve<IConnectionManager>().
GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
var config = new HubConfiguration()
{
Resolver = resolver
};
// Register the default hubs route: ~/signalr/hubs
RouteTable.Routes.MapHubs(config);
}
}
Чтобы запустить приложение StockTicker в Visual Studio, нажмите клавишу F5. В окне браузера перейдите в раздел http://localhost:*port*/SignalR.Sample/StockTicker.html.
Приложение имеет точно те же функции, что и раньше. (Описание см. в разделе "Трансляция сервера" с ASP.NET SignalR.) Мы не изменили поведение, но просто упростили тестирование, обслуживание и развитие кода.