AutoFac (控制反转IOC 与依赖注入DI)

重要的参考资料http://www.cnblogs.com/xdp-gacl/p/4249939.html

谈谈对Spring IOC的理解


IOC概念(很重要)

项目

先引入AutoFac 和AutoFac MVC两个程序集到项目中


 
然后我们在MVC(UI层)的App_Start文件夹下创建一个AutoFacConfig.cs类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace FB.CMS.MvcSite.App_Start
{
    using Autofac;
    using Autofac.Integration.Mvc;
    using System.Reflection;
    using System.Web.Mvc;

    /// <summary>
    /// 这个类是我自己定义的一个类,主要用初始化AutoFac容器的相关数据
    /// </summary>
    public class AutoFacConfig
    {


        public static void Register()
        {
            //初始化AutoFac的相关功能
            /*
             1.0 告诉AutoFac初始化数据仓储层FB.CMS.Repository.dll中所有类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中
             2.0 告诉AutoFac初始化业务逻辑层FB.CMS.Services.dll中所有类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中
             3.0 将MVC默认的控制器工厂替换成AutoFac的工厂
             */

            //第一步: 构造一个AutoFac的builder容器
            ContainerBuilder builder = new Autofac.ContainerBuilder();

            //第二步:告诉AutoFac控制器工厂,控制器类的创建去哪些程序集中查找(默认控制器工厂是去扫描bin目录下的所有程序集)
            //2.1 从当前运行的bin目录下加载FB.CMS.MvcSite.dll程序集
            Assembly controllerAss = Assembly.Load("FB.CMS.MvcSite");

            //2.2 告诉AutoFac控制器工厂,控制器的创建从controllerAss中查找(注意:RegisterControllers()方法是一个可变参数,如果你的控制器类的创建需要去多个程序集中查找的话,那么我们就再用Assembly controllerBss=Assembly.Load("需要的程序集名")加载需要的程序集,然后与controllerAss组成数组,然后将这个数组传递到RegisterControllers()方法中)
            builder.RegisterControllers(controllerAss);



            //第三步:告诉AutoFac容器,创建项目中的指定类的对象实例,以接口的形式存储(其实就是创建数据仓储层与业务逻辑层这两个程序集中所有类的对象实例,然后以其接口的形式保存到AutoFac容器内存中,当然如果有需要也可以创建其他程序集的所有类的对象实例,这个只需要我们指定就可以了)

            //3.1 加载数据仓储层FB.CMS.Repository这个程序集。
            Assembly repositoryAss = Assembly.Load("FB.CMS.Repository");
            //3.2 反射扫描这个FB.CMS.Repository.dll程序集中所有的类,得到这个程序集中所有类的集合。
            Type[] rtypes = repositoryAss.GetTypes();
            //3.3 告诉AutoFac容器,创建rtypes这个集合中所有类的对象实例
            builder.RegisterTypes(rtypes)
                .AsImplementedInterfaces(); //指明创建的rtypes这个集合中所有类的对象实例,以其接口的形式保存

            //3.4 加载业务逻辑层FB.CMS.Services这个程序集。
            Assembly servicesAss = Assembly.Load("FB.CMS.Services");
            //3.5 反射扫描这个FB.CMS.Services.dll程序集中所有的类,得到这个程序集中所有类的集合。
            Type[] stypes = servicesAss.GetTypes();
            //3.6 告诉AutoFac容器,创建stypes这个集合中所有类的对象实例
            builder.RegisterTypes(stypes)
                .AsImplementedInterfaces(); //指明创建的stypes这个集合中所有类的对象实例,以其接口的形式保存


            //第四步:创建一个真正的AutoFac的工作容器
            var container = builder.Build();


            //我们已经创建了指定程序集的所有类的对象实例,并以其接口的形式保存在AutoFac容器内存中了。那么我们怎么去拿它呢?
            //从AutoFac容器内部根据指定的接口获取其实现类的对象实例
            //假设我要拿到IsysFunctionServices这个接口的实现类的对象实例,怎么拿呢?
            //var obj = container.Resolve<IsysFunctionServices>(); //只有有特殊需求的时候可以通过这样的形式来拿。一般情况下没有必要这样来拿,因为AutoFac会自动工作(即:会自动去类的带参数的构造函数中找与容器中key一致的参数类型,并将对象注入到类中,其实就是将对象赋值给构造函数的参数)


            //第五步:将当前容器中的控制器工厂替换掉MVC默认的控制器工厂。(即:不要MVC默认的控制器工厂了,用AutoFac容器中的控制器工厂替代)此处使用的是将AutoFac工作容器交给MVC底层 (需要using System.Web.Mvc;)
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

            //我们知道控制器的创建是调用MVC默认的控制器工厂,默认的控制器工厂是调用控制器类的无参构造函数
            //可是我们如果要使用AutoFac自动工厂,将对象通过构造函数注入类中,那么这个构造函数就需要带参数
            //如果我们将控制器的无参构造函数删除,保留带参数的构造函数,MVC默认的控制器工厂来创建控制的时候
            //就会去调用无参的构造函数,可是这时候发现没有无参的构造函数于是就报“没有为该对象定义无参数的构造函数”错误
            //既然报错,那我们如果保留无参的构造函数,同时在声明一个带参数的构造函数是否可行呢?
            //答案;行是行,但是创建控制器的时候,MVC默认的控制器工厂调用的是无参构造函数,它并不会去调用有参的构造函数
            //这时候,我们就只能将AutoFac它的控制器工厂替换调用MVC默认的控制器工厂(控制器由AutoFac的控制器工厂来创建)
            //而AutoFac控制器工厂在创建控制的时候只会扫描带参数的构造函数,并将对象注入到带参数的构造函数中
            //AutofacDependencyResolver这个控制器工厂是继承了 IDependencyResolver接口的,而IDependencyResolver接口是MVC的东西
            //MVC默认的控制器工厂名字叫:DefaultControllerFactory
            //具体参考:http://www.cnblogs.com/artech/archive/2012/04/01/controller-activation-032.html

        }
    }
}
View Code

