SignalR中的依赖注入
什么是依赖注入?
如果你已经熟悉依赖注入可以跳过此节。
依赖注入 (DI) 模式下,对象并不为自身的依赖负责。 下边的例子是一个主动 DI. 假设你有个对象需要消息日志。你可能定义了一个日志接口:
interface ILogger
{
void LogMessage(string message);
}
在你的对象中,你可以创建一个 ILogger
来记录消息。
// 不用依赖注入。
class SomeComponent
{
ILogger _logger = new FileLogger(@"C:\logs\log.txt");
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
可以工作,但不是最好的设计。如果你想将FileLogger
换成其它的ILogger
实现, 你就得修改 SomeComponent
。假如有一堆的对象使用 FileLogger
, 你就得将所有的对象都改一遍,或者学决定将 FileLogger形成单例模式,你依旧需要整个程序的修改。
更好的做法是将 ILogger
i注入到对象,比如通过构造函数:
// 使用依赖注入.
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中简单依赖注入
细看一下聊天程序教程 Getting Started with SignalR. 下边是这个程序的Hub类:
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 应用并不直接创建hub; SignalR 会为你创建。默认情况下,SignalR 期望一个有参数的构造方法。然而你可以很容易的注册一个函数来创建这个hub 实例,然后用这个函数来实现 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
: hub 类,管理客户端连接。StockTicker
: 一个单例用于存放股票价格并定时更新。
StockTickerHub
放了一个 StockTicker
单例的引用,同时 StockTicker
放了一个 StockTickerHub的IHubConnectionContext引用。使用接口与
StockTickerHub实例进行通讯。
(更多信息见: Server Broadcast with ASP.NET SignalR.)
我们用 IoC容器来解开一点依赖。首先,我们简化StockTickerHub
和StockTicker类。在下边的代码中,我注释了部分我们用不到的代码。
删除StockTickerHub没有参数的构造器。取而代之,我们一般DI来创建hub。
[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。
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. (其它流行的库包括 Castle Windsor, Spring.Net,Autofac, Unity, 和StructureMap.)
使用NuGet 包管理器安装 Ninject 库. 在Visual Studio中, 打开工具菜单选择库包管理器 | 包管理器命令行。在包管理器命令行窗口,输入以下命令:
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));
}
}
这个类重写DefaultDependencyResolver的GetService 和GetServices 方法 。 SignalR 在运行时调用这些方法创建各种对象,包括hub 实例,以及SignalR内部的各类服务。
- GetService创建类型的单个实例。重写这个方法调用Ninject内核的TryGet方法。如果这个方法返回null, 则回到默认的处理器。
- GetServices 方法创建特定类型的对象集合。重写这个方法将Ninject的结果和默认处理器的结果联系起来。
配置Ninject 绑定
现在我们使用 Ninject来申明类型绑定
打开应用程序的 Startup.cs 类文件(that you either created manually as per the package instructions in readme.txt
, or that was created by adding authentication to your project). 在 Startup.Configuration
方法中, 创建 Ninject 容器, Ninject 叫做 kernel.
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
, kernel需要创建一个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
类。
增加一个hub配置将依赖处理器传给 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 http://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中 安 F5运行StockTicker程序。在浏览器窗口中,导航到 http://localhost:*port*/SignalR.Sample/StockTicker.html
.
这个程序的功能和前边完全一样。 (描述内容见: Server Broadcast with ASP.NET SignalR.) 我们没有改变行为,只是将代码变得容易测试、维护和进化。
SignalR中的依赖注入的更多相关文章
- ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入
原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...
- 在WPF中使用依赖注入的方式创建视图
在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...
- ASP.NET Core 在 JSON 文件中配置依赖注入
前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...
- 在.NET Core控制台程序中使用依赖注入
之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...
- ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
- ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理
ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
随机推荐
- 飞檐走壁navMesh
http://www.manew.com/thread-106030-1-1.html
- 绘图神器-matplotlib入门
这次,让我们使用一个非常有名且十分有趣的玩意儿来完成今天的任务,它就是jupyter. 一.安装jupyter matplotlib入门之前,先安装好jupyter.这里只提供最为方便快捷的安装方式: ...
- windows环境下MySQL-5.7.12-winx64下载安装与配置
系统:64位Win-7 官网压缩包:mysql-5.7.12-winx64.zip 前后花了一些时间,以前都是下载软件直接安装在本地,现在这个不一样,下载压缩包后要解压缩到安装目录,然后在控制台下配置 ...
- [html/js]点击标题出现下拉列表
效果 初始 点击后 参考代码 <!DOCTYPE html> <html> <head> <title>Layer group example</ ...
- HDU 5222 ——Exploration——————【并查集+拓扑排序判有向环】
Exploration Time Limit: 30000/15000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)T ...
- 【linux相识相知】网络属性配置
当我们拥有一个崭新的计算机的时候,第一步恐怕都是迫不及待的下载各种软件,看视频,听音乐等,这里的关键的一点是要有网络.现在的个人计算机大部分都是windows操作系统的,接入网络网络很简单,插上网线也 ...
- 人民币金额大小写Js转换
/** * 数字转中文 * @param dValue * @returns */ function chineseNumber(dValue) { var maxDec = 2; // 验证输入金额 ...
- Csharp:字符串操作
public class StringControl { /// <summary> /// 客户端浏览器 /// http://en.wikipedia.org/wiki/Web_bro ...
- 洛谷P1730 最小密度路径(floyd)
题意 题目链接 Sol zz floyd. 很显然的一个dp方程\(f[i][j][k][l]\)表示从\(i\)到\(j\)经过了\(k\)条边的最小权值 可以证明最优路径的长度一定\(\leqsl ...
- Java设计模式—门面模式(带案例分析)
1.门面模式的定义: 门面模式(Facade Pattern)也叫做外观模式,是一种比较常用的封装模式,其定义如下: 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行.门面模式 ...