在net Core3.1上基于winform实现依赖注入实例

目录

1.背景

net core3.1是微软LTS长期3年支持版本,正式发布于2019-12-03,并且在windows平台上支持了Winfrom跟WPF桌面应用。本文介绍了使用Winform时的第一步,将应用层以及ORM涉及到的DBconfig,仓储层等依赖注入到容器中,并通过构造函数法从容器中调用实例,供给各窗体控件使用。

备注:本文的依赖注入讲解基于微软原生自带的DI,通过Ninject或者AutoFac可自行仿照操作,原理相通。

2.依赖注入

2.1依赖注入是什么?

依赖注入是通过反转控制(IOC),设计模式属于代理模式+工厂模式,由serviceProvider根据实例接口或者实例类型调用,注入时生命周期的设置,控制实例化及配置实例生命周期,并返回实例给程序员调用,从而达到解放程序员的生产力,不用再去new 一个个实例,也不用去考虑实例之间的依赖关系,也不用去考虑实例的生命周期。实现,分为三个阶段,第一,程序员将服务注入服务容器阶段,第二程序员DI实例调用阶段,第三serviceProvider服务管理者根据注入时的配置返回给程序对应的实例以及配置好实例的生命周期。

一张图就可以理解依赖注入实例调用过程

图片来源出处,感谢作者。

这里再向读者做个说明ServiceCollection是服务容器,serviceProvider是服务管理者,管理着服务容器,当程序发送抽象接口,或者类型时,serviceProvider会根据设置好的生命周期,返回需要的实例配置好实例的生命周期给程序员使用。

2.1依赖注入的目的

通过代理模式serviceProvider控制反转,他将持有控制权,将所有需要用到的接口,类型,反射出对应的实例,实例化以及设置好实例的生命周期,然后将控制权返还给程序员,不用再去new 一个个实例,也不用去考虑实例之间的依赖关系,也不用去考虑实例的生命周期,最终目的就是解放程序员的生产力,让程序员更轻松地写程序。

2.2依赖注入带来的好处

2.2.1生命周期的控制

在注入的同时可以设置如下三种生命周期:

  • Transient

    每次注入时,都重新 new 一个新的实例。
  • Scoped

    每个 Request 都重新 new 一个新的实例,同一个 Request 不管经过多少个 Pipeline 都是用同一个实例。
  • Singleton

    被实例化后就不会消失,程序运行期间只会有一个实例。

2.2.1.1 生命周期测试举例

  • 定义同一个例子对应三个不同生命周期的接口
public interface ISample
{
int Id { get; }
} public interface ISampleTransient : ISample
{
} public interface ISampleScoped : ISample
{
} public interface ISampleSingleton : ISample
{
} public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private static int _counter;
private int _id; public Sample()
{
_id = ++_counter;
} public int Id => _id;
}
  • 将对应的服务与接口注册到容器中
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISampleTransient, Sample>();
services.AddScoped<ISampleScoped, Sample>();
services.AddSingleton<ISampleSingleton, Sample>();
// Singleton 也可以用以下方法注册
// services.AddSingleton<ISampleSingleton>(new Sample());
}
}
  • Controller中获取对应DI实例的HashCode
public class HomeController : Controller
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton; public HomeController(
ISampleTransient transient,
ISampleScoped scoped,
ISampleSingleton singleton)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
} public IActionResult Index() {
ViewBag.TransientId = _transient.Id;
ViewBag.TransientHashCode = _transient.GetHashCode(); ViewBag.ScopedId = _scoped.Id;
ViewBag.ScopedHashCode = _scoped.GetHashCode(); ViewBag.SingletonId = _singleton.Id;
ViewBag.SingletonHashCode = _singleton.GetHashCode();
return View();
}
}
  • VewBag 显示组件
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>

可自行做测试,具体可参考此博客

2.2.2 实现了展现层(调用者)与服务类之间的解耦

如上,实例是在HomeController中通过接口来调用实例的,因此修改程序只需要在实例中需改,而不需要在调用层修改。

这符合了6大程序设计原则中的依赖倒置原则:

1.高层模块不应该依赖于低层模块,两者都应该依赖其抽象

