依赖注入 一、依赖注入与控制反转是什么 控制反转(inversion of control,IoC
)是设计模式中非常重要的思想,而依赖注入(dependency injection,DI)是控制反转思想的一种重要的实现方式
IOC
:控制反转,是一个理论,概念,思想。 描述的:把对象的创建,赋值,管理工作都交给代码之外的容器实现,也就是对象的创建是有其它外部资源完成
控制:创建对象,对象的属性赋值,对象之间的关系管理。
反转:把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象。创建对象,给属性赋值。
在传统的软件开发中,代码依赖的对象都是由调用者自己编写代码进行创建和组装的.
如下代码:
1 2 3 var connStr = ConfigurationManager.ConnectionStrings["connStr1" ] ConnectionString;SqlConnection conn = new SqlConnection(connStr);
以上的代码是数据库的链接代码,通常我们都会写到一个配置文件中。
这里我们自己获取配置文件中的链接字符串,然后自己创建链接sql server
数据库的链接对象SqlConnection
这个过程,需要我们程序员自己非常清楚每个类的作用,以及如何构建和组装他们。
例如:开发人员需要知道“使用ConfigurationManager
类读取连接字符串”这个知识点,并且需要知道要把连接字符串作为参数来调用SqlConnection
的构造方法,以创建数据库连接对象。开发人员必须了解这些细节。这增加了开发人员的负担,使开发人员无法专注于业务逻辑代码的开发。同时,这些代码也与ConfigurationManager、SqlConnection
等类强耦合。如果需要把从配置文件读取改为从环境变量读取或者改为连接MySQL
数据库,就要对代码进行修改。
控制反转的目的就是把“创建和组装对象”操作的控制权从业务逻辑的代码中转移到框架中,这样业务代码中只要说明“我需要某个类型的对象”,框架就会帮助我们创建这个对象
关于控制反转的实现主要有如下两种方式。
第一种:服务定位:假设框架中有一个类叫做ServiceLocator
,只要调用它的GetService
方法就可以获取想要的对象,至于对象是如何创建的我们不用关心,示例代码如下所示:(如下代码是伪代码)
1 IDbConnection conn = ServiceLocator.GetService<IDbConnection>();
第二种方式:依赖注入
假设框架中有一个自动为类的属性赋值的功能,只要在代码中通过属性声明说明我们需要什么类型的对象,框架就会创建这个对象。这个框架我们叫做”容器”,而这个对象我们叫做“服务”
示例代码如下所示:
1 2 3 4 5 6 7 8 class Demo { public IDbConnection Conn { get ; set ; } public void InsertDB () { IDbCommand cmd = Conn.CreateCommand(); } }
在上面的代码中,我们创建了一个Conn
属性,当创建Demo
这个类的对象的时候,框架会自动的给Conn
这个属性赋值。
使用比较多的还是依赖注入。
我们把负责提供对象的注册和获取功能的框架叫作“容器”,注册到容器中的对象叫作“服务”(service) 。
从以上代码可以看出,依赖注入的方式更简单,只要容器给它们赋值即可,不需要像服务定位器那样需要我们通过代码去获取服务。因此我们优先选择依赖注入的方式,只有在依赖注入不满足要求的情况下,才使用服务定位器。
综上所述,控制反转就是把“我创建对象”,变成“我要对象”。实现控制反转的时候,我们可以采用依赖注入或者服务定位器两种方式。程序启动的时候,需要把服务注册到容器中,由容器负责服务的管理。
二、生命周期简介 依赖注入框架中注册的服务有一个重要的概念叫作“生命周期”,通俗地说就是“获取服务的时候是创建一个新对象还是用之前的对象”。依赖注入框架中服务的生命周期有3种。
(1)瞬态(transient):每次被请求的时候都会创建一个新对象。缺点是生成的对象比较多,会浪费内存,所以谨慎使用Transient
(2) 范围(scoped):在给定的范围内,多次请求共享同一个服务对象,服务每次被请求的时候都会返回同一个对象;在不同的范围内,服务每次被请求的时候会返回不同的对象。这个范围可以由框架定义,也可以由开发人员自定义。在ASP.NET Core
中,服务默认的范围是一次HTTP请求,也就是在同一次HTTP请求中,不同的注入会获得同一个对象;在不同的HTTP请求中,不同的注入会获得不同的对象。这种方式适用于在同一个范围内共享同一个对象的情况。
(3)单例(singleton):全局共享同一个服务对象。这种生命周期可以节省创建新对象的资源。为了避免并发修改等问题,单例的服务对象最好是无状态对象.
怎样选择?
如果一个类没有状态,也就是没有属性和成员变量,可以定义成singleton
.
在Asp.net core
中,每次请求对应一个线程,在这个线程内没有并发修改的问题,所以会使用scoped
三、服务注册 下面我们来看一下具体的服务注册的方式来实现控制反转演示代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ITestService t = new TestServiceImpl(); t.Name = "laowang" ; t.SayHi(); public interface ITestService { public string Name { get ; set ; } public void SayHi () ; } public class TestServiceImpl : ITestService { public string ? Name { get ; set ; } public void SayHi () { Console.WriteLine($"I'm {Name} " ); } }
在上面的代码中创建了TestServiceImpl
类,让其实现了ITestService
这个接口。
同时,我们就可以通过new TestServiceImpl();
的方式来创建实例,这是我们以前常用的写法。
但是,现在创建实例的操作应该通过IOC
容器来完成。
实现控制反转有依赖注入与服务注册,这里我们先来看一下服务注册的方式:
首先通过NuGet
安装对应的开发包。
1 Install-Package Microsoft.Extensions.DependencyInjection
然后在代码中引用对应的命名空间:
1 using Microsoft.Extensions.DependencyInjection
服务注册的具体 实现代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddTransient<TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()) { TestServiceImpl t= sp.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); } public interface ITestService { public string Name { get ; set ; } public void SayHi () ; } public class TestServiceImpl : ITestService { public string ? Name { get ; set ; } public void SayHi () { Console.WriteLine($"I'm {Name} " ); } }
在上面的代码中,我们首先创建了用于服务注册的容器,容器的接口是IServiceCollection
,其默认实现的类就是ServiceCollection
.
当然在IServiceCollection
接口中定义了AddTransient、AddScoped和AddSingleton
这3组扩展方法,分别用来注册瞬态、范围和单例服务.
注册完成后,我们调用IServiceCollection的BuildServiceProvider
方法创建一个ServiceProvider
对象,这个ServiceProvider
对象就是一个服务定位器。由于ServiceProvider
对象实现了IDisposable
接口,因此需要使用using
对其进行资源的释放
在我们需要获取服务的时候,可以调用ServiceProvider
类的GetService
方法。
看到上面的代码,大家内心可能会想:“简单的一个new TestServiceImpl
能够完成的事情被你搞得这么复杂,有什么意义?”不要急,很多技术开始的时候都让人感觉“多此一举”,比如刚接触“反射技术”的时候,读者肯定也产生过类似的疑问,但是我们用到的很多框架都是基于反射技术实现的。
四、生命周期演示 在这一小节中,我们演示一下关于注册服务的时候三种不同的生命周期,看一下是否和前面分析的一样。
1 2 3 4 5 6 7 8 9 10 11 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddTransient<TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ TestServiceImpl t= sp.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); TestServiceImpl t1 = sp.GetService<TestServiceImpl>()!; Console.WriteLine(object .ReferenceEquals(t, t1)); }
我们知道上面注册的是瞬态状态下的生命周期,然后又获取了一个新的对象t1
,然后通过object.ReferenceEquals
方法,比较t
与t1
是否是通过一个对象,如果是同一个对象返回的是true
,不是同一个对象返回的是false
.
执行结果是false
,说明t
与t1
不是同一个对象,也就是说,每次调用GetService
方法获取到的都是新的对象。
下面再来看一下单例模式下的情况
1 2 3 4 5 6 7 8 9 10 11 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddSingleton<TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ TestServiceImpl t= sp.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); TestServiceImpl t1 = sp.GetService<TestServiceImpl>()!; Console.WriteLine(object .ReferenceEquals(t, t1)); }
在以上的代码中,我们通过单例的方式注册了服务,然后在通过object.ReferenceEquals(t, t1)
比较的时候返回的结果是true
,说明t与t1
是两个相同的对象。
下面再来看一下范围(scoped
).
在控制台程序中,范围是我们程序员自己来确定的,但是在Asp.net core
中是有框架来确定的,也就是前面所说的一次请求就是一个范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddScoped<TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ using (IServiceScope scope1 = sp.CreateScope()) { TestServiceImpl t = scope1.ServiceProvider.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); TestServiceImpl t1 = scope1.ServiceProvider.GetService<TestServiceImpl>()!; Console.WriteLine(object .ReferenceEquals(t, t1)); }
在上面的代码中通过sp.CreateScope()
方法,创建了一个scope
范围,由于IServiceScope实现了``IDisposable
接口,所以这里需要using
,而using
就表示一个范围。
在一个范围内,t
与t1
就是同一个对象。
下面我们再来看一下不同范围的情况,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddScoped<TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ TestServiceImpl test; using (IServiceScope scope1 = sp.CreateScope()) { TestServiceImpl t = scope1.ServiceProvider.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); TestServiceImpl t1 = scope1.ServiceProvider.GetService<TestServiceImpl>()!; Console.WriteLine(object .ReferenceEquals(t, t1)); test = t; } using (IServiceScope scope2 = sp.CreateScope()) { TestServiceImpl t = scope2.ServiceProvider.GetService<TestServiceImpl>()!; t.Name = "test" ; t.SayHi(); TestServiceImpl t1 = scope2.ServiceProvider.GetService<TestServiceImpl>()!; Console.WriteLine(object .ReferenceEquals(t, t1)); Console.WriteLine(object .ReferenceEquals(test,t)); } }
在上面的代码中,我们又创建了一个新的范围。
首先,把第一个范围内创建的对象t
赋值给了test
,然后再另外一个范围内比较test
与t
是否是同一个对象,发现不是。
这也就说明了,不同范围内创建的对象是不相同的。
如果在注册服务的时候,还是单例的方式,那么上面代码中在不同范围内创建的对象都是同一个对象。
1 services.AddSingleton<TestServiceImpl>();
五、服务定位器 1 services.AddSingleton<TestServiceImpl>();
在上面注册服务的时候,我们写到都是具体的实现类型,而不是接口。
这里我们最好写成接口的形式,面向接口编程有很对的好处。
1 2 3 4 5 6 7 8 9 10 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddSingleton<ITestService,TestServiceImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ ITestService testService = sp.GetService<ITestService>()!; testService.Name = "test" ; testService.SayHi(); }
在上面的代码中,当我们获取服务的时候,只要通过GetService
方法获取实现了ITestService
接口的服务。
当然,随着项目业务复杂以后,通过AddSingleton
注册服务的和通过GetService
方法获取服务的不是同一个人。
注册服务的是某个框架,而通过GetService
方法获取服务的是写具体业务的开发人员。写业务的开发人员,不用关心服务是怎么来的,只是通过GetService
方法来获取服务就可以了。
这里还需要强调一点,关于Addxxx
方法是有很多重载的,这里以AddSingleton
方法来进行说明。
1 2 services.AddSingleton(typeof (ITestService), new TestServiceImpl());
下面我们再来看一下关于GetService
方法的问题。
该方法的定义如下所示,它是一个泛型的方法。
1 public static T? GetService<T>(this IServiceProvider provider)
所以我们在调用GetService
方法的时候,传递的是ITestService
,定义的变量的类型也是ITestService
1 2 3 4 5 6 7 8 9 10 11 12 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddSingleton(typeof (ITestService), new TestServiceImpl()); using (ServiceProvider sp = services.BuildServiceProvider()){ TestServiceImpl testService = sp.GetService<TestServiceImpl>()!; testService.Name = "test" ; testService.SayHi(); }
当运行上面的程序的时候,程序会出错,testService
的值为null
.
说明:第一:这里GetService
来获取服务的时候,所需要的泛型类型,一定是在调用AddSingleton
方法的时候,注册的类型。在上面的代码中,AddSingleton
注册的类型是ITestService
.
所以这里的GetService
方法获取服务的时候,指定的类型应该是ITestService
而不是TestServiceImpl
.
当然,如果在调用AddSingleton
方法的时候,指定的类型是TestServiceImpl
,这时候在调用GetService
方法获取对应的服务的时候,类型是TestServiceImpl
是可以的。
1 services.AddSingleton(typeof (TestServiceImpl), new TestServiceImpl());
但是,我们前面也提到过,最好使用接口。
第二:通过出错的信息,我们可以看出:GetService
如果找不到服务,返回值是null
当然,GetService
方法还有非泛型的情况,如下代码所示:
1 2 3 4 5 6 7 services.AddSingleton(typeof (ITestService), new TestServiceImpl()); using (ServiceProvider sp = services.BuildServiceProvider()){ ITestService testService =(ITestService)sp.GetService(typeof (ITestService))! ; testService.Name = "test" ; testService.SayHi(); }
当然,我们一般都是使用泛型的应用。
除了使用GetService
方法获取服务意外,也可以使用GetRequiredService
方法 来获取服务。
该方法有泛型和非泛型两种情况,下面我们只是看一下它的泛型情况就可以了。
这里需要注意的是GetRequiredService
方法,在找不到具体的服务的时候,并不会返回null
,而是直接抛异常,这是与GetService
方法的区别,GetService
方法在找不到服务的时候,返回的是null
.
1 2 3 4 5 6 7 8 services.AddSingleton(typeof (ITestService), new TestServiceImpl()); using (ServiceProvider sp = services.BuildServiceProvider()){ ITestService testService=sp.GetRequiredService<ITestService>(); testService.Name = "test" ; testService.SayHi(); }
在上面的代码中通过GetRequiredService
方法来获取服务。
1 ITestService testService=sp.GetRequiredService<TestServiceImpl>();
上面的代码很明显无法获取TestServiceImpl
这个服务,所以会抛出以下异常信息。
1 2 System.InvalidOperationException:“No service for type 'TestServiceImpl' has been registered.”
如果想想获取多个服务,可以使用GetServices
这个方法来完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface ITestService { public string Name { get ; set ; } public void SayHi () ; } public class TestServiceImpl : ITestService { public string ? Name { get ; set ; } public void SayHi () { Console.WriteLine($"I'm {Name} " ); } } public class TestServiceImpl2 : ITestService { public string ? Name { get ; set ; } public void SayHi () { Console.WriteLine($"I'm {Name} " ); } }
在上面的代码中,我们又定义了TestServiceImpl2
类,实现了ITestService
接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddSingleton(typeof (ITestService), new TestServiceImpl()); services.AddSingleton(typeof (ITestService), new TestServiceImpl2()); using (ServiceProvider sp = services.BuildServiceProvider()){ IEnumerable<ITestService>tests= sp.GetServices<ITestService>(); foreach (ITestService it in tests) { it.Name =it.GetType().ToString(); it.SayHi(); } }
在上面的代码中,又注册了TestServiceImpl2
服务。
下面通过GetServices
获取了实现了ITestService
接口的所有服务,返回的是IEnumerable
这个泛型接口。
下面进行了遍历。
获取每个服务的类型,赋值给了Name
属性,然后调用了SayHi
方法。
六、依赖注入 前面我们实现控制反转的方式都是通过服务定位的方式来实现的。
在这一小节中我们将通过依赖注入的方式来实现控制反转。
在.Net
中DI
(依赖注入)默认采用的是构造函数的注入。也就是通过构造函数完成服务的注入
下面看一下基本的代码实现:
在解决方法中,又重新创建了一个项目,并且在该项目下面按照了包Install-Package Microsoft.Extensions.DependencyInjection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 interface ILog { public void Log (string message ) ; } class LogImpl : ILog { private readonly IConfig config; public LogImpl (IConfig config ) { this .config = config; } public void Log (string message ) { string configMsg = config.GetValue("日志" ); Console.WriteLine(configMsg); Console.WriteLine("日志信息:" +message); } } interface IConfig { public string GetValue (string name ) ; } class ConfigImpl : IConfig { public string GetValue (string name ) { return $"获取了{name} 的配置信息" ; } }
在上面的代码中,我们首先定义了一个IConfig
接口,然后定义类ConfigImpl
实现了IConfig
接口中的GetValue
方法。
同时还定义了ILog
接口,在该接口中声明的Log
方法是完成日志记录的。
在LogImpl
类中实现了ILog
接口中的Log
方法。
当然,在进行日志记录的时候,需要获取相关的配置信息。
所以在LogImpl
类中添加了对应的构造函数,该构造函数的参数就是注入的配置对象。
对应的config
实例,我们一般都是通过readonly
来修饰,防止 后面重新给config
赋值。
在LogImpl
这个类中,我们不用关心config
是怎么来的,我们只要知道框架会提供一个config
的服务就可以了。
这也就是说明,写业务代码的开发人员,只需要要服务就可以了,但是服务是怎么创建的,怎么来的不用关心。
下面继续完善代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddScoped<Controller>(); services.AddScoped<ILog, LogImpl>(); services.AddScoped<IConfig, ConfigImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ var c=sp.GetRequiredService<Controller>(); c.Test(); } class Controller { private readonly ILog log; public Controller (ILog log ) { this .log = log; } public void Test () { Console.WriteLine("开始业务" ); this .log.Log("aaaaaaa" ); Console.WriteLine("业务结束" ); } }
在上面的代码中首先创建了services
这个服务的容器,下面向该容器中注册了Controller
等服务。
然后通过容器对象中的BuildServiceProvider
方法,创建了ServiceProvider
对象,在通过该对象中的GetRequiredService
方法获取Controller
服务,最后调用Controller
服务中的test
方法。
同时下面也创建了Controller
这个类,在该类的构造函数中完成了ILog
服务的注入。
在其test
方法中调用了ILog
服务中的Log
方法,完成了日志的记录。
当然在Log
方法中也读取了对应的配置信息。
完整的案例代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 using Microsoft.Extensions.DependencyInjection;ServiceCollection services = new ServiceCollection(); services.AddScoped<Controller>(); services.AddScoped<ILog, LogImpl>(); services.AddScoped<IConfig, ConfigImpl>(); using (ServiceProvider sp = services.BuildServiceProvider()){ var c=sp.GetRequiredService<Controller>(); c.Test(); } class Controller { private readonly ILog log; public Controller (ILog log ) { this .log = log; } public void Test () { Console.WriteLine("开始业务" ); this .log.Log("aaaaaaa" ); Console.WriteLine("业务结束" ); } } interface ILog { public void Log (string message ) ; } class LogImpl : ILog { private readonly IConfig config; public LogImpl (IConfig config ) { this .config = config; } public void Log (string message ) { string configMsg = config.GetValue("日志" ); Console.WriteLine(configMsg); Console.WriteLine("日志信息:" +message); } } interface IConfig { public string GetValue (string name ) ; } class ConfigImpl : IConfig { public string GetValue (string name ) { return $"获取了{name} 的配置信息" ; } }
思考:这种依赖注入到底有什么好处?
如果日志的配置信息需要从数据库中来获取应该怎样进行处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface IConfig { public string GetValue (string name ) ; } class ConfigImpl : IConfig { public string GetValue (string name ) { return $"获取了{name} 的配置信息" ; } } class DBConfigImpl : IConfig { public string GetValue (string name ) { return $"从数据库中获取了{name} 的配置信息" ; } }
在上面的代码中,我们又定义了DBConfigImpl
类,实现了IConfig
接口中的GetValue
1 2 3 4 5 ServiceCollection services = new ServiceCollection(); services.AddScoped<Controller>(); services.AddScoped<ILog, LogImpl>(); services.AddScoped<IConfig, DBConfigImpl>();
同时在注册配置服务的时候,换成了DBConfigImpl
.
这时候运行程序,发现日志所需要的配置信息是从数据中获取的。
但是问题是我们并没有修改Controller
类,LogImpl
类中的代码(**LogImpl
类根本就不关心日志的配置信息是从哪获取的,只要实现了IConfig
接口就行**),这也就是说通过依赖注入降低了模块之间的耦合。系统变得更加的灵活了。
七、案例 要求:我们开发一个传统分层的项目,项目结构如下:
UI
层,业务逻辑层,数据访问层。
UI层
访问业务逻辑成,业务逻辑层访问数据访问层。数据层通过ADO.NET
来操作数据库。
首先创建Model
层,在该层中创建了一个UserInfo.cs
类,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Model { public class UserInfo { public int Id { get ; set ; } public string ? Name { get ; set ; } public string ? Password { get ; set ; } } }
创建DAL
层,在该层中先创建接口IUserDAO.cs
,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using Model;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DAL { public interface IUserDAO { public UserInfo GetUserInfo (string userName ) ; } }
在DAL
层中需要引入Model
层。
下面还需要在DAL
层中创建UserDAO.cs
类来实现IUserDAO.cs
这个接口。UserDAO.cs
类中的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using Model;using System;using System.Collections.Generic;using System.Data;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Xml.Linq;namespace DAL { public class UserDAO : IUserDAO { private readonly IDbConnection conn; public UserDAO (IDbConnection conn ) { this .conn = conn; } public UserInfo GetUserInfo (string userName ) { using (var dat = SqlHelper.ExecuteQuery(conn, $"select * from userInfo where Name={userName} " )) { if (dat.Rows.Count <= 0 ) return null ; DataRow row = dat.Rows[0 ]; int id = (int )row["id" ]; string uname = (string )row["Name" ]; string password = (string )row["Password" ]; return new UserInfo() { Id = id, Name = uname, Password = password }; }; } } }
在UserDAO
类中通过构造方法的形式依赖注入了IDConnect
.
所实现的方法GetUserInfo
中通过SqlHelper
类操作数据库。(把SqlHelper
类添加到了DAL
层中)
下面创建了BLL
层,同时在该层中创建了接口IUserBLL.cs
,该接口中的代码如下所示:
1 2 3 4 5 6 7 8 9 namespace BLL { public interface IUserBLL { public bool CheckLogin (string userName, string password ) ; } }
下面又创建了类UserBLL.cs
来实现以上的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace BLL { public class UserBLL : IUserBLL { private readonly IUserDAO userDAO; public UserBLL (IUserDAO userDao ) { this .userDAO = userDao; } public bool CheckLogin (string userName, string password ) { var user=userDAO.GetUserInfo(userName); if (user == null ) { return false ; } else { return user.Password == password; } } } }
在上面的代码中也是通过构造方法注入了IUserDAO
服务,
注意这里要求注入的是IUserDAO
接口,而不是UserDAO
类,因为我们要面向接口编程,而不是面向实现编程。由于这里是基于接口编程的
同时在BLL
层中要引入DAL
层。
下面看一下UI
层中的代码,在Program.cs
文件中的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using BLL;using DAL;using Microsoft.Extensions.DependencyInjection;using System.Data;using System.Data.SqlClient;ServiceCollection services = new ServiceCollection(); services.AddScoped<IDbConnection>(sp => { string connStr = "Data Source=.;Initial Catalog=TestDB;uid=sa;pwd=123456" ; var conn = new SqlConnection(connStr); conn.Open(); return conn; }); services.AddScoped<IUserDAO, UserDAO>(); services.AddScoped<IUserBLL, UserBLL>(); using (ServiceProvider sp = services.BuildServiceProvider()){ var userBll=sp.GetService<IUserBLL>(); bool b = userBll.CheckLogin("admin" , "12345688888" ); Console.WriteLine(b); }
在上面代码的最开始注册了生命周期为范围
的IDbConnection
服务。
同时又注册了IUserDAO,IUserBLL
两个服务。
最后通过sp
这个Provider
对象中的GetService
方法获取了IUserBLL
服务。从而完成了该服务中的CheckLogin
方法的调用。
这样做的好处就是:如果以后需要更换数据库。例如更换成MySQL
数据库,只需要将上面的代码中SqlConnection
换成MySQLConnection
即可,而UserDAO,UserBLL
中的代码都是不需要修改的。
说明:数据库TestDB
中有一个userInfo
表,该表的字段Id
是主键,Name
表示用户名,Password
表示密码。
今日总结-Kilgour 首先将了要停止使用new
这个关键字,采用一种引得框架(或者说新的方法)- 加入Microsoft.Extensions.DependencyInjection
Nuget包来实现对象的自主创建和回收。并且介绍了相关的语法和用法(七、总结)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 using Microsoft.Extensions.DependencyInjection;ServiceCollection service = new ServiceCollection(); service.AddScoped<Controller> (); service.AddScoped<ILog, Log> (); service.AddScoped<IConfig, Config2>(); using (var sp = service.BuildServiceProvider()){ var controller = sp.GetRequiredService<Controller>(); controller.Test(); } public class Controller { private readonly ILog _log; public Controller (ILog log ) { this ._log = log; } public void Test () { Console.WriteLine("业务的开始" ); _log.WriteLog("hello world" ); Console.WriteLine("业务的结束" ); } } public interface ILog { public void WriteLog (string message ) ; } public class Log : ILog { private readonly IConfig _config; public Log (IConfig config ) { this ._config = config; } public void WriteLog (string message ) { string str = _config.GetValue("日志" ); Console.WriteLine(str); Console.WriteLine("然后打印日志信息" + message); } } public interface IConfig { public string GetValue (string name ) ; } public class Config : IConfig { public string GetValue (string name ) { return $"首先获取该服务{name} 的配置信息" ; } } public class Config2 : IConfig { public string GetValue (string name ) { return $"首先获取服务器的{name} 配置信息" ; } }
创建json配置文件存放SQL连接字符串
该Json配置文件应防止于Debug文件目录下,但是每次要手动放置。因此右键点击Json文件属性将复制到输出目录
改为如果较新则复制
在JSON文件中如上图创建连接数据库字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Microsoft.Extensions.Configuration;ConfigurationBuilder cb = new ConfigurationBuilder(); cb.AddJsonFile("config.Json" ,optional:false ,reloadOnChange:true ); IConfigurationRoot root = cb.Build(); string str = root["name" ]!;Console.WriteLine("用户名是:" + str); string address = root.GetSection("proxy:address" ).Value!;Console.WriteLine("地址是:" + address); Console.WriteLine("连接数据库字符串是:" + root.GetConnectionString("str" ));