本篇文章,讲解如何使用Auotfac, Asp.net MVC和EF Code First,搭建一个松散的架构。 例子代码主要完成的功能是:
列出数据库中Student表中的所有学生信息。
阅读目录:
一、 使用Entity Framework Code First, 写代码创建Student表
二、使用Migrations, 生成数据库和初始化数据
三、创建Controller方法和View
四、正式项目开发中的困境
五、解耦合,脱离数据层
六、实例化,可恶的实例化
七、使用Autofac依赖注入
八、总结
public class SchoolContext : DbContext { public SchoolContext() : base("DefaultConnection") { } public DbSet<Student> Students { get; set; } } [Table("Student")] public class Student { [Key] public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
打开"package manager console"
运行Migration, 生成数据库更新代码
会在项目中生成Migrations文件夹,以及2个代码文件。
修改代码, 在Seed方法中,添加程序的初始化数据, 添加3条记录
protected override void Seed(SchoolContext context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // context.Students.AddOrUpdate( s => s.Id, new Student {Name = "Andrew Peters", Age = 18}, new Student {Name = "Brice Lambson", Age = 29}, new Student {Name = "Rowan Miller", Age = 56} ); }
执行"Update-Database"命令,生成和代码匹配的数据库
下图是生成的数据库结果:
controller代码非常简单,如下:
public class HomeController : Controller { public ActionResult Index() { var studentRepository = new StudentRepository(); var students = studentRepository.GetStudents();//调用数据层方法,获取数据 return View(students); } }
最后,运行起来的效果:
假设一个在一个真实的项目环境里面,你和甲一起开发整个项目, 其中甲负责EF数据访问部分,你负责MVC逻辑和显示部分。
在真实项目中当然远远不止Student一个表,可能有上百个,还有很多的存储过程。
你在开发的过程中,常常运行遇到数据层的bug,不断抛出异常,导致你的开发无法顺利进行下去。你常常需要停下来,调试到数据层,找到bug原因,然后告诉甲赶快改好,你还等着开发页面上的一个ajax特效。
随着不断的出现的数据层bug, 眼看项目结束日期越来越近,你已经焦头烂额,但是却还有很多功能没有完成,老板也开始怀疑你的能力..........
你对甲已经忍无可忍了,你的命运为什么要掌握在甲的手中,要想办法摆脱甲。
好吧,我要依赖在抽象的接口上,而不是直接依赖甲开发的数据层。
首先我们可以创建一个接口:
public interface IStudentRepository { IEnumerable<Student> GetStudents(); }
然后, 创建一个集成这个接口的类,这个类并不访问数据库,但是提供我们开发页面所需的数据。
public class StubStudentRepository:IStudentRepository { public IEnumerable<Student> GetStudents() { return new[] { new Student {Id = 1, Name = "Sam", Age = 14} }; } }
好了,一切都准备好了, 开始改造我们的Controller代码
public class HomeController : Controller { public ActionResult Index() { IStudentRepository studentRepository = new StubStudentRepository(); //IStudentRepository studentRepository = new StudentRepository();//注释掉访问数据层的代码,用Stub类代替 var students = studentRepository.GetStudents(); return View(students); } }
由于,我们写的Stub类,不访问数据库,而且不需要有复杂的逻辑,只是提供我们Controller代码运行所需要的基本数据就可以了。这样你的开发就依赖在你自己写的更加可靠的Stub类上了。
最后,你叫来甲,对他说:哥们, 我为我们之间的依赖,创建好了接口,你以后的数据访问代码,都从这个接口继承吧。
从此,这个项目开发变成了另外一种样子,你再也不抱怨甲总是写不稳定的代码了(因为你不依赖他了),你总是能通过写一些Stub类,返回不同的值,来测试你的界面代码。
在Controller的代码中,我们有下面2行代码,如果是发布的情况下,我们使用下面一行,开发过程中,使用上面一行。
但是,这个项目代码太多了,难道到发布的时候,要我一个个找出来,都换成真实的甲的数据库访问层的类的实例吗?
IStudentRepository studentRepository = new StubStudentRepository(); //IStudentRepository studentRepository = new StudentRepository();//注释掉访问数据层的代码,用Stub类代替
这个时候,就是Autofac大显身手的时候了,
首先,我们改造Controller代码:
public class HomeController : Controller { private readonly IStudentRepository _studentRepository; //由构造函数来提供Controller的依赖IStudentRepository public HomeController(IStudentRepository studentRepository) { _studentRepository = studentRepository; } public ActionResult Index() { var students = _studentRepository.GetStudents(); return View(students); } }
然后, 修改Global.asax,
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { //Autofac初始化过程 var builder = new ContainerBuilder(); builder.RegisterControllers(typeof(MvcApplication).Assembly);//注册所有的Controller //开发环境下,使用Stub类 builder.RegisterAssemblyTypes(typeof (MvcApplication).Assembly).Where( t => t.Name.EndsWith("Repository") && t.Name.StartsWith("Stub")).AsImplementedInterfaces(); //发布环境下,使用真实的数据访问层 //builder.RegisterAssemblyTypes(typeof(MvcApplication).Assembly).Where( // t => t.Name.EndsWith("Repository")).AsImplementedInterfaces(); var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); //其它的初始化过程 ........ } }
当我们使用下面这行代码的时候,所有的controller就都是使用Stub类的实例
//开发环境下,使用Stub类 builder.RegisterAssemblyTypes(typeof (MvcApplication).Assembly).Where( t => t.Name.EndsWith("Repository") && t.Name.StartsWith("Stub")).AsImplementedInterfaces();
当我们使用下面代码的时候,所有的controller就都用的是实际的数据访问类实例。
//发布环境下,使用真实的数据访问层 builder.RegisterAssemblyTypes(typeof(MvcApplication).Assembly).Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces();
关于Autofac的详细具体用法,大家可以上官方网站: http://code.google.com/p/autofac/
这里也有文章,对Autofac用法有总结 AutoFac使用方法总结
对于Autofac内部实现的机理, 这里有一篇文章,IoC容器Autofac(3) - 理解Autofac原理,我实现的部分Autofac功能(附源码)
对于Autofac在Asp.net MVC中是如何实现依赖注入的分析,这里有篇文章 分析Autofac如何实现Controller的Ioc(Inversion of Control)
其它相关文章
IoC容器Autofac(1) -- 什么是IoC以及理解为什么要使用Ioc
IoC容器Autofac(2) - 一个简单示例(附demo源码)
IoC容器Autofac(3) - 理解Autofac原理,我实现的部分Autofac功能(附源码)
最后,附上本文相关源代码 AutofactMVC.zip使用Nuget, 如果有编译错误, 参照这篇文章 Nuget如何自动下载依赖DLL引用