展现层Controller没有依赖Model层Sample类,两者都依赖了Sample的接口抽象ISample,ISampleTransient,ISampleScoped,ISampleSingleton.

2.抽象不应该依赖于细节

接口层只定义规范,没有定义细节。

public interface ISample
{
int Id { get; }
} public interface ISampleTransient : ISample
{
} public interface ISampleScoped : ISample
{
} public interface ISampleSingleton : ISample
{
}

3.细节应该依赖于抽象

DI中取实例依赖于接口:

ISampleTransient transient;

服务类的实现也依赖于接口:

public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private static int _counter;
private int _id; public Sample()
{
_id = ++_counter;
} public int Id => _id;
}

2.2.3 开发者不用再去考虑依赖之间的关系

使程序员不用再去考虑各个DI实例之间的依赖,以及new很多个相互依赖的实例。

2.3 依赖注入使用的设计模式

2.3.1 代理模式

在依赖注入的服务调用的地方,容器管理者serviceProvider从程序员手中取得控制权,控制所需服务实例化以及设置好他的生命周期,然后返回给程序员。

2.3.2 工厂模式

根据DI的生命周期设置,根据接口或者类型,生产出各种生命周期的实例,需要注意的是这里有可能是同一实例(scope的单次请求中,或者Transient生命周期),Transient每次产生的都是新的实例。

3.在Net Core 3.1上基于winform实现依赖注入

3.1 Net Core 3.1中对winform的支持。

笔者发现在最新的VS发行版中,能创建winform工程,但却无法打开设计器,也无法打开winform的工具箱。怎么办?

微软官方博客中提到在VS16.5预览版中支持了winform设计器,根据博客中提到,需要在此下载链接下载VS16.5预览版。

NetCore3.1 winform截图如下:

可以看到控件明显比基于dot Net Framework的好看很多,同时,工具箱中的控件很少,微软把一些老的已经有替代的控件删除了,并且以后会慢慢加入一些必要的控件。

3.2 winform依赖注入与net core MVC的不同?

net core MVC容器是自动创建好的,只需要在ConfigureServices方法里配置服务即可。而在Net Core3.1上创建了winform工程之后窗体是new实例,以单例的形式跑的。容器的配置创建,都需要自己来做。

    static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

那如果需要向Form窗体中注入服务就需要在new实例的时候就传入实参。

[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); var services = new ServiceCollection(); ConfigureServices(services); using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
var logg = services.BuildServiceProvider().GetRequiredService<ILogger<Form1>>(); var businessObject = services.BuildServiceProvider().GetRequiredService<IBusinessLayer>(); Application.Run(new Form1(logg, businessObject));
}
}

调用的时候用窗体的构造函数调用服务接口即可。

public partial class Form1 : Form
{
private readonly ILogger _logger; private readonly IBusinessLayer _business;
public Form1(ILogger<Form1> logger, IBusinessLayer business)
{
_logger = logger;
_business = business;
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
try
{
_logger.LogInformation("Form1 {BusinessLayerEvent} at {dateTime}", "Started", DateTime.UtcNow); // Perform Business Logic here
_business.PerformBusiness(); MessageBox.Show("Hello .NET Core 3.0 . This is First Forms app in .NET Core"); _logger.LogInformation("Form1 {BusinessLayerEvent} at {dateTime}", "Ended", DateTime.UtcNow); }
catch (Exception ex)
{
//Log technical exception
_logger.LogError(ex.Message);
//Return exception repsponse here
throw; } }
}

本方法摘自此文

这样至少有两个缺点:

  1. Form1中构造函数的依赖注入实例调用泄露在了他的调用层,这不符合6大程序设计原则中的依赖倒置原则;
  2. 当Form1中需要从DI中增加接口实例调用时,也需要在如下调用代码中增加对应实参。而且实参多了,会很冗长。

    Application.Run(new Form1(logg, businessObject));

3.3 解决3.2的思路

把form的类型也以单例的形式注入到容器中,调用时,获取MainForm类型的服务。这样此服务实例依赖于其他的服务。ServiceProvider容器管理者会自动解决好服务之间的依赖关系,并将对应的服务实例化并根据生命周期设置好,交给程序员去使用。问题完美解决。

此思路有借鉴于以下两篇文章

微软MSDN

stackoverflow

