【ASP.NET】ASP.NET MVC 3 & Unity.MVC3

最近學習了下 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 的代碼】

public class HomeController : Controller {

    ContactEntities _db = new ContactEntities();

    public ActionResult Index() {
        var dn = _db.Contacts;
        return View(dn);
    }
}

【使用 Repository Pattern 的代碼】

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 數據從哪來就自由了...
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> 

namespace MvcWithUnityTest.Models
{
    public class DbEntities : DbContext
    {
        public DbSet<User> Users { get; set; }
    }
}

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 請求時被注入實例。

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 整個系統結構,好比如下圖:




發佈了143 篇原創文章 · 獲贊 10 · 訪問量 127萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章