然后我们在Global.asax文件中的Application_Start()方法中来调用这个类

using FB.CMS.MvcSite.App_Start;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace FB.CMS.MvcSite
{
    // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
    // 请访问 http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //第一: 在网站一启动的时候就初始化AutoFac的相关功能
            /*
             1.0 告诉AutoFac初始化数据仓储层FB.CMS.Repository.dll中所有类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中
             2.0 告诉AutoFac初始化业务逻辑层FB.CMS.Services.dll中所有类的对象实例。这些对象实例以其接口的形式保存在AutoFac容器中
             3.0 将MVC默认的控制器工厂替换成AutoFac的工厂
             */

            //具体做法就是我们去App_Start文件夹下创建一个AutoFacConfig类,具体实现什么功能去这个类中实现。然后再这里调用下这个类
            AutoFacConfig.Register();
        }
    }
}
View Code
使用
Home控制器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace FB.CMS.MvcSite.Controllers
{
    using FB.CMS.IServices;
    public class HomeController : Controller
    {
        IsysFunctionServices dal;
        public HomeController(IsysFunctionServices dal) //依赖构造函数进行对象注入
        {
            this.dal = dal; //在构造函数中初始化HomeController控制器类的dal属性 (这个dal属性的类型是IsysFunctionServices)
        }

        public ActionResult Index()
        {
            var a = dal.QueryWhere(r => r.fID > 20).ToList(); //查询
            return View();
        }
    }
}
View Code

用AutoFac 在一个控制器下通过构造函数,注入多个对象的时候,我们可以对BaseDal进行优化