这里向大家重点推荐下stackoverflow,这个基于世界级的程序员论坛,在我遇到很多的疑难杂症,孤立无援的时候,他都会给予我解决问题的思路,方向甚至方案,再次致敬感谢stackoverflow,同时也感谢谷歌。

3.4代码实现

3.4.1 在Program.cs中建立服务注册静态方法

        private static void ConfigureServices(ServiceCollection services)
{
//App
services.ApplicationServiceIoC();
//Infra //Repo
services.InfrastructureORM<DapperIoC>(); //Presentation 其他的窗体也可以注入在此处
services.AddSingleton(typeof(MainForm));
}

这里需要说明的是,笔者这里的IoC是应用层,展现层,仓储层分层注入了,每层都写了ServiceCollection服务容器的静态方法,所以服务可以在各层注入,读者可以不去追究,将自己的服务注入在此即可。

分层注入:

分层注入简单实现

CameraDM_Service注册在了ApplicationServiceIoC,ApplicationServiceIoC注册在了ConfigureServices。这就是我刚说的分层注入每层的依赖。

    public static class ServicesIoC
{
public static void ApplicationServiceIoC(this IServiceCollection services)
{
services.AddScoped(typeof(IServiceBase<>), typeof(ServiceBase<>));
services.AddScoped<ICameraDM_Service, CameraDM_Service>();
}
}

重点关注

将窗体类型注入,当然后续加入其它窗体也可用同样方法进行注入。

services.AddSingleton(typeof(MainForm));

3.4.2 创建服务容器对象

var services = new ServiceCollection();

3.4.3 添加服务注册

 ConfigureServices(services);

此步骤调用的就是3.4.1中的方法。

3.4.4 构建ServiceProvider对象

  var serviceProvider = services.BuildServiceProvider();

3.4.5 运行MainForm服务

向服务管理者请求MainForm类型的实例服务,具体调用过程详见2.1。

Application.Run(serviceProvider.GetService<MainForm>());

这一步是重点,也是winform跟MVC使用上的区别,但是本质却是相同的,都是由serviceProvider管理着WPF,winform或者MVC这些实例以及他们对应的类型,只不过MVC容器已经创建好了,容器管理者serviceProvider也已经创建好了,直接往容器里Add服务即可,而winform,WPF,net core控制台程序需要我们自己去往容器里添加注册服务,并且创建容器管理者serviceProvider。因为ServiceCollection容器是死的,只有创建了serviceProvider容器管理者这个代理角色,容器才能体现出他的价值。而只有serviceProvider,没有ServiceCollection里的服务也是毫无意义的。

3.4.1到3.4.5整体代码如下:

    static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//创建服务容器对象
var services = new ServiceCollection(); //添加服务注册
ConfigureServices(services);
//构建ServiceProvider对象
var serviceProvider = services.BuildServiceProvider();
//向服务管理者请求MainForm类型的实例服务
Application.Run(serviceProvider.GetService<MainForm>());
}
private static void ConfigureServices(ServiceCollection services)
{
//App
services.ApplicationServiceIoC();
//Infra //Repo
services.InfrastructureORM<DapperIoC>(); //Presentation 其他的窗体也可以注入在此处
services.AddSingleton(typeof(MainForm));
}
}

3.4.6构造函数法调用DI实例

    public partial class MainForm : Form
{
ICameraDM_Service _cameraDM_Service;
public MainForm(ICameraDM_Service cameraDM_Service)
{
_cameraDM_Service = cameraDM_Service;
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(_cameraDM_Service.GetAllCameraInfo().ToList().Count().ToString());
var _camera =await _cameraDM_Service.GetAllIncludingTasksAsync();
//textBox1.Text = _camera.ToList().Count().ToString();
var _cameraNo3 = await _cameraDM_Service.GetByIdAsync(3);
textBox1.Text = _cameraNo3.InstallTime.ToString();
}
}

3.5演示效果

点击按钮之后从摄像头服务中获取到了摄像头的数量。

点击确定之后从摄像头服务中获取到了3号摄像头的安装时间。

3.6 如何调用用依赖注入在母窗体中调用子窗体

3.6属于12/23/13:33新增,为了答复1楼网友 编程老油条所问问题。

