在.net core中實(shí)現(xiàn)字段和屬性注入的示例代碼
簡(jiǎn)單來(lái)說(shuō),使用Ioc模式需要兩個(gè)步驟,第一是把服務(wù)注冊(cè)到容器中,第二是從容器中獲取服務(wù),我們一個(gè)一個(gè)討論并演化。這里不會(huì)考慮使用如Autofac等第三方的容器來(lái)代替默認(rèn)容器,只是提供一些簡(jiǎn)單實(shí)用的小方法用于簡(jiǎn)化應(yīng)用層的開(kāi)發(fā)。
將服務(wù)注入到容器
asp.netcore官方給出的在容器中注冊(cè)服務(wù)方法是,要在Startup類的ConfigureServices方法中添加服務(wù),如下所示:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton(typeof(UserService));
services.AddSingleton(typeof(MsgService));
services.AddSingleton(typeof(OrderService));
}
AddMvc方法添加了mvc模塊內(nèi)部用到的一些服務(wù),這個(gè)是封裝好的,一句話就行了,其他第三方組件也都提供了類似的Add方法,把自己內(nèi)部需要的服務(wù)都封裝好注冊(cè)進(jìn)去了。但是我們應(yīng)用開(kāi)發(fā)人員使用的類,還是需要一個(gè)一個(gè)寫進(jìn)去的,大家最常見(jiàn)的三層架構(gòu)中的數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層便是此類服務(wù),上面代碼中我加入了三個(gè)業(yè)務(wù)服務(wù)類。這顯然不是長(zhǎng)久之計(jì),我想大家在開(kāi)發(fā)中也會(huì)針對(duì)此問(wèn)題做一些處理,這里說(shuō)下我的,僅供參考吧。
解決方法就是批量注冊(cè)!說(shuō)到批量,就需要一個(gè)東西來(lái)標(biāo)識(shí)一批東西,然后用這一個(gè)東西來(lái)控制這一批東西。在.net程序的世界中,有兩個(gè)可選的角色,一個(gè)是接口Interface,另一個(gè)是特性Attribute。
如果使用接口作為標(biāo)識(shí)來(lái)使用,限制就太死板了,一個(gè)標(biāo)識(shí)的信息不是絕對(duì)的單一,是不推薦使用接口的,因?yàn)榭赡苄枰攵鄠€(gè)接口才能共同完成,所以我選擇特性作為標(biāo)識(shí)。特性相較與接口有什么特點(diǎn)呢?特性在運(yùn)行時(shí)是類的實(shí)例,所以可以存儲(chǔ)更多的信息。
下面我們簡(jiǎn)單實(shí)現(xiàn)一個(gè)AppServiceAttribute:
/// <summary>
/// 標(biāo)記服務(wù)
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AppServiceAttribute : Attribute
{
}
這個(gè)特性類取名AppService有兩個(gè)理由,一是指定是應(yīng)用層的服務(wù)類,二是避免使用Service這樣的通用命名和其他類庫(kù)沖突。
有了標(biāo)識(shí),就可以批量處理了,我們?cè)谝粋€(gè)新的類中給IServiceCollection提供一個(gè)擴(kuò)展方法,用來(lái)批量添加標(biāo)記有AppService特性的服務(wù)到容器中。
public static class AppServiceExtensions
{
/// <summary>
/// 注冊(cè)應(yīng)用程序域中所有有AppService特性的服務(wù)
/// </summary>
/// <param name="services"></param>
public static void AddAppServices(this IServiceCollection services)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
var serviceAttribute = type.GetCustomAttribute<AppServiceAttribute>();
if (serviceAttribute != null)
{
services.AddSingleton(type);
}
}
}
}
}
我們遍歷應(yīng)用程序中所有程序集,然后嵌套遍歷每個(gè)程序集中的所有類型,判斷類型是否有AppService特性,如果有的話就添加到容器中,這里有點(diǎn)不自信哦,為什么呢,因?yàn)槲沂鞘褂肁ddSingleton方法以單例模式將服務(wù)添加到容器中的,雖然三層中的數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層絕大部分都可以使用單例,但是我們希望更通用一些,大家都知道netcore自帶的Ioc容器支持三種生命周期,所以我們修改AppServiceAttribute,添加一個(gè)Lifetime屬性:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AppServiceAttribute : Attribute
{
/// <summary>
/// 生命周期
/// </summary>
public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton;
}
Lifetime的默認(rèn)值我們?cè)O(shè)置成ServiceLifetime.Singleton是比較合適的,因?yàn)榇蟛糠址?wù)我們都希望使用單例注冊(cè),一個(gè)合理的默認(rèn)設(shè)置可以節(jié)省使用者很多代碼,新手可能還會(huì)樂(lè)于復(fù)制粘貼,但老同志肯定都深有體會(huì)。
有了Lifetime這個(gè)信息,我們就可以改進(jìn)AddAppServices方法了,在判斷serviceAttribute不為null后,使用下面的代碼替換services.AddSingleton(type):
switch (serviceAttribute.Lifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton(serviceType, type);
break;
case ServiceLifetime.Scoped:
services.AddScoped(serviceType, type);
break;
case ServiceLifetime.Transient:
services.AddTransient(serviceType, type);
break;
default:
break;
}
現(xiàn)在我們可以注冊(cè)不同生命周期的服務(wù)了,只是該控制是在類的定義中,按理說(shuō),服務(wù)對(duì)象注冊(cè)到容器中的生命周期,是不應(yīng)該在類的定義中確定的,因?yàn)橐粋€(gè)類的定義是獨(dú)立的,定義好之后,使用者可以用任何一種容器支持的生命周期來(lái)注冊(cè)實(shí)例。但是此時(shí)這樣的設(shè)計(jì)是比較合理的,因?yàn)槲覀円鉀Q的是應(yīng)用層服務(wù)的批量注冊(cè),這類服務(wù)一般在定義的時(shí)候就已經(jīng)確定了使用方式,而且很多時(shí)候服務(wù)的開(kāi)發(fā)者就是該服務(wù)的使用者!所以我們可以把這個(gè)當(dāng)成合理的反范式設(shè)計(jì)。
目前這樣子,對(duì)于我來(lái)說(shuō),基本已經(jīng)夠用了,因?yàn)樵趹?yīng)用層,我都是依賴實(shí)現(xiàn)編程的😀(哈哈,會(huì)不會(huì)很多人說(shuō)咦......呢?)。設(shè)計(jì)模式說(shuō):“要依賴于抽象,不要依賴于具體”,這點(diǎn)我還沒(méi)做到,我抽空檢討(呵呵,誰(shuí)信呢?。?。所以呢,我們的批量注入要支持那些優(yōu)秀的同學(xué)。
從上面的代碼不難發(fā)現(xiàn),如果定義接口IA和其實(shí)現(xiàn)A:IA,并在A上添加AppService特性是不行的:
public interface IA { }
[AppService]
public class A : IA { }
這個(gè)時(shí)候我們并不能依賴IA編程,因?yàn)槲覀冏?cè)的服務(wù)類是A,實(shí)現(xiàn)類是A,我們需要注冊(cè)成服務(wù)類是IA,實(shí)現(xiàn)類是A才可:
public class HomeController : Controller
{
private IA a;
public HomeController(IA a)
{
this.a = a; //這里a是null,不能使用
}
}
讓我繼續(xù)改進(jìn),在AppServiceAttribute中,我們加入服務(wù)類型的信息:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AppServiceAttribute : Attribute
{
/// <summary>
/// 生命周期
/// </summary>
public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Singleton;
/// <summary>
/// 指定服務(wù)類型
/// </summary>
public Type ServiceType { get; set; }
/// <summary>
/// 是否可以從第一個(gè)接口獲取服務(wù)類型
/// </summary>
public bool InterfaceServiceType { get; set; } = true;
}
我們從兩個(gè)方面入手來(lái)解決服務(wù)類型的問(wèn)題,一個(gè)是指定ServiceType,這個(gè)就毫無(wú)疑問(wèn)了,在A的AppService中可以明確指定IA為其服務(wù)類:
[AppService(ServiceType = typeof(IA))]
public class A : IA { }
另一個(gè)是從服務(wù)類自身所繼承的接口中獲取服務(wù)類形,這一點(diǎn)要在AddAppServices方法中體現(xiàn)了,再次改進(jìn)AddAppServices方法,還是替換最開(kāi)始services.AddSingleton(type)的位置:
var serviceType = serviceAttribute.ServiceType;
if (serviceType == null && serviceAttribute.InterfaceServiceType)
{
serviceType = type.GetInterfaces().FirstOrDefault();
}
if (serviceType == null)
{
serviceType = type;
}
switch (serviceAttribute.Lifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton(serviceType, type);
break;
case ServiceLifetime.Scoped:
services.AddScoped(serviceType, type);
break;
case ServiceLifetime.Transient:
services.AddTransient(serviceType, type);
break;
default:
break;
}
我們首先檢查serviceAttribute.ServiceType,如果有值的話,它就是注冊(cè)服務(wù)的類型,如果沒(méi)有的話,看是否允許從接口中獲取服務(wù)類型,如果允許,便嘗試獲取第一個(gè)作為服務(wù)類型,如果還沒(méi)獲取到,就把自身的類型作為服務(wù)類型。
- 第一種情況不常見(jiàn),特殊情況才會(huì)指定ServiceType,因?yàn)閷懫饋?lái)麻煩;
- 第二種情況適用于依賴抽象編程的同學(xué),注意這里只取第一個(gè)接口的類型;
- 第三種情況就是適用于像我這種有不良習(xí)慣的患者(依賴實(shí)現(xiàn)編程)!
到此為止我們的服務(wù)注冊(cè)已經(jīng)討論完了,下面看看如何獲取。
字段和屬性注入
這里我們說(shuō)的獲取,不是框架默認(rèn)容器提供的構(gòu)造器注入,而是要實(shí)現(xiàn)字段和屬性注入,先看看構(gòu)造器注入是什么樣的:
public class HomeController : Controller
{
UserService userService;
OrderService orderService;
MsgService msgService;
OtherService otherService;
OtherService2 otherService2;
public HomeController(UserService userService, OrderService orderService, MsgService msgService, OtherService otherService, OtherService2 otherService2)
{
this.userService = userService;
this.orderService = orderService;
this.msgService = msgService;
this.otherService = otherService;
this.otherService2 = otherService2;
}
}
如果引用的服務(wù)不再添加還好,如果編寫邊添加就太要命了,每次都要定義字段、在構(gòu)造器方法簽名中些添加參數(shù)、在構(gòu)造器中賦值,便捷性和Spring的@autowired注解沒(méi)法比,所以我們要虛心學(xué)習(xí),創(chuàng)作更便捷的操作。
首先我們?cè)俣x個(gè)特性,叫AutowiredAttribute,雖然也是個(gè)標(biāo)識(shí),但是由于這個(gè)特性是用在字段或者屬性上,所以只能用特性Attribute,而不能使用接口Interface,到這里我們又發(fā)現(xiàn)一點(diǎn),使用接口作為標(biāo)識(shí)的話,只能用在類、接口和結(jié)構(gòu)中,而不能用在他們的成員上,畢竟接口的主要作用是定義一組方法契約(即抽象)!
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class AutowiredAttribute : Attribute
{
}
這個(gè)特性里面什么也沒(méi)有,主要是下面這個(gè)類,裝配操作都在這里:
/// <summary>
/// 從容器裝配service
/// </summary>
[AppService]
public class AutowiredService
{
IServiceProvider serviceProvider;
public AutowiredService(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public void Autowired(object service)
{
var serviceType = service.GetType();
//字段賦值
foreach (FieldInfo field in serviceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var autowiredAttr = field.GetCustomAttribute<AutowiredAttribute>();
if (autowiredAttr != null)
{
field.SetValue(service, serviceProvider.GetService(field.FieldType));
}
}
//屬性賦值
foreach (PropertyInfo property in serviceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
if (autowiredAttr != null)
{
property.SetValue(service, serviceProvider.GetService(property.PropertyType));
}
}
}
}
我們剛剛寫的[AppService]特性在這里已經(jīng)用上了,并且這個(gè)類使用構(gòu)造器注入了IServiceProvider。Autowired(object service)方法的參數(shù)是要裝配的服務(wù)實(shí)例,首先獲取服務(wù)類型,再使用反射查詢有AutowiredAttribute特性的字段和屬性,我們?cè)跇?gòu)造器注入了serviceProvider,這里便可以使用serviceProvider的GetService方法從容器中獲取對(duì)應(yīng)類型的實(shí)例來(lái)給字段和屬性賦值。 整個(gè)過(guò)程就是這樣,簡(jiǎn)單明了。開(kāi)始的時(shí)候我想使用靜態(tài)類來(lái)編寫AutowiredService,但是靜態(tài)類沒(méi)法注入IServiceProvider,解決方法也有,可以使用定位器模式全局保存IServiceProvider:
/// <summary>
/// 服務(wù)提供者定位器
/// </summary>
public static class ServiceLocator
{
public static IServiceProvider Instance { get; set; }
}
在Setup的Configure方法中賦值:
ServiceLocator.Instance = app.ApplicationServices;
這樣在靜態(tài)的AutowiredService中也就可以訪問(wèn)IServiceProvider了,但是使其自己也注冊(cè)成服務(wù)能更好的和其他組件交互,java有了spring框架,大家都認(rèn)可spring,一切都在容器中,一切都可注入,spring提供了統(tǒng)一的對(duì)象管理,非常好,我感覺(jué)netcore的將來(lái)也將會(huì)是這樣。
Autowired(object service)方法的實(shí)現(xiàn)雖然簡(jiǎn)單,但是使用了效率底下的反射,這個(gè)美中不足需要改進(jìn),以前可以使用晦澀難懂的EMIT來(lái)編寫,現(xiàn)在有Expression,編寫和閱讀都簡(jiǎn)單了好多,并且效率也不比EMIT差,所以我們使用表達(dá)式+緩存來(lái)改進(jìn)。Autowired方法要做的就是從容器中取出合適的對(duì)象,然后賦值給service要自動(dòng)裝配的字段和屬性,據(jù)此我們先編寫出委托的偽代碼:
(obj,serviceProvider)=>{
((TService)obj).aa=(TAAType)serviceProvider.GetService(aaFieldType);
((TService)obj).bb=(TBBType)serviceProvider.GetService(aaFieldType);
...
}
注意偽代碼中的類型轉(zhuǎn)換,Expression表達(dá)式在編譯成委托時(shí)是非常嚴(yán)格的,所有轉(zhuǎn)換都不能省。寫表達(dá)式的時(shí)候我習(xí)慣先寫偽代碼,我希望大家也能養(yǎng)成這個(gè)習(xí)慣!有了偽代碼我們可以開(kāi)始改造AutowiredService類了:
/// <summary>
/// 從容器裝配service
/// </summary>
[AppService]
public class AutowiredService
{
IServiceProvider serviceProvider;
public AutowiredService(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
Dictionary<Type, Action<object, IServiceProvider>> autowiredActions = new Dictionary<Type, Action<object, IServiceProvider>>();
public void Autowired(object service)
{
Autowired(service, serviceProvider);
}
/// <summary>
/// 裝配屬性和字段
/// </summary>
/// <param name="service"></param>
/// <param name="serviceProvider"></param>
public void Autowired(object service, IServiceProvider serviceProvider)
{
var serviceType = service.GetType();
if (autowiredActions.TryGetValue(serviceType, out Action<object, IServiceProvider> act))
{
act(service, serviceProvider);
}
else
{
//參數(shù)
var objParam = Expression.Parameter(typeof(object), "obj");
var spParam = Expression.Parameter(typeof(IServiceProvider), "sp");
var obj = Expression.Convert(objParam, serviceType);
var GetService = typeof(IServiceProvider).GetMethod("GetService");
List<Expression> setList = new List<Expression>();
//字段賦值
foreach (FieldInfo field in serviceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var autowiredAttr = field.GetCustomAttribute<AutowiredAttribute>();
if (autowiredAttr != null)
{
var fieldExp = Expression.Field(obj, field);
var createService = Expression.Call(spParam, GetService, Expression.Constant(field.FieldType));
var setExp = Expression.Assign(fieldExp, Expression.Convert(createService, field.FieldType));
setList.Add(setExp);
}
}
//屬性賦值
foreach (PropertyInfo property in serviceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
if (autowiredAttr != null)
{
var propExp = Expression.Property(obj, property);
var createService = Expression.Call(spParam, GetService, Expression.Constant(property.PropertyType));
var setExp = Expression.Assign(propExp, Expression.Convert(createService, property.PropertyType));
setList.Add(setExp);
}
}
var bodyExp = Expression.Block(setList);
var setAction = Expression.Lambda<Action<object, IServiceProvider>>(bodyExp, objParam, spParam).Compile();
autowiredActions[serviceType] = setAction;
setAction(service, serviceProvider);
}
}
}
代碼一下子多了不少,不過(guò)由于我們前面的鋪墊,理解起來(lái)也不難,至此自動(dòng)裝配字段和屬性的服務(wù)已經(jīng)寫好了,下面看看如何使用:
編寫服務(wù)類,并添加[AppService]特性
[AppService]
public class MyService
{
//functions
}
在Setup的ConfigureServices方法中注冊(cè)應(yīng)用服務(wù)
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//注冊(cè)應(yīng)用服務(wù)
services.AddAppServices();
}
在其他類中注入使用,比如Controller中
public class HomeController : Controller
{
[Autowired]
MyUserService myUserService;
public HomeController(AutowiredService autowiredService)
{
autowiredService.Autowired(this);
}
}
HomeController的構(gòu)造函數(shù)是不是簡(jiǎn)潔了許多呢!而且再有新的服務(wù)要注入,只要定義字段(屬性也可以,不過(guò)字段更方便)就可以了,注意:我們定義的字段不能是只讀的,因?yàn)槲覀円贏utowiredService中設(shè)置。我們還用上面的例子,看一下它的威力吧!
public class HomeController : Controller
{
[Autowired]
UserService userService;
[Autowired]
OrderService orderService;
[Autowired]
MsgService msgService;
[Autowired]
OtherService otherService;
[Autowired]
OtherService2 otherService2;
public HomeController(AutowiredService autowiredService)
{
autowiredService.Autowired(this);
}
}
感謝您的觀看!全文已經(jīng)完了,我們沒(méi)有使用第三方容器,也沒(méi)有對(duì)自帶的容器大肆修改和破壞,只是在服務(wù)類的構(gòu)造器中選擇性的調(diào)用了AutowiredService.Autowired(this)方法,為什么是選擇性的呢,因?yàn)槟氵€可以使用在構(gòu)造器中注入的方式,甚至混用,一切都好,都不會(huì)錯(cuò)亂。
nuget安裝:
PM> Install-Package Autowired.Core
git源碼:
[Autowired.Core] https://gitee.com/loogn/Autowired.Core
更新:
- 支持多個(gè)AppServiceAttribute,
- 支持服務(wù)唯一標(biāo)識(shí),通過(guò)Identifier指定服務(wù)實(shí)現(xiàn)
到此這篇關(guān)于在.net core中實(shí)現(xiàn)字段和屬性注入的示例代碼的文章就介紹到這了,更多相關(guān).net core 字段和屬性注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net 使用Silverlight操作ASPNETDB數(shù)據(jù)庫(kù)
asp.net下使用Silverlight操作ASPNETDB數(shù)據(jù)庫(kù)的實(shí)現(xiàn)代碼2010-01-01
asp.net下實(shí)現(xiàn)URL重寫技術(shù)的代碼
asp.net下實(shí)現(xiàn)URL重寫技術(shù)的代碼...2007-10-10
在ASP.Net Core中使用Lamar的全過(guò)程
這篇文章主要給大家介紹了關(guān)于在ASP.Net Core中使用Lamar的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
c# rabbitmq 簡(jiǎn)單收發(fā)消息的示例代碼
這篇文章主要介紹了c# rabbitmq 簡(jiǎn)單收發(fā)消息的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
通過(guò)HttpClient 調(diào)用ASP.NET Web API示例
本篇文章主要介紹了通過(guò)HttpClient 調(diào)用ASP.NET Web API示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03