BaseDal类 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FB.CMS.Repository
{
    using FB.CMS.IRepository;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq.Expressions;
    using System.Runtime.Remoting.Messaging;
    using System.Threading;
    public class BaseDal<TEntity> : IBaseDal<TEntity> where TEntity : class
    {
        //BaseDbContext db = new BaseDbContext();

        //对创建上下文容器类对象进行优化(原理:一个线程下我们只创建一个上下文容器类对象,然后保存到线程缓存当中去,当同一个线程过来的时候,就从线程缓存当中取上下文容器类对象)
        public BaseDbContext db
        {
            get
            {
                //获取BaseDbContext的完全限定名,其实这个名字没什么特别的意义,仅仅是一个名字而已,也可以取别的名字的
                string threadName = typeof(BaseDbContext).FullName;
                //获取key为threadName的这个线程缓存(CallContext就是线程缓存容器类)
                object dbObj = CallContext.GetData(threadName);
                //如果key为threadName的线程缓存不存在
                if (dbObj == null)
                {
                    //创建BaseDbContext类的对象实例
                    dbObj = new BaseDbContext();
                    //将这个BaseDbContext类的对象实例保存到线程缓存当中(以键值对的形式进行保存的,我这就将key设为当前线程的完全限定名了)
                    CallContext.SetData(threadName, dbObj);
                    return dbObj as BaseDbContext;
                }
                return dbObj as BaseDbContext;
            }

        }
        DbSet<TEntity> _dbset;
        public BaseDal()
        {
            this._dbset = db.Set<TEntity>(); //初始化
        }

        #region 增加
        public void AddEnity(TEntity model)
        {
            if (model == null)
            {
                throw new Exception("moddel不能为null");
            }
            this._dbset.Add(model);


        }

        #endregion

        #region 物理删除
        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="model">实体类</param>
        /// <param name="isaddedContext">是否物理删除</param>
        public void DeleteEntity(TEntity model, bool isaddedContext)
        {
            if (model == null)
            {
                throw new Exception("DeleteEntity方法中的model不能为null");
            }
            //如果仅仅是逻辑删除的话,那我们只要调用编辑方法将标识为逻辑删除的那个字段修改为true就可以了。
            if (isaddedContext == true)
            {
                this._dbset.Attach(model);

            }
            this._dbset.Remove(model);
        }

        #endregion

        #region 查寻
        /// <summary>
        /// 普通带条件查询
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public IQueryable<TEntity> QueryWhere(Expression<Func<TEntity, bool>> where)
        {
            return this._dbset.Where(where);
        }

        /// <summary>
        /// 连表查询
        /// </summary>
        /// <param name="where">连表查询的条件筛选查询</param>
        /// <param name="tablesName">要做连表查询的所有表名集合</param>
        /// <returns></returns>
        public IQueryable<TEntity> QueryJoin(Expression<Func<TEntity, bool>> where, string[] tablesName)
        {
            if (tablesName == null || tablesName.Any() == false)
            {
                throw new Exception("连表查询最少也要一个表,所有QueryJoin方法中tablesName中最少也需要有一个表名");
            }

            DbQuery<TEntity> query = this._dbset;
            foreach (string tableName in tablesName)
            {
                //不断的连表,直到把tablesName里的所有表都连完
                query = query.Include(tableName);
            }
            return query.Where(where); //然后对连表进行条件筛选查询
        }

        /// <summary>
        /// 带条件的分页查询
        /// </summary>
        /// <typeparam name="TKey">按哪个字段进行排序</typeparam>
        /// <param name="pageindex">当前页</param>
        /// <param name="pagesize">页大小</param>
        /// <param name="rowCount">数据总条数</param>
        /// <param name="order">排序</param>
        /// <param name="where">筛选条件</param>
        /// <returns></returns>
        public IQueryable<TEntity> QueryByPage<TKey>(int pageindex, int pagesize, out int rowCount, Expression<Func<TEntity, TKey>> order, Expression<Func<TEntity, bool>> where)
        {
            //获取总条数
            rowCount = this._dbset.Count(where);

            //建议将这个Where条件语句放在前面,如果你放到后面,分页的时候可能存在问题。
            return this._dbset.Where(where).OrderByDescending(order).Skip((pageindex - 1) * pagesize).Take(pagesize);

        }

        /// <summary>
        /// 调用存储过程或执行SQL语句(但是我们不推荐执行sql语句)
        /// </summary>
        /// <typeparam name="TElement">
        /// 因为存储过程返回的数据不一定就是TEntity这个实体,因为存储过返回的结果集有可能是自己拼接出来的,所以这个方法的返回结果
        /// 为Lsit<TEntity>就不合适了。 这个 TElement是在调用的存储过程的时候传入的一个实体,此实体必须和调用的存储过程的返回结集
        /// 中的字段名称保存一致(你这个存储过程返回有多个字段,那么你这个实体中就应该有多少个属性)
        /// </typeparam>
        /// <param name="sql">
        /// 假设我创建了这么一个存储过程:
        /// create proc proc_T_UserInfo_Paging2(@pageSize int,@currentPage int,@CountData  )  
        /// 那现在我们调用这个存储过程,那么这个SQL语句的写法就是:
        /// proc_T_UserInfo_Paging2 @pageSize int,@currentPage int,@CountData       /// 
        /// </param>
        /// <param name="prms">参数化查询的参数数组</param>
        /// <returns></returns>
        public List<TElement> RunProc<TElement>(string sql, params object[] prms)
        {
            return db.Database.SqlQuery<TElement>(sql, prms).ToList();
        }
        #endregion

        #region 编辑
        /// <summary>
        /// 编辑
        /// </summary>
        /// <param name="model">实体</param>
        /// <param name="propertyNames">要编辑的的所有属性名称序列</param>
        public void EditEntity(TEntity model, string[] propertyNames)
        {
            if (model == null)
            {
                throw new Exception("EditEntity方法中的参数model不能为null");
            }
            if (propertyNames.Any() == false || propertyNames == null)
            {
                throw new Exception("EditEntity方法中的参数propertyNames最少需要一个属性");
            }
            System.Data.Entity.Infrastructure.DbEntityEntry entry = db.Entry(model);
            entry.State = System.Data.EntityState.Unchanged;
            foreach (string item in propertyNames)
            {
                entry.Property(item).IsModified = true;
            }
            db.Configuration.ValidateOnSaveEnabled = false;
        }

        #endregion

        #region 统一保存
        public int SaveChanges()
        {
            return db.SaveChanges();
        }
        #endregion
    }
}
View Code

