SignalR 中的相依性插入
作者: Patrick Fletcher
警告
本檔不適用於最新版的 SignalR。 請查看ASP.NET Core SignalR。
本主題中使用的軟體版本
- Visual Studio 2013
- .NET 4.5
- SignalR 第 2 版
本主題的舊版
如需舊版 SignalR 的相關資訊,請參閱 SignalR 舊版。
問題和批註
請留下您喜歡本教學課程的意見反應,以及我們可以在頁面底部的批註中改善的內容。 如果您有與教學課程不直接相關的問題,您可以將問題張貼到 ASP.NET SignalR 論壇 或 StackOverflow.com。
相依性插入是移除物件之間硬式編碼相依性的方法,可讓您更輕鬆地取代物件的相依性、使用模擬物件測試 () 或變更執行時間行為。 本教學課程說明如何在 SignalR 中樞上執行相依性插入。 它也會示範如何搭配 SignalR 使用 IoC 容器。 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");
}
}
這可運作,但不是最佳設計。 如果您想要以另一個 ILogger
實作取代 FileLogger
,則必須修改 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);
此模式稱為建 構函式插入。 另一個模式是 setter 插入,您可以在其中透過 setter 方法或屬性來設定相依性。
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來註冊函式。
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.Register(
typeof(ChatHub),
() => new ChatHub(new ChatMessageRepository()));
App.MapSignalR();
// ...
}
現在 SignalR 會在需要建立 ChatHub
實例時叫用此匿名函式。
IoC 容器
上述程式碼適用于簡單案例。 但您仍必須撰寫下列專案:
... new ChatHub(new ChatMessageRepository()) ...
在具有許多相依性的複雜應用程式中,您可能需要撰寫許多「連接」程式碼。 此程式碼可能難以維護,特別是如果相依性是巢狀的。 也很難進行單元測試。
其中一個解決方案是使用 IoC 容器。 IoC 容器是負責管理相依性的軟體元件。您會向容器註冊類型,然後使用容器來建立物件。 容器會自動找出相依性關聯。 許多 IoC 容器也可讓您控制物件存留期和範圍等專案。
注意
「IoC」 代表「控制反轉」,這是架構呼叫應用程式程式碼的一般模式。 IoC 容器會為您建構物件,這會「反轉」一般控制流程。
在 SignalR 中使用 IoC 容器
聊天應用程式可能太簡單,無法受益于 IoC 容器。 相反地,讓我們看看 StockTicker 範例。
StockTicker 範例會定義兩個主要類別:
StockTickerHub
:管理用戶端連線的中樞類別。StockTicker
:保留股票價格並定期更新它們的單一。
StockTickerHub
會保留單一的 StockTicker
參考,同時 StockTicker
保留 的 IHubConnectionCoNtextStockTickerHub
參考。 它會使用此介面來與 StockTickerHub
實例通訊。 (如需詳細資訊,請參閱使用 ASP.NET SignalR.) 進行伺服器廣播
我們可以使用 IoC 容器將這些相依性取消糾纏一些。 首先,讓我們簡化 StockTickerHub
和 StockTicker
類別。 在下列程式碼中,我已批註化我們不需要的部分。
從 移除無參數建構函 StockTickerHub
式。 相反地,我們將一律使用 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<dynamic> clients)
{
if (clients == null)
{
throw new ArgumentNullException("clients");
}
Clients = clients;
LoadDefaultStocks();
}
//public static StockTicker Instance
//{
// get
// {
// return _instance.Value;
// }
//}
接下來,我們可以藉由建立 的 StockTicker
介面來重構程式碼。 我們將使用此介面將 與 StockTicker
類別分離 StockTickerHub
。
Visual Studio 讓這類重構變得容易。 開啟 StockTicker.cs 檔案,以滑鼠右鍵按一下 StockTicker
類別宣告,然後選取 [重構 ... 擷取介面。
在 [ 擷取介面 ] 對話方塊中,按一下 [ 全選]。 其他部分保留預設值。 按一下 [確定]。
Visual Studio 會建立名為 IStockTicker
的新介面,也會變更 StockTicker
為衍生自 IStockTicker
。
開啟 IStockTicker.cs 檔案,並將介面變更為 public。
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 程式庫
.NET 有許多開放原始碼 IoC 容器。 在本教學課程中,我將使用 Ninject。 (其他熱門程式庫包括Spring.Net、Autofac、Unity和StructureMap.)
使用 NuGet 套件管理員來安裝 Ninject 程式庫。 在 Visual Studio 中,從 [工具] 功能表選取[NuGet 套件管理員套件管理員>主控台]。 在 [Package Manager Console] 視窗中,輸入下列命令:
Install-Package Ninject -Version 3.0.1.10
取代 SignalR 相依性解析程式
若要在 SignalR 中使用 Ninject,請建立衍生自 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));
}
}
這個類別會覆寫DefaultDependencyResolver的GetService和GetServices方法。 SignalR 會呼叫這些方法,在執行時間建立各種物件,包括中樞實例,以及 SignalR 內部使用的各種服務。
- GetService方法會建立類型的單一實例。 覆寫此方法以呼叫 Ninject 核心的 TryGet 方法。 如果該方法傳回 Null,請回復為預設解析程式。
- GetServices方法會建立指定型別的物件集合。 覆寫這個方法,將 Ninject 的結果與預設解析程式的結果串連。
設定 Ninject 系結
現在我們將使用 Ninject 來宣告類型系結。
開啟應用程式的 Startup.cs 類別 (您根據 中的 readme.txt
套件指示手動建立,或藉由將驗證新增至專案) 所建立。 在 方法中 Startup.Configuration
,建立 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(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
此程式碼會建立傳回 IHubConnection的匿名函式。 WhenInjectedInto方法會指示 Ninject 只在建立 IStockTicker
實例時使用此函式。 原因是 SignalR 會在內部建立 IHubConnectionCoNtext 實例,而且我們不想覆寫 SignalR 建立它們的方式。 此函式僅適用于我們的 StockTicker
類別。
藉由新增中樞設定,將相依性解析程式傳遞至 MapSignalR 方法:
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
使用新參數更新範例之 Startup 類別中的 Startup.ConfigureSignalR 方法:
public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
{
app.MapSignalR(config);
}
現在 SignalR 會使用 MapSignalR中指定的解析程式,而不是預設解析程式。
以下是 的完整程式代碼清單 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);
}
}
若要在 Visual Studio 中執行 StockTicker 應用程式,請按 F5。 在瀏覽器視窗中,流覽至 http://localhost:*port*/SignalR.Sample/StockTicker.html
。
應用程式的功能與之前完全相同。 (如需描述,請參閱 伺服器廣播與 ASP.NET SignalR.) 我們尚未變更行為;只是讓程式碼更容易測試、維護和演進。