ASP.NET MVC 3 & Unity.MVC3

来源:互联网 发布:sql before触发器 编辑:程序博客网 时间:2024/05/22 17:38

最近学习了下 ASP.NET MVC,比较之前的 WebForm 没有了 IsPostBack 的判断,事件处理也被 Action 取代。MVC 中 WebForm中大量的事件处理中UI绑定,混杂的 js 注入, style 修改没有了;服务端控件不用了,结局是 View 被释放了,Controller可以被单元测试了,拿着 ViewModel 可以快速替换 View。(说句实话要不是有 Razor 这样的页面引擎加上 VS IDE 的强力智能感知,ASP.NET MVC 和 JSP 没有区别,说不定还会有人把 strust 标签,spring 标签拿来在 .NET 上封装一遍)

再加上现在的 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 的代码】
[csharp]
public class HomeController : Controller { 
 
    ContactEntities _db = new ContactEntities(); 
 
    public ActionResult Index() { 
        var dn = _db.Contacts; 
        return View(dn); 
    } 

【使用 Repository Pattern 的代码】
[csharp]
namespace MvcContacts.Models { 
    public class EF_ContactRepository : MvcContacts.Models.IContactRepository { 
 
        private ContactEntities _db = new ContactEntities(); 
 
        public Contact GetContactByID(int id) { 
            return _db.Contacts.FirstOrDefault(d => d.Id == id); 
        } 
... 
测试可以用个 Mock Repository,_db 数据从哪来就自由了...
[csharp]
namespace MvcContacts.Tests.Models { 
    class InMemoryContactRepository : MvcContacts.Models.IContactRepository { 
        private List<Contact> _db = new List<Contact>(); 
... 
而 Controller 变成这样: www.2cto.com
[csharp]
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>
[csharp]
namespace MvcWithUnityTest.Models 

    public class DbEntities : DbContext 
    { 
        public DbSet<User> Users { get; set; } 
    } 

2. GenericRepository
主要针对 EF(ObjectContext) 和 EF CodeFirst(DbContext) 抽出接口 IRepository
\

[csharp]
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  实例
[csharp]
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 文件

\

 

[csharp] print?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; 
    } 

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;
    }
}
这里我把依赖关系都放到配置文件里了:

[html] print?<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> 
<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 请求时被注入实例。


[csharp] print?public class HomeController : Controller 
 { 
     [Dependency] 
     public IRepository<User> UserRepository { get; set; } 
 
     public ActionResult Index() 
     { 
         ViewBag.Message = "ASP.NET MVC3 With Unity.MVC3!"; 
         var users = UserRepository.GetAll(); 
         return View(users); 
     } 
 
     public ActionResult About() 
     { 
         return View(); 
     } 
 } 
public class HomeController : Controller
 {
     [Dependency]
     public IRepository<User> UserRepository { get; set; }

     public ActionResult Index()
     {
         ViewBag.Message = "ASP.NET MVC3 With Unity.MVC3!";
         var users = UserRepository.GetAll();
         return View(users);
     }

     public ActionResult About()
     {
         return View();
     }
 }
运行:
\

利用了 IoC 整个系统结构,好比如下图:


\