Home控制器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace FB.CMS.Site.Controllers
{
    using FB.CMS.IRepository;
    
    public class HomeController : Controller
    {
        IsysFunctionRepository fundal;
        IsysKeyValueRepository kvdal;

        //我们在通过AutoFac将IsysFunctionRepository和IsysKeyValueRepository的两个实现类的对象实例注入到当前构造函数中
        //那么在注入IsysFunctionRepository这实现类的对象的时候就会执行到sysFunctionRepository类,而这个sysFunctionRepository类
        //又是继承了BaseDal类,然后就会去执行BaseDal类的构造函数,而在BaseDal这个类的构造函数中我们执行了
        //this._dbset = db.Set<TEntity>()这段代码,这段代码中使用了db这个上下文容器类对象。既然使用了这个对象,所以就会首先初始化这个对象
        //按照平常的写法,一般都是直接new一个上下文容器对象,即:BaseDbContext db = new BaseDbContext();
        //然后当我们再对构造函数的参数IsysKeyValueRepository kvdal进行对象注入的时候,同样的原理又会执行一次
        //BaseDbContext db = new BaseDbContext();即又创建了一个上下文容器类对象。所以说当我们通过构造函数一次性注入多个对象的时候
        //会创建多个上下文容器类对象。所以这样的做法是非常不好的。因为我们如果在同一个线程里面用两个上下文容器类对象分别去对数据增删改
        //的后,【最后统一执行db.SaveChanges()】;这时候会发现只有后面的那个上下文容器类对象对数据的增删改操作成功,而前面的那个上下文容器类对象对数据的增删
        public HomeController(IsysFunctionRepository fundal, IsysKeyValueRepository kvdal)
        {
            this.fundal = fundal;
            this.kvdal = kvdal;
        }
        
        public ActionResult Index()
        {
            //var model = fundal.QueryWhere(r => r.fID == 20).FirstOrDefault();
            //model.fName = "默认888";
            //fundal.SaveChanges(); //执行保存修改其实就已经操作一次数据库了

            //var model2 = kvdal.QueryWhere(r => r.KID == 3).FirstOrDefault();
            //model2.KName = "公司88"; 

            //fundal.SaveChanges(); //这里又执行一次保存修改操作,又执行了一个数据库,


            //那我们就希望在同一个控制器中(同一个控制中意味着在同一个线程当中)不管操作多少个上下文容器类对象对数据的操作
            //我们只希望最后只执行已给SaveChanges(),这样就能保障最少次的对数据库的操作。从而提高性能
            //那我们怎么做呢?其实很简单,当我们执行构造函数对多个对象进行注入的时候,我们保证只有一个上下文容器类对象实例
            //当我们在Home控制器中通过构造函数注入两个对象,那么这个Home控制器会有一个线程进行管理,那么当这个线程过来的时候
            //我们就创建先去一个线程缓存中去获取一下这个线程名对应的缓存,如果缓存不存在,那么我们就创建一个上下文容器类对象实例
            //并将这个对象实例存储到线程缓存当中,然后返回这个上下文容器类对象。如果缓存存在,那么就直接获取这个缓存进行返回
            //这样就保证了同一线程下,只有个上下文容器类实例对象,这样我们就可以用任何上下文容器类对象对数据的增删改后,最后只
            //需要用任何一个上下文容器类对象执行一次SaveChanges()就可以了

            var model = fundal.QueryWhere(r => r.fID == 20).FirstOrDefault();
            model.fName = "默认888";
            //fundal.SaveChanges(); //执行保存修改其实就已经操作一次数据库了

            var model2 = kvdal.QueryWhere(r => r.KID == 3).FirstOrDefault();
            model2.KName = "公司88";

            fundal.SaveChanges(); //通过对BaseDal中对创建上下文容器类的优化后,我们看到在同一个线程中 两个上下文容器类对象(通过优化后其实这个两个上下文容器类对象其实是同一个上下文容器类对象)同时对数据进行改的操作,最后只执行了一个fundal.SaveChanges(); 方法,就将数据成功保存了。这样就达到了最少操作数据库。性能提升了

            return View(model);
          
        }

    }
}
View Code
直接在控制器中使用方式是
public ActionResult Index()
{
    ContainerBuilder builder = new ContainerBuilder();
    builder.RegisterType<UserInfoSevices>(); //想拿到UserInfoSevices类的实例
    builder.RegisterType<UserInfoRepository>().As<IUserInfoRepository>(); //与之关联的UserInfoRepository类也需要拿到,并转化成接口的形式保存
    var aa = builder.Build().Resolve<UserInfoSevices>().QueryModel(r => r.Age > 0); //在这里使用

    return View();
}
View Code

原文地址:https://www.cnblogs.com/wyt007/p/7882069.html