Injektáž závislostí v centrech SignalR
Upozornění
Tato dokumentace není určená pro nejnovější verzi SignalR. Podívejte se na ASP.NET Core SignalR.
Verze softwaru použité v tomto tématu
- Visual Studio 2013
- .NET 4.5
- SignalR verze 2
Předchozí verze tohoto tématu
Informace o starších verzích služby SignalR najdete v tématu Starší verze služby SignalR.
Dotazy a komentáře
Pošlete nám prosím zpětnou vazbu k tomu, jak se vám tento kurz líbil a co bychom mohli vylepšit v komentářích v dolní části stránky. Pokud máte dotazy, které přímo nesouvisejí s kurzem, můžete je publikovat na fóru ASP.NET SignalR nebo StackOverflow.com.
Injektáž závislostí je způsob, jak odebrat pevně zakódované závislosti mezi objekty, což usnadňuje nahrazení závislostí objektu, a to buď pro testování (pomocí napodobených objektů), nebo změnu chování za běhu. Tento kurz ukazuje, jak provádět injektáž závislostí v centrech SignalR. Ukazuje také, jak používat kontejnery IoC se službou SignalR. Kontejner IoC je obecná architektura pro injektáž závislostí.
Co je injektáž závislostí?
Pokud už injektáž závislostí znáte, tuto část přeskočte.
Injektáž závislostí (DI) je vzor, ve kterém objekty nejsou zodpovědné za vytváření vlastních závislostí. Tady je jednoduchý příklad, jak di motivovat. Předpokládejme, že máte objekt, který potřebuje protokolovat zprávy. Můžete definovat rozhraní protokolování:
interface ILogger
{
void LogMessage(string message);
}
V objektu můžete vytvořit ILogger
protokol pro protokolování zpráv:
// Without dependency injection.
class SomeComponent
{
ILogger _logger = new FileLogger(@"C:\logs\log.txt");
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
To funguje, ale není to nejlepší návrh. Pokud chcete nahradit FileLogger
jinou ILogger
implementací, budete muset upravit SomeComponent
. Předpokládejme, že mnoho dalších objektů používá FileLogger
, budete muset změnit všechny z nich. Nebo pokud se rozhodnete udělat FileLogger
jednoúčelový, budete také muset provést změny v celé aplikaci.
Lepším přístupem je "vložit" ILogger
objekt do objektu , například pomocí argumentu konstruktoru:
// 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");
}
}
Objekt teď není zodpovědný za výběr, který ILogger
se má použít. Implementace můžete přepínat ILogger
beze změny objektů, které na ní závisejí.
var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);
Tento vzor se nazývá injektáž konstruktoru. Dalším vzorem je injektáž setteru, kde nastavíte závislost prostřednictvím metody nebo vlastnosti setter.
Jednoduchá injektáž závislostí v SignalR
Zvažte aplikaci Chat z kurzu Začínáme s SignalR. Tady je třída centra z této aplikace:
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
Předpokládejme, že chcete ukládat chatovací zprávy na server před jejich odesláním. Můžete definovat rozhraní, které abstrahuje tuto funkci, a použít DI k vložení rozhraní do ChatHub
třídy.
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);
}
Jediným problémem je, že aplikace SignalR nevytvoří přímo centra. SignalR je vytvoří za vás. SignalR ve výchozím nastavení očekává, že třída rozbočovače bude mít konstruktor bez parametrů. Můžete ale snadno zaregistrovat funkci pro vytvoření instancí centra a tuto funkci použít k provádění DI. Zaregistrujte funkci voláním GlobalHost.DependencyResolver.Register.
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.Register(
typeof(ChatHub),
() => new ChatHub(new ChatMessageRepository()));
App.MapSignalR();
// ...
}
SignalR teď vyvolá tuto anonymní funkci vždy, když potřebuje vytvořit ChatHub
instanci.
Kontejnery IoC
Předchozí kód je v jednoduchých případech v pořádku. Ale stále jste museli napsat toto:
... new ChatHub(new ChatMessageRepository()) ...
Ve složité aplikaci s mnoha závislostmi může být potřeba napsat hodně tohoto "propojovacího" kódu. Údržba tohoto kódu může být náročná, zejména pokud jsou závislosti vnořené. Testování jednotek je také obtížné.
Jedním z řešení je použití kontejneru IoC. Kontejner IoC je softwarová komponenta, která zodpovídá za správu závislostí. V kontejneru zaregistrujete typy a pak pomocí kontejneru vytvoříte objekty. Kontejner automaticky zjistí vztahy závislostí. Mnoho kontejnerů IoC také umožňuje řídit věci, jako je životnost objektů a rozsah.
Poznámka
"IoC" je zkratka pro "inverzi řízení", což je obecný vzor, kdy architektura volá kód aplikace. Kontejner IoC vytvoří vaše objekty za vás, což "invertuje" obvyklý tok řízení.
Použití kontejnerů IoC v SignalR
Chatovací aplikace je pravděpodobně příliš jednoduchá na to, aby z kontejneru IoC profitovat. Místo toho se podívejme na ukázku StockTicker .
Ukázka StockTicker definuje dvě hlavní třídy:
StockTickerHub
: Třída centra, která spravuje připojení klientů.StockTicker
: Jeden objekt, který uchovává ceny akcií a pravidelně je aktualizuje.
StockTickerHub
obsahuje odkaz na StockTicker
singleton, zatímco StockTicker
obsahuje odkaz na IHubConnectionContext pro StockTickerHub
. Toto rozhraní používá ke komunikaci s StockTickerHub
instancemi. (Další informace najdete v tématu Všesměrové vysílání serveru s ASP.NET SignalR.)
Pomocí kontejneru IoC můžeme tyto závislosti trochu rozvést. Nejprve si zjednodušíme StockTickerHub
třídy a StockTicker
. V následujícím kódu jsem okomentoval části, které nepotřebujeme.
Odeberte konstruktor bez parametrů z StockTickerHub
. Místo toho vždy použijeme di k vytvoření centra.
[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;
}
// ...
V případě StockTicker odeberte instanci typu singleton. Později použijeme kontejner IoC k řízení životnosti nástroje StockTicker. Konstruktor také zpřístupněte veřejnosti.
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<dynamic> clients)
{
if (clients == null)
{
throw new ArgumentNullException("clients");
}
Clients = clients;
LoadDefaultStocks();
}
//public static StockTicker Instance
//{
// get
// {
// return _instance.Value;
// }
//}
Dále můžeme refaktorovat kód vytvořením rozhraní pro StockTicker
. Toto rozhraní použijeme k oddělení StockTickerHub
třídy StockTicker
.
Visual Studio usnadňuje tento druh refaktoringu. Otevřete soubor StockTicker.cs, klikněte pravým tlačítkem na StockTicker
deklaraci třídy a vyberte Refaktorovat ... Extrahovat rozhraní.
V dialogovém okně Extrahovat rozhraní klikněte na Vybrat vše. Zbytek ponechte ve výchozím nastavení. Klikněte na OK.
Visual Studio vytvoří nové rozhraní s názvem IStockTicker
a také změny StockTicker
odvozené z IStockTicker
.
Otevřete soubor IStockTicker.cs a změňte rozhraní na veřejné.
public interface IStockTicker
{
void CloseMarket();
IEnumerable<Stock> GetAllStocks();
MarketState MarketState { get; }
void OpenMarket();
void Reset();
}
StockTickerHub
Ve třídě změňte dvě instance na 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;
}
Vytvoření IStockTicker
rozhraní není nezbytně nutné, ale chtěl jsem ukázat, jak může di di pomoci omezit propojení mezi komponentami ve vaší aplikaci.
Přidání knihovny Ninject
Existuje mnoho opensourcových kontejnerů IoC pro .NET. Pro účely tohoto kurzu použiju Ninject. (Mezi další oblíbené knihovny patří Castle Windsor, Spring.Net, Autofac, Unity a StructureMap.)
Pomocí Správce balíčků NuGet nainstalujte knihovnu Ninject. V sadě Visual Studio v nabídce Nástroje vyberteKonzola Správce>balíčků NuGet. V okně konzoly Správce balíčků zadejte následující příkaz:
Install-Package Ninject -Version 3.0.1.10
Nahrazení překladače závislostí SignalR
Chcete-li použít Ninject v signalR, vytvořte třídu, která je odvozena z 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));
}
}
Tato třída přepíše Metody GetService a GetServicesDefaultDependencyResolver. SignalR volá tyto metody k vytváření různých objektů za běhu, včetně instancí centra, a také různých služeb používaných interně službou SignalR.
- Metoda GetService vytvoří jednu instanci typu. Přepište tuto metodu volání metody TryGet jádra Ninject. Pokud tato metoda vrátí hodnotu null, vraťte se k výchozímu překladače.
- Metoda GetServices vytvoří kolekci objektů zadaného typu. Přepište tuto metodu a zřetězíte výsledky z Ninjectu s výsledky z výchozího překladače.
Konfigurace vazeb Ninject
Teď použijeme Ninject k deklaraci vazeb typu.
Otevřete třídu Startup.cs vaší aplikace (kterou jste buď vytvořili ručně podle pokynů k balíčku v readme.txt
, nebo kterou jste vytvořili přidáním ověřování do projektu). V metodě Startup.Configuration
vytvořte kontejner Ninject, který Ninject volá jádro.
var kernel = new StandardKernel();
Vytvořte instanci našeho vlastního překladače závislostí:
var resolver = new NinjectSignalRDependencyResolver(kernel);
Vytvořte vazbu pro IStockTicker
následujícím způsobem:
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
Tento kód říká dvě věci. Za prvé, kdykoli aplikace potřebuje IStockTicker
, jádro by mělo vytvořit instanci .StockTicker
Za druhé, StockTicker
třída by měla být vytvořena jako jeden objekt. Ninject vytvoří jednu instanci objektu a pro každý požadavek vrátí stejnou instanci.
Vytvořte vazbu pro IHubConnectionContext následujícím způsobem:
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
Tento kód vytvoří anonymní funkci, která vrátí funkci IHubConnection. Metoda WhenInjectedInto říká Ninjectu, aby tuto funkci používal pouze při vytváření IStockTicker
instancí. Důvodem je, že Služba SignalR interně vytváří instance IHubConnectionContext a nechceme přepsat způsob, jakým je SignalR vytváří. Tato funkce platí jenom pro naši StockTicker
třídu.
Předejte překladač závislostí do metody MapSignalR přidáním konfigurace centra:
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
Aktualizujte metodu Startup.ConfigureSignalR ve třídě Startup ukázky pomocí nového parametru:
public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
{
app.MapSignalR(config);
}
Teď bude SignalR místo výchozího překladače používat překladač zadaný v MapSignalR.
Tady je úplný výpis kódu pro Startup.Configuration
.
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
var kernel = new StandardKernel();
var resolver = new NinjectSignalRDependencyResolver(kernel);
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
}
}
Pokud chcete spustit aplikaci StockTicker v sadě Visual Studio, stiskněte klávesu F5. V okně prohlížeče přejděte na http://localhost:*port*/SignalR.Sample/StockTicker.html
.
Aplikace má úplně stejné funkce jako předtím. (Popis najdete v tématu Všesměrové vysílání serveru s ASP.NET SignalR.) Nezměnili jsme chování. jen usnadnil testování, údržbu a vývoj kódu.