WebApi详解+Unity注入--下篇:Unity注入

16,IOC 容器Unity

DIP依赖倒置原则:

一种软件架构设计原则(抽象概念)。依赖抽象不依赖细节。

IOC控制反转(Inversion of Control

传统开发,上端依赖(调用/指定)下端对象,会有依赖,把对下端对象的依赖转移到第三方容器(工厂+配置+反射),使程序拥有更好的扩展性,是DIP 的具体实现方式,可以用来降低代码之间的额耦合度。

DI 即依赖注入(Dependency Injection):

  1. 是实现IOC的手段和方法,就是能做到构造某个对象时,将依赖的对象自动初始化并注入。
  2. 有三种注入方式:构造方法注入--属性注入--方法注入(按照时间顺序)。
  3. 构造方法注入用的最多,默认找参数最多的构造方法,可以不用特性,可以去掉对容器的依赖。
16.1,Unity

微软推出的IOC框架,使用这个框架,可以实现AOP切面编程,便于代码的后期维护,此外该框架还自带单例模式,可以提高程序的运行效率。

安装NuGet包:UnityUnity.AbstractionsUnity.Container

16.1.1,构造方法的注入

如果存在多个构造方法,且这些构造方法均适配依赖注入,那么默认情况下注入选择的是参数多的构造方法。但可通过[InjectionConstructor]特性指定特定的构造方法。

[InjectionConstructor]:标记指定的构造方法为构造方法注入

C# 复制代码
public class TestServiceB : ITestService.ITestServiceB
    {
        int id = 10;
        [InjectionConstructor]//使用特性指定注入时选择此构造方法
        public TestServiceB(ITestService.ITestServiceA testServiceA)
        {

        }
        //进行构造方法注入时默认选择参数较多的构造方法,可使用[InjectionConstructor]特性指定注入构造方法
        public TestServiceB(ITestService.ITestServiceA testServiceA1, ITestService.ITestServiceA testServiceA2)
        {
            var reuslt = Object.ReferenceEquals(testServiceA1, testServiceA2);
        }
        public void PrintInfo()
        {
            Console.WriteLine("TestServiceB");
        }
    }
16.1.2,属性的注入

在构建某一个对象的时候,如果明确需要做属性注入,该对象中的需要注入的属性,就会根据属性的类型,创建出对象,赋值给属性。

[Dependency]:标记属性为属性注入

C# 复制代码
[Dependency]//使用该特性进行属性注入
        public ITestService.ITestServiceA TestServiceA { get; set; }

需要注意的是:注入顺序是首先注入构造方法,其次注入属性,再次注入方法 ,所以在构造方法里查看TestServiceA为null,当执行完构造方法后才再执行对属性的注入。

16.1.3,方法的注入

在构造某一个对象的时候,自动去执行某些方法,根据方法的参数类型,自动构造出参数的类型实例,传递到方法的参数中,就可以将这个参数注入到类的内部。

注意:方法的注入是在实例化对象的时候自动进行,不需要外部显式调用执行。方法类型需要为Public否则不能自动运行。

[InjectionMethod]:标记方法为方法注入

C# 复制代码
 [InjectionMethod]
        public void PrintInnerInfo(ITestService.ITestServiceA testServiceA)
        {
            
            Console.WriteLine($"方法注入:在对象实例化中自动调用。TestServicA.Id={testServiceA.Id}");
        }
16.2,生命周期
  1. TransientLifetimeManager:瞬时生命周期

  2. ContainerControlledLifetimeMannager: 单例生命周期

  3. PerThreadLifetimeManager:线程单例生命周期

16.3,配置文件

安装Nuget包:Unity.Configuration

示例:

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>

<configuration>
	<configSections>
		<!-- Unity配置节声明 -->
		<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration"/>
	</configSections>

	<!-- Unity核心配置-->
	<unity>
		<containers>
			<container name="Container">
				<!-- ITestService.ITestServiceA:接口名;ITestService:接口所在的程序集 -->
				<register type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
					<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
					<lifetime type="transient"></lifetime>
					<!-- 单例写法,解开注释即用 -->
					<!--<lifetime type="singleton"></lifetime>-->
				</register>
				<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService"></register>
			</container>
		</containers>
	</unity>
</configuration>
16.3.1,配置别名

当一个接口被多个类实现的时候,就需要在配置中指明名称,否则将以最后一个类的实例作为对象进行注入。

C# 复制代码
 public class TestServiceA : ITestService.ITestServiceA
    {
        public int Id { get; set; } = 12;

        public void PrintInfo()
        {
            Console.WriteLine("TestServiceA");
        }
    }
public class TestServiceAA : ITestService.ITestServiceA
    {
        public int Id { get ; set ; }

        public void PrintInfo()
        {
            Console.WriteLine("这是TestServiceAA"); ;
        }
    }

通过属性name指定名称

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>

<configuration>
	<configSections>
		<!-- Unity配置节声明 -->
		<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Unity.Configuration"/>
	</configSections>
	<!-- Unity核心配置-->
	<unity>
		<containers>
			<container name="Container">
				<!-- ITestService.ITestServiceA:接口名;ITestService:接口所在的程序集 -->
				<!--通过name属性指定名称-->
				<register name="testServiceA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
					<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
					<lifetime type="transient"></lifetime>
					<!-- 单例写法,解开注释即用 -->
					<!--<lifetime type="singleton"></lifetime>-->
				</register>
				<!--通过name属性指定名称-->
				<register name="testServiceAA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceAA,TestService">
					
				</register>
				<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService"></register>
			</container>
		</containers>
	</unity>
</configuration>

通过名称选择注入

C# 复制代码
 ExeConfigurationFileMap mapfile = new ExeConfigurationFileMap();
            mapfile.ExeConfigFilename = System.IO.Path.Combine(Environment.CurrentDirectory, "config/unity.config");
             
            Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(mapfile, ConfigurationUserLevel.None);
            var section = configuration.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
            Unity.UnityContainer container = new Unity.UnityContainer();
            container.LoadConfiguration(section, "Container");
          
            var serviceA = container.Resolve<ITestService.ITestServiceA>("testServiceA");
            var serviceAA = container.Resolve<ITestService.ITestServiceA>("testServiceAA");
16.3.2,配置构造方法注入

默认情况下,容器实例化对象选择的无参构造方法,若要选择指定的构造方法有以下两种方法:

  1. 第一种:在需要调用的构造方法上添加[InjectionConstructor]特性,参考16.1.1节内容。
  2. 第二种:在配置文件中配置构造方法参数,在容器实例化中将根据参数类型,数量自动调用参数匹配的构造方法,这种方式不需要在构造方法上添加[InjectionConstructor]特性
xml 复制代码
<register  type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
					<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
					<lifetime type="transient"></lifetime>
					<!-- 单例写法,解开注释即用 -->
					<!--<lifetime type="singleton"></lifetime>-->
				</register>
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
					<!--指定构造函数-->
					<constructor>
						<!--参数1-->
						<!--name="id":表示构造方法的形参名为id-->
						<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
						<param name="id" type="System.Int32" value="3"></param>
						<!--参数2-->
						<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
						<param name="testServiceA"  ></param>
					</constructor>
				</register>

调用形参为idtestServiceA的构造方法

C# 复制代码
 public class TestServiceB : ITestService.ITestServiceB
    {
        int id = 10;
        //[InjectionConstructor]//指定注入此构造方法
        public TestServiceB(ITestService.ITestServiceA testServiceA)
        {

        }
        public TestServiceB(int id)
        {
            this.id = id;
        }
     //配置将调用这个方法
        public TestServiceB(int id,ITestService.ITestServiceA testServiceA)
        {
            this.id = id;
        }
        [Dependency]
        public ITestService.ITestServiceA TestServiceA { get; set; }
        //进行构造方法注入时默认优先使用参数较多的构造方法,可使用[InjectionConstructor]特性指定注入构造方法
        public TestServiceB(ITestService.ITestServiceA testServiceA1, ITestService.ITestServiceA testServiceA2)
        {
            var reuslt = Object.ReferenceEquals(testServiceA1, testServiceA2);
        }
 }

特别注意:这里使用的ITestService.ITestServiceA配置时不能命名(即属性name赋值),命名后将抛异常。

错误示例:注册时设置了name属性

xml 复制代码
<register name="testServiceA" type="ITestService.ITestServiceA,ITestService" mapTo="TestService.TestServiceA,TestService">
					<!-- 瞬时生命周期,小写正确,可省略(默认就是transient) -->
					<lifetime type="transient"></lifetime>
					<!-- 单例写法,解开注释即用 -->
					<!--<lifetime type="singleton"></lifetime>-->
				</register>

执行var serviceB = container.Resolve<ITestService.ITestServiceB>();抛出异常:

16.3.3,配置属性注入
xml 复制代码
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
					<!--指定构造函数-->
					<constructor>
						<!--参数1-->
						<!--name="id":表示构造方法的形参名为id-->
						<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
						<param name="id" type="System.Int32" value="3"></param>
						<!--参数2-->
						<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
						<param name="testServiceA"  ></param>
					</constructor>
					<!--配置属性注入-->
					<property name="TestServiceA"></property>
				</register>
C# 复制代码
       // [Dependency]
//通过配置声明注入
        public ITestService.ITestServiceA TestServiceA { get; set; }
16.3.4,配置方法注入
xml 复制代码
<register type="ITestService.ITestServiceB,ITestService" mapTo="TestService.TestServiceB,TestService">
					<!--指定构造函数-->
					<constructor>
						<!--参数1-->
						<!--name="id":表示构造方法的形参名为id-->
						<!--因为是Int32类型没有进行注入所以这里需要添加value属性值-->
						<param name="id" type="System.Int32" value="3"></param>
						<!--参数2-->
						<!--该参数是注入所以不需要添加value属性值,同时注册该类型的时候不能指定name否则将抛异常-->
						<param name="testServiceA"  ></param>
					</constructor>
					<!--配置属性注入-->
					<property name="TestServiceA"></property>
					<!--配置方法注入-->
					<method name="PrintConfigInfo">
						<param name="testServiceA"></param>
						<param name="id" type="System.Int32" value="100"></param>
					</method>
				</register>

注入的方法

C# 复制代码
  public void PrintConfigInfo(ITestService.ITestServiceA testServiceA,int id)
        {
            Console.WriteLine($"方法注入:在对象实例化中自动调用。TestServicA.Id={testServiceA.Id},注入的Id值为:{id}");
        }
16.3.5,配置程序集位置

默认情况下,map To的程序集位于执行程序目录下,而如果不在执行程序目录下则需要进行如下配置:

注意是在app.config文件中,而不是自定义的config文件,在自定义的config文件中配置无效。

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <runtime>
	
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
		<!-- probePrivatePath:指定程序集的私有探测目录,多个目录用分号;分隔 -->
		<!--指定 CLR 查找私有程序集的额外目录,lib 是相对于执行文件的相对路径-->
		<probing privatePath="lib"/>
      <!-- (可选)若有版本/公钥冲突,可添加程序集重定向 -->
			<!--<dependentAssembly>
				<assemblyIdentity name="TestService" publicKeyToken="null" culture="neutral"/>
				<bindingRedirect oldVersion="0.0.0.0-9.9.9.9" newVersion="1.0.0.0"/>
			</dependentAssembly>-->
        </assemblyBinding>
  </runtime>
</configuration>

Demo链接

https://download.csdn.net/download/lingxiao16888/92544537?spm=1001.2014.3001.5501

相关推荐
世洋Blog14 小时前
面经-CPU、内存、GPU的性能优化
unity·性能优化
lingxiao1688816 小时前
WebApi详解+Unity注入--中篇:.net core的WebAPI
unity·c#·.netcore
ServBay17 小时前
C# 成为 2025 年的编程语言,7个C#技巧助力开发效率
后端·c#·.net
weixin_4239950020 小时前
unity 处理图片:截图,下载,保存
java·unity·游戏引擎
故事不长丨20 小时前
C#进制转换:从基础原理到实战应用
开发语言·c#·进制转换·16进制·2进制·10进制
liulilittle20 小时前
VEthernet 框架实现 tun2socks 的技术原理
网络·windows·c#·信息与通信·通信
云草桑21 小时前
.net AI API应用 客户发的信息提取对接上下游系统报价
ai·c#·.net·semantickernel·sk
故事不长丨1 天前
C#File文件操作全解析:从基础用法到异常处理
服务器·开发语言·visualstudio·c#·文件操作·io流·file
呆呆敲代码的小Y1 天前
【Unity实战篇】| 游戏轮播图效果,多种实现思路及完整教程
游戏·unity·游戏引擎·实战·游戏开发·轮播图·u3d