问题如下:

假设在MainForm中的button1_Click,还需要打开其他窗口,要如何实现?(只能往 MainForm 中传递serviceProvider吗?)

把serviceProvider设计成全局静态的,可设计成单例模式或直接放在Main的属性中,供全局任意子窗体访问获取DI实例即可,当然同时,其他窗体也需要注入到容器中。

3.6.1 注入子窗体

注入生命周期为瞬时的Form1类型。

services.AddTransient(typeof(Form1));

        private static void ConfigureServices(ServiceCollection services)
{
//App
services.ApplicationServiceIoC();
//Infra //Repo
services.InfrastructureORM<DapperIoC>(); //Presentation 其他的窗体也可以注入在此处
services.AddSingleton(typeof(MainForm));
services.AddTransient(typeof(Form1)); }

因为Form1是MainForm的子窗体,而MainForm设置成了单例模式,所以在MainForm中打开Form1是属于同一次请求,姑不能用AddSingleton跟AddScope模式。如果使用以上两种模式,会报如下异常:

比如设置Form1生命周期为单例模式

            services.AddSingleton(typeof(MainForm));
services.AddSingleton(typeof(Form1));

第一次调用正常,

关闭Form1第二次点击MainForm的button1时,报如下异常:

因为是单例模式,我们关闭了Form1,MainForm还在,再次点击button1,会找不到生命周期是单例模式的Form1实例,就会报如上异常。

修改成

services.AddTransient(typeof(Form1));

问题得到完美解决,无论关闭多少次Form1,都能通过MainForm的button1调用打开Form1。

3.6.2 设置全局serviceProvider容器服务管理者

修改serviceProvider为Program静态类的公用属性(全局),以给子窗体或其他winform中的组件来容器服务者获取DI实例

public static IServiceProvider serviceProvider { get; set; }

Program.cs全部代码如下。

    static class Program
{
public static IServiceProvider serviceProvider { get; set; }
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{ Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//创建服务容器对象
var services = new ServiceCollection(); //添加服务注册
ConfigureServices(services);
//构建ServiceProvider对象
serviceProvider = services.BuildServiceProvider(); //向服务管理者请求MainForm类型的实例服务
Application.Run(serviceProvider.GetService<MainForm>());
}
private static void ConfigureServices(ServiceCollection services)
{
//App
services.ApplicationServiceIoC();
//Infra //Repo
services.InfrastructureORM<DapperIoC>(); //Presentation 其他的窗体也可以注入在此处
services.AddSingleton(typeof(MainForm));
services.AddTransient(typeof(Form1)); }
}

3.6.3 MainForm中调用Form1

到Program服务管理者属性手中拿到对应所需类型的设置好生命周期的Form1实例。显示Form1。

    public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
} private async void button1_Click(object sender, EventArgs e)
{
var child = (Form)Program.serviceProvider.GetService((typeof(Form1)));
child.Show();
}
}

3.6.4 构造函数法调用DI实例

Form1调用camera服务

此处同3.5

    public partial class Form1 : Form
{ ICameraDM_Service _cameraDM_Service;
public Form1(ICameraDM_Service cameraDM_Service)
{
_cameraDM_Service = cameraDM_Service;
InitializeComponent(); }
private async void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(_cameraDM_Service.GetAllCameraInfo().ToList().Count().ToString());
}
}

3.6.5效果

4.最后

本来就想写篇短文,谁知道洋洋洒洒还写得有点长。本文如果大家读了有疑惑,请提出来,我会耐心解答;如果知识点上有不妥当不正确或者不同见解的地方,也恳请指出,我同时也很渴望进步。最后祝大家冬至安康,阖家幸福。

