從零開始寫C# MVC框架之--- 用autofac ioc 容器實現依賴注入
本章查找了一篇對依賴注入解釋得非常不錯的文章為基礎,再加上自己的理解,不然還真不好用語言的方式表達清楚,引用下面這位仁兄的文章
依賴注入產生的背景:
隨著面向物件分析與設計的發展,一個良好的設計,核心原則之一就是將變化隔離,使得變化部分發生變化時,不變部分不受影響。為了做到這一點,要利用面向物件中的多型性,使用多型性後,客戶類不再直接依賴服務類,而是依賴於一個抽象的介面,這樣,客戶類就不能在內部直接例項化具體的服務類。但是,客戶類在運作中又客觀需要具體的服務類提供服務,因為介面是不能例項化去提供服務的。就產生了“客戶類不準例項化具體服務類”和“客戶類需要具體服務類”這樣一對矛盾。為了解決這個矛盾,開發人員提出了一種模式:客戶類定義一個注入點,用於服務類的注入,而客戶類的客戶類負責根據情況,例項化服務類,注入到客戶類中,從而解決了這個矛盾。
依賴注入的正式定義:
依賴注入(Dependency Injection),是這樣一個過程:由於某客戶類只依賴於服務類的一個介面,而不依賴於具體服務類,所以客戶類只定義一個注入點。在程式執行過程中,客戶類不直接例項化具體服務類例項,而是客戶類的執行上下文環境或專門元件負責例項化服務類,然後將其注入到客戶類中,保證客戶類的正常執行。
說了這麼多依賴注入的解釋,那什麼IOC容器,用於實現依賴注入功能的元件或框架,就是IoC Container,比如本框架中用到的 autofac。
文章只描述了介面和實現,重點是面向介面程式設計的方式,但是控制器呼叫介面,介面怎樣去自動呼叫實現類,就是本章討論的重點,只要實現autofac的注入功能,就能解決 控制器中呼叫介面,介面自動呼叫服務實現類 。
以上是理論講解,下面結合系統程式碼:新增 Zy.Utility.ServicesProvider 專案,用於實現 autofac 依賴注入功能
實現原理,外掛配置和反射獲取程式集,再建立介面例項。
1、新增 Autofac 、Autofac.Configuration 的引用
2、獲取程式集配置資訊,用於反射獲取介面與服務的程式集,再用 (T)Activator.CreateInstance 方法建立服務介面實現類例項
/// <summary> /// 例項建立代理 /// </summary> /// <typeparam name="T"></typeparam> public static class Instance<T> { /// <summary> /// 配置檔案節處理者 /// </summary> static SectionHandler handler; /// <summary> /// 已手動配置的服務對應關係 /// </summary> static Hashtable ServiceNameSpaceMapper = null; /// <summary> /// 已註冊的型別 /// </summary> static Dictionary<Type, Type> RegistedTypes = new Dictionary<Type, Type>(); /// <summary> /// 初始化 /// </summary> static Instance() { var reader = new ConfigurationSettingsReader("autofac"); handler = reader.SectionHandler; GetDefaultNamespaceRegisterMapper(); } /// <summary> /// 檢查是否已經進行了顯示的配置 /// </summary> /// <typeparam name="T">待檢查的型別</typeparam> /// <returns>如果已經在配置檔案中配置返回true,否則返回false</returns> static bool HasConfiguration<T>() { return handler.Components.Any(item => item.Service.Contains(typeof(T).FullName)); } /// <summary> /// 自動建立對應型別的實現 /// </summary> public static T Create { get { try { var hasconfiguration = HasConfiguration<T>(); var interfaceType = typeof(T); if (!hasconfiguration)//配置檔案中未配置,採用預設註冊 { if (!RegistedTypes.ContainsKey(interfaceType)) RegisteType<T>(); } var targetImplementType = RegistedTypes[interfaceType]; return (T)Activator.CreateInstance(targetImplementType); } catch (Exception ex) { throw new ServiceInstacnceCreateException(typeof(T).Name + "服務例項建立失敗", ex); } } } /// <summary> /// 註冊T型別 /// </summary> /// <typeparam name="T"></typeparam> static void RegisteType<T>() { var interfaceType = typeof(T); string fullName = interfaceType.FullName; string iserviceName = interfaceType.Name; lock (fullName) { if (!RegistedTypes.ContainsKey(interfaceType)) { string namespaceName = fullName.Substring(0, fullName.LastIndexOf('.'));//獲取目標型別名稱空間 string implementClass = ServiceNameSpaceMapper[namespaceName] as string;//從配置中獲取對應名稱空間的實現名稱空間的名稱 if (implementClass == null) throw new ApplicationException("未配置" + fullName + "的實現"); string nameSpace = implementClass.Split(',')[0];//獲取對應實現類的名稱空間 string assembly = implementClass.Split(',')[1];//獲取對應實現類的程式集名稱 string implementName = iserviceName.Substring(1);//獲取對應實現類的名稱 string className = string.Format("{0}.{1}, {2}", nameSpace, implementName, assembly);//獲取對應實現類的全名 Type implementType = Type.GetType(className);//根據實現類全名建立型別 if (implementType == null) throw new ApplicationException("未找到" + className); RegistedTypes.Add(interfaceType, implementType);//將目標型別加入已註冊型別列表中,表示已經註冊過該型別的實現 } } } /// <summary> /// 獲取手動配置的名稱空間註冊對應關係 /// </summary> static void GetDefaultNamespaceRegisterMapper() { ServiceNameSpaceMapper = new Hashtable(); var config = ConfigurationManager.GetSection("serviceProvider") as ServiceProviderConfig; foreach (var item in config.Items) { if (ServiceNameSpaceMapper.ContainsKey(item.Interface)) throw new ApplicationException("配置檔案中最多隻能配置一個" + item.Interface + "的實現"); ServiceNameSpaceMapper.Add(item.Interface, item.NameSpace + "," + item.Assembly); } } }
3、新建一個ServiceHelper類,用於呼叫 上面程式碼的 Create 方法,Web控制器使用此類。
4、在Web專案的web.config中配置,服務介面和實現的名稱空間分別是:Zy.Xn.IServices 、 Zy.Xn.Services ,因此,配置程式碼如下:
<configSections>
<section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration" />
<section name="serviceProvider" type="Zy.Utility.ServicesProvider.ServiceProviderConfig,Zy.Utility.ServicesProvider" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<serviceProvider>
<item interface="Zy.Xn.IServices" assembly="Zy.Xn.Services" namespace="Zy.Xn.Services"></item>
</serviceProvider>
以上是依賴注入的原理和實現,下面是在專案中的使用,演示使用者登入功能
1、使用者服務介面:
public interface IUserService : IService
{
/// <summary>
/// 獲取使用者
/// </summary>
/// <param name="loginUserName"></param>
/// <returns></returns>
User GetUser(string loginUserName);
}
2、使用者服務介面實現:
public class UserService : ServiceBase, IUserService
{
/// <summary>
/// 獲取使用者
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public User GetUser(string loginUserName)
{
return context.Users.FirstOrDefault(item => item.LoginUserName == loginUserName);
}
}
3、登入功能LoginController:
/// <summary>
/// 登入
/// </summary>
/// <param name="userName">使用者名稱</param>
/// <param name="password">密碼</param>
/// <returns></returns>
[HttpPost]
public JsonResult Login(string userName, string password)
{
bool flag = false;
string msg = string.Empty;
if (!string.IsNullOrWhiteSpace(userName) && !string.IsNullOrWhiteSpace(password))
{
var user = <strong>ServiceHelper.Create<IUserService>().GetUser(userName)</strong>;
if (user != null)
{
if (user.LoginPassword == password)
{
Session[CacheKeys.User_Info_Cache_Key] = user;
Session.Timeout = CacheKeys.User_Info_Cache_Time;
msg = "登入成功";
flag = true;
}
else
msg = "密碼錯誤";
}
else
msg = "不存在該使用者";
}
else
msg = "請傳入使用者名稱和密碼";
return Json(new { success = flag , msg = msg});
}
OK,大功告成,以後專案中所有的介面呼叫都是如此,只調介面即可