1. 程式人生 > >從零開始寫C# MVC框架之--- 用autofac ioc 容器實現依賴注入

從零開始寫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,大功告成,以後專案中所有的介面呼叫都是如此,只調介面即可