再加上现在的 EF,Model层以及DAL实现很自然的交给了 EF等ORM框架。加上现在的成熟的 Repository Pattern 和 UnitOfWork Pattern 实践上的Service分层也变成约定俗成。(关于 Repository 和 UnitOfWork 参看:Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application) 。分离出 Repository, UnitOfWork 就是避免在 Controller 里直接写入 Linq2Db 的代码,这样难以实现 Mockup,好比下面的代码:
(详细参考:Walkthrough: Using TDD with ASP.NET MVC)
【没有使用 Repository Pattern 的代码】
【使用 Repository Pattern 的代码】
namespace MvcContacts.Tests.Models { class InMemoryContactRepository : MvcContacts.Models.IContactRepository { private List<Contact> _db = new List<Contact>(); ... 而 Controller 变成这样:
public class HomeController : Controller { IContactRepository _repository; public HomeController() : this(new EF_ContactRepository()) { } public HomeController(IContactRepository repository) { _repository = repository; } public ViewResult Index() { throw new NotImplementedException(); } } }
其实上面的都是引子,用 Unity 目的是进一步推迟 Repository 或者 UnitOfWork (很多时候演变成 Service 了) 的实例化时机,交给了 IoC 容器注入。以达到更灵活切换的目的,比如从 MS Entities 变换到 MySql Entities 或者是从 ObjectContext 变换到 DbContext (CodeFirst)。
下面介绍一下 Unity.MVC3 的实践过程:
1. EF CodeFirst Models
用 EF CodeFirst 创建一个 Models 工程,用来管理 Entities
表很简单,只有一个 DbSet<User>
2. GenericRepository
主要针对 EF(ObjectContext) 和 EF CodeFirst(DbContext) 抽出接口 IRepository
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; namespace GenericRepository { public interface IRepository<T> : IDisposable where T : class { IQueryable<T> AsQueryable(); IEnumerable<T> GetAll(); IEnumerable<T> Find(Expression<Func<T, bool>> where); T Single(Expression<Func<T, bool>> where); T First(Expression<Func<T, bool>> where); void Delete(T entity); void Add(T entity); void Update(T entity); } } DbContextRepository<T> 对应于 DbContext 的 IRepository<T> 实现,通过构造方法注入 Context 实例
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; using System.Linq.Expressions; namespace GenericRepository { public class DbContextRepository<T> : IRepository<T> where T : class { protected DbSet<T> _objectSet; protected DbContext _context; public DbContextRepository(DbContext context) { _objectSet = context.Set<T>(); _context = context; } public IQueryable<T> AsQueryable() { return _objectSet; } public IEnumerable<T> GetAll() { return _objectSet.ToList(); } public IEnumerable<T> Find(Expression<Func<T, bool>> where) { return _objectSet.Where(where); } public T Single(Expression<Func<T, bool>> where) { return _objectSet.Single(where); } public T First(Expression<Func<T, bool>> where) { return _objectSet.First(where); } public void Delete(T entity) { if (_context.Entry(entity).State == System.Data.EntityState.Detached) _objectSet.Attach(entity); _objectSet.Remove(entity); } public void Add(T entity) { _objectSet.Add(entity); } public void Update(T entity) { _objectSet.Attach(entity); _context.Entry(entity).State = System.Data.EntityState.Modified; } public void Dispose() { System.Diagnostics.Trace.WriteLine("context dispose"); _context.Dispose(); } } }
3. MVC Web 应用
(1) 先通过 NuGet 获取 Unity.MVC3
ASP.NET MVC3 中开放了依赖注入容器的接口IDependencyResolver,ASP.NET Controller 被调用时,会利用该接口进行依赖注入。因此可以利用这个接口,
使用任何的依赖注入容器。另外,Unity.MVC3.dll在UnityContainerExtensions 类里扩展了RegisterControllers 方法,
它将为当前 Assembly 所有非 abstract Controller 完成注册(来自 IControllerFactory 的依赖 )
添加完毕,会发现在 Web 工程下多出Bootstrapper.cs 文件
public static class Bootstrapper { public static void Initialise() { var container = BuildUnityContainer(); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); } private static IUnityContainer BuildUnityContainer() { var container = new UnityContainer(); // register all your components with the container here // e.g. container.RegisterType<ITestService, TestService>(); container.LoadConfiguration("default"); container.RegisterControllers(); return container; } }
这里我把依赖关系都放到配置文件里了:
<configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> </configSections> ... <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <alias alias="IRepository" type="GenericRepository.IRepository`1, GenericRepository" /> <alias alias="DbContextRepository" type="GenericRepository.DbContextRepository`1, GenericRepository" /> <alias alias="User" type="MvcWithUnityTest.Models.User, MvcWithUnityTest.Models" /> <alias alias="DbEntities" type="MvcWithUnityTest.Models.DbEntities, MvcWithUnityTest.Models" /> <container name="default"> <register type="IRepository[User]" mapTo="DbContextRepository[User]"> <lifetime type="HierarchicalLifetimeManager" /> <constructor> <param name="context" dependencyType="DbEntities"> </param> </constructor> </register> </container> </unity> </configuration> 然后在 Global.asax.cs 里调用 Bootstrapper.Initialise(); 即可。另外,需要注意的是<register type="IRepository[User]" mapTo="DbContextRepository[User]"> 里加上了<lifetime type="HierarchicalLifetimeManager" />
这样在 Controller 生命周期结束时才会调用 Dispose。(待展开)
再来看看 Controller 的实现:
根据上面的配置文件:<register type="IRepository[User]" mapTo="DbContextRepository[User]">
[Dependency] 标识的 UserRepository 会在 Controller 请求时被注入实例。
利用了 IoC 整个系统结构,好比如下图: