AutoFac (控制反转IOC 与依赖注入DI)
重要的参考资料http://www.cnblogs.com/xdp-gacl/p/4249939.html
谈谈对Spring 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 } } }
然后我们在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(); } } }
使用
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(); } } }
用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 } }
Home控制器
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(); }
作者:一个大西瓜
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!