在net Core3.1上基于winform实现依赖注入实例的更多相关文章

  1. spring3——IOC之基于XML的依赖注入(DI )

    我们知道spring容器的作用是负责对象的创建和对象间关系的维护,在上一篇博客中我们讲到spring容器会先调用对象的无参构造方法创建一个空值对象,那么接下来容器就会对对象的属性进行初始化,这个初始化 ...

  2. 07 Spring框架 依赖注入(四)基于注解的依赖注入

    前面几节我们都在使用xml进行依赖的注入,但是在实际的开发中我们往往偏爱于使用注解进行依赖注入,因为这样更符合我们人的思维,并且更加快捷,本节就来讲述Spring基于注解的依赖注入: 信息注入注解 @ ...

  3. 详解 Spring 3.0 基于 Annotation 的依赖注入实现(转)

    使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的 ...

  4. 详解 Spring 3.0 基于 Annotation 的依赖注入实现--转载

    使用 @Repository.@Service.@Controller 和 @Component 将类标识为 Bean Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的 ...

  5. 详解 Spring 3.0 基于 Annotation 的依赖注入实现

    Spring 的依赖配置方式与 Spring 框架的内核自身是松耦合设计的.然而,直到 Spring 3.0 以前,使用 XML 进行依赖配置几乎是唯一的选择.Spring 3.0 的出现改变了这一状 ...

  6. spring4——IOC之基于注解的依赖注入(DI )

    spring容器对于Bean的创建和对象属性的依赖注入提供了注解的支持,让我们在开发中能够更加便捷的实现对象的创建和对象属性的依赖注入.一,对于Bean的创建spring容器提供了以下四个注解的支持: ...

  7. Spring:基于注解的依赖注入的使用

    1.什么是pojo?什么是bean? 首先,在之前几篇Spring的介绍文章当中,自己都提到了一个名词叫做POJO类,但是在回顾Spring的注解的使用的时候,去形容java当中的对象还有一个名词是叫 ...

  8. 史上最好用的依赖注入框架Google Guice【转】

    Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IOC).Guice非常小而且快. (其他的依赖注入框架还有Dagger,Spring) Spring ...

  9. Spring 基于构造函数的依赖注入

    基于构造函数依赖注入 sprig通过bean创建对象时,会通过bean提供的参数来选择调用某个构造函数.上例中, <constructor-arg ref="spellChecker& ...

随机推荐

  1. 【TCP/IP网络编程】:01理解网络编程和套接字

    1.网络编程和套接字 网络编程与C语言中的printf函数和scanf函数以及文件的输入输出类似,本质上也是一种基于I/O的编程方法.之所以这么说,是因为网络编程大多是基于套接字(socket,网络数 ...

  2. MySQL基础之Natural Join用法

    Natural join即自然连接,natural join等同于inner join或inner using,其作用是将两个表中具有相同名称的列进行匹配 用https://www.w3resourc ...

  3. 【linux】linux命令lsof和grep命令的配合使用---linux根据端口查看PID,根据PID关键字高亮显示

    lsof命令,根据端口,查看进程PID lsof -i: ps命令+grep命令 --color参数,根据PID查看进程详情,高亮显示关键字 ps -ef | grep --color=always

  4. nikto---基本使用

    目录 一:基本使用 二:调节扫描过程 三:命令行选项 四:配置文件 注意:使用版本:Nikto v2.1.6 功能:Web服务器评估工具,目的在于查找任何类型的Web服务器的各种默认和不安全的文件,配 ...

  5. Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理

    Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理 说明:Java生鲜电商平台中,我们将进一步理解微服务架构的核心要点和实现原理,为读者的实践提供微服务的设计模式,以期让微服务 ...

  6. rsync 未授权访问漏洞

    rsync rsync,remote synchronize顾名思意就知道它是一款实现远程同步功能的软件,它在同步文件的同时,可以保持原来文件的权限.时间.软硬链接等附加信息. rsync是用 &qu ...

  7. 定制Dynamics CRM标准导出功能:不能导出指定列的值

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复239或者20161203可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong. ...

  8. PyCharm常用快捷键(pycharm使用教程)

    pycharm常用快捷键与设置 pycharm高频率使用的快捷键 Ctrl+Shift+F10 运行当前的页面 Ctrl + / 注释(取消注释)选择的行 Ctrl+Shift+F 高级查找 Shif ...

  9. linux cpu信息

    /* who参数 RUSAGE_SELF:只返回当前程序的CPU时间占用信息 RUSAGE_CHILDREN:还包括子进程的CPU时间占用信息 */ int getrusage(int who, st ...

  10. logstash的安装,启动与输出

    在相应的目录下 wget https://artifacts.elastic.co/downloads/logstash/logstash-6.6.2.tar.gz 解压 tar -zxvf  log ...