• SignalR中的依赖注入


    什么是依赖注入?

    如果你已经熟悉依赖注入可以跳过此节。

    依赖注入 (DI) 模式下,对象并不为自身的依赖负责。 下边的例子是一个主动 DI. 假设你有个对象需要消息日志。你可能定义了一个日志接口:

    C#
    interface ILogger 
    {
        void LogMessage(string message);
    }
    

    在你的对象中,你可以创建一个 ILogger来记录消息。

    C#
    // 不用依赖注入。
    class SomeComponent
    {
        ILogger _logger = new FileLogger(@"C:logslog.txt");
    
        public void DoSomething()
        {
            _logger.LogMessage("DoSomething");
        }
    }
    

    可以工作,但不是最好的设计。如果你想将FileLogger换成其它的ILogger 实现, 你就得修改 SomeComponent。假如有一堆的对象使用 FileLogger, 你就得将所有的对象都改一遍,或者学决定将 FileLogger形成单例模式,你依旧需要整个程序的修改。

    更好的做法是将 ILogger i注入到对象,比如通过构造函数:

    C#
    // 使用依赖注入.
    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 的实现而不更改依赖的哪个对象。

    C#
    var logger = new TraceLogger(@"C:logslog.etl");
    var someComponent = new SomeComponent(logger);
    

    这个模式叫 构造函数注入. 另一种模式是设置注入,在需要的地方可以通过设置器方法或属性来设置依赖。

    SignalR中简单依赖注入

    细看一下聊天程序教程 Getting Started with SignalR. 下边是这个程序的Hub类:

    C#
    public class ChatHub : Hub
    {
        public void Send(string name, string message)
        {
            Clients.All.addMessage(name, message);
        }
    }
    

    假设你想把聊天的信息在发送前先存下来。你可以定义一个接口来抽象这些功能,然后使用 DI 把这个接口注入到ChatHub 类中。

    C#
    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来注册这个函数。

    C#
    public void Configuration(IAppBuilder app)
    {
    	GlobalHost.DependencyResolver.Register(
    		typeof(ChatHub), 
    		() => new ChatHub(new ChatMessageRepository()));
    
    	App.MapSignalR();
    
    	// ...
    }
    

    现在SignalR就会在你需要创建 ChatHub 实例的时候来调用这个匿名函数。

    IoC 容器

    上边的代码在简单的场合下已经不错了,但你还是得这样写:

    C#
    ... 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容器来解开一点依赖。首先,我们简化StockTickerHubStockTicker类。在下边的代码中,我注释了部分我们用不到的代码。

    删除StockTickerHub没有参数的构造器。取而代之,我们一般DI来创建hub。

    C#
    [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。

    C#
    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.

    C#
    public interface IStockTicker
    {
        void CloseMarket();
        IEnumerable<Stock> GetAllStocks();
        MarketState MarketState { get; }
        void OpenMarket();
        void Reset();
    }
    

    StockTickerHub 中, 将StockTicker 的两个实例改为 IStockTicker:

    C#
    [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 WindsorSpring.Net,AutofacUnity, 和StructureMap.)

    使用NuGet 包管理器安装 Ninject 库. 在Visual Studio中, 打开工具菜单选择库包管理器 | 包管理器命令行。在包管理器命令行窗口,输入以下命令:

    PowerShell
    Install-Package Ninject -Version 3.0.1.10
    

    替换SignalR 依赖处理器

    要让 Ninject 同 SignalR一起工作,创建一个类继承于DefaultDependencyResolver。

    C#
    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.

    C#
    var kernel = new StandardKernel();
    

    创建自定义依赖处理器的实例:

    C#
    var resolver = new NinjectSignalRDependencyResolver(kernel);
    

    创建IStockTicker 的绑定,如下:

    C#
    kernel.Bind<IStockTicker>()
        .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()  // Bind to StockTicker.
        .InSingletonScope();  // Make it a singleton object.
    

    这个代码说了两件事。首先,程序什么时候需要IStockTicker, kernel需要创建一个StockTicker的实例。其次, StockTicker类需要创建为单例对象。 Ninject创建对象的一个实例,并返回每个请求相同的实例。

    创建IHubConnectionContext的绑定如下:

    C#
    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 方法:

    C#
    var config = new HubConfiguration();
    config.Resolver = resolver;
    Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
    

    更新示例的Startup类的Startup.ConfigureSignalR方法参数:

    C#
    public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
    {
        app.MapSignalR(config);
    }
    

    现在SignalR将会使用MapSignalR中指定的处理器,替代默认的处理器。

    这里列出了 Startup.Configuration的完整代码:

    C#
    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.) 我们没有改变行为,只是将代码变得容易测试、维护和进化。

  • 相关阅读:
    操纵持久化对象
    面阵和线扫工业相机选型
    线扫描镜头简介及选型
    Halcon的anisotropic_diffusion()函数,用于对图像进行各向异性散射增强处理
    VB、C#等高级语言与三菱PLC(Q系列、L系列、FX系列、A系列)串口、以太网通讯的DLL及源代码
    Halcon学习笔记之支持向量机
    C#中使用byte[]数据,生成Bitmap(256色 灰度 BMP位图)源代码
    Halcon学习SVM
    利用MLP(多层感知器)创建一个新的OCR分级器
    Halcon中OCR的实现及关键函数解析
  • 原文地址:https://www.cnblogs.com/icoolno1/p/6941702.html
Copyright © 2020-2023  润新知