在asp.net mvc4控制器中使用Autofac來解析依賴

原文地址:http://www.remondo.net/using-autofac-resolve-dependencies-mvc-controller/

前言:特翻譯此文,爲親近中文的朋友。


Breaking dependencies in our code is all about enhanced maintainability, flexibility and testability. In the previous post I created some nasty dependencies in my MVC application. The HomeController instantiates a repository. This repository calls upon an external system for data in turn. In this case Windows Azure Table Storage. Unit testing becomes cumbersome (even unreliable) with a dependency on any external system holding state. As Jeremy Miller wrote a nice blogpost on the Qualities of a Good Unit Test in which he says:

我們的代碼中打破(抽離)依賴無非是爲了增強可維護性、可測試性和靈活性。在前面的帖子我在MVC應用程序創建了一些嚴重的依賴性。HomeController實例化一個存儲庫。這個存儲庫調用外部系統(來獲取)數據。其情形是位於Windows Azure的存儲表。過於依賴於外部系統,單元測試將變得冗長繁瑣(甚至不可靠)。Jeremy Miller 關於一個好的單元測試的質量寫了一篇不錯的博客中,他說:

“[..] put evil stateful things behind nice, deterministic mocks or stubs.”


But before we can do that, we need to refactor out all these dependencies first. We do this by placing small interfaces between dependent objects. Then we configure an IoC container to take care of injecting the proper dependencies whenever needed. In this example we use Autofac, but it could be any of the well established .NET IoC containers out there.

但是在我們能做到這一點之前,我們首先需要重構出所有這些依賴項。爲此,我們在這些依賴對象之間創建了一些小接口。然後我們配置一個IoC容器,負責注入適當的依賴關係以便於在需要的時候用到。在這個示例中,我們使用Autofac,但它也可以是任何其他的.NET的loc容器。

Clearly state dependencies in the constructor

在構造函數中明確聲明依賴

 

In this example we have a simple MVC 4 application listing a couple of Olympic gold medal winners. These are retrieved from a SQL database with the Entity Framework. The data retrieval process is encapsulated in a repository. But by newing up the GoldMedalWinnersRepository in the HomeController we have created a permanent and hidden dependency.

在這個例子中,我們有一個簡單的MVC 4應用程序用來列舉出奧運金牌的得主。這些都從一個SQL數據庫用Entity Framework檢索到的數據。數據檢索過程封裝在一個存儲庫中。但是在HomeController中通過新建一個GoldMedalWinnersRepository實例, 我們已經創建了一個永久的隱藏的依賴。

using System.Web.Mvc;
using TestableMvcApp.DataContext;
using TestableMvcApp.Models;
using TestableMvcApp.Repositories;
 
namespace TestableMvcApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index(GoldMedalWinnersModel model)
        {
            ViewBag.Message = "Gold Medal Winners";
 
            var repository = new GoldMedalWinnersRepository(
                new GoldMedalWinnersContext());
 
            model.Winners = repository.GetAll();
 
            return View(model);
        }
    }
}

Testing this controller forces us to use/test the repository logic as well. Let’s solve this first. To break the dependency with external classes a constructor should ask for those dependencies. So we could move the instantiation of the repository outside the HomeController by passing it in the constructor:

測試該控制器迫使我們使用/測試存儲庫邏輯。讓我們先解決這個依賴。爲了打破與外部類之間的依賴,可以讓構造函數應該要求這些依賴項(作爲參數傳入)。所以我們可以從HomeController中移除存儲庫的實例化,而改用構造函數傳入參數:

using System.Web.Mvc;
using TestableMvcApp.Models;
using TestableMvcApp.Repositories;
 
namespace TestableMvcApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly GoldMedalWinnersRepository _repository;
 
        public HomeController(GoldMedalWinnersRepository repository)
        {
            _repository = repository;
        }
 
        public ActionResult Index(GoldMedalWinnersModel model)
        {
            ViewBag.Message = "Gold Medal Winners";
 
            model.Winners = _repository.GetAll();
 
            return View(model);
        }
    }
}
It is one small step in the right direction.
這是往正確方向的一小步。

Call upon abstractions

調用抽象(接口/抽象類)

Ok, now the constructor clearly states all of its dependencies. But if we test the controller we still have to deal with the repository calling the database. In order to test the controller independently from the repository we need to pass in a fake version of this repository. This way we have total control of the inner workings of the object while testing the real controller. To do this we need to create an abstraction of the repository; here’s the interface:

好的,現在構造函數明確闡述了它的所有依賴項。但是如果我們測試控制器我們仍然需要處理存儲庫調用數據庫的(邏輯)。爲了使控制器的測試獨立於存儲庫(邏輯),我們需要傳遞一個虛擬存儲庫版本。這種方式我們完全控制對象的內部運作,而測試真正的控制器。要做到這一點,我們需要創建一個存儲庫的抽象;下面是接口:

using System.Collections.Generic;
using TestableMvcApp.Pocos;
 
namespace TestableMvcApp.Repositories
{
    public interface IGoldMedalWinnersRepository
    {
        GoldMedalWinner GetById(int id);
        IEnumerable<GoldMedalWinner> GetAll();
        void Add(GoldMedalWinner goldMedalWinner);
    }
}
Let’s change the controllers constructor to ask for an abstract IGoldMedalWinnersRepository interface instead of an actual GoldMedalWinnersRepository object.
讓我們改變控制器的構造函數要求傳入一個抽象IGoldMedalWinnersRepository接口而不是實際GoldMedalWinnersRepository對象。
using System.Web.Mvc;
using TestableMvcApp.Models;
using TestableMvcApp.Repositories;
 
namespace TestableMvcApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly IGoldMedalWinnersRepository _repository;
 
        public HomeController(IGoldMedalWinnersRepository repository)
        {
            _repository = repository;
        }
 
        public ActionResult Index(GoldMedalWinnersModel model)
        {
            ViewBag.Message = "Gold Medal Winners";
 
            model.Winners = _repository.GetAll();
 
            return View(model);
        }
    }
}

As we will see a bit later, the constructor of the repository also asks for a dependency. It needs a Entity Framework DbContext to function. So we need to create an abstraction for that as well; IGoldMedalWinnersContext.
稍後我們將看到,存儲庫的構造函數也有一個依賴項。它需要一個Entity Framework DbContext來正常運作。所以我們需要爲此創建一個抽象(接口);IGoldMedalWinnersContext:
using System.Data.Entity;
using TestableMvcApp.Pocos;
 
namespace TestableMvcApp.DataContext
{
    public interface IGoldMedalWinnersContext
    {
        DbSet<GoldMedalWinner> GoldMedalWinners { get; set; }
        DbSet<Country> Countries { get; set; }
        int SaveChanges();
    }
}
Now let the constructor of our repository ask for an implementation of this interface, and we’re nicely decoupled.
在讓我們的存儲庫(類)的構造函數要求一個這個接口的實現(作爲傳入闡述),這樣我們就成功的解耦了。
using System.Collections.Generic;
using System.Linq;
using TestableMvcApp.DataContext;
using TestableMvcApp.Pocos;
 
namespace TestableMvcApp.Repositories
{
    public class GoldMedalWinnersRepository : IGoldMedalWinnersRepository
    {
        private readonly IGoldMedalWinnersContext _goldContext;
 
        public GoldMedalWinnersRepository(IGoldMedalWinnersContext goldContext)
        {
            _goldContext = goldContext;
        }
 
        #region IGoldMedalWinnersRepository Members
 
        public GoldMedalWinner GetById(int id)
        {
            return _goldContext.GoldMedalWinners.Find(id);
        }
 
        public IEnumerable<GoldMedalWinner> GetAll()
        {
            return _goldContext.GoldMedalWinners.ToList();
        }
 
        public void Add(GoldMedalWinner goldMedalWinner)
        {
            _goldContext.GoldMedalWinners.Add(goldMedalWinner);
            _goldContext.SaveChanges();
        }
 
        #endregion
    }
}
At this point we can pass any repository that implements our interface. So it must be quite easy to fake, stub or mock the repository and dbcontext within our unit tests.
這時我們可以傳入實現了接口的任何存儲庫類。在我們的單元測試中,很容易僞造,複製或模擬存儲庫和dbcontext(來做一個僞庫以便於排除數據庫的因素)。

Using an IoC container to resolve dependencies

使用IoC容易來解析依賴

This is nice and all, but where do we instantiate our repository? We can use an IoC container for this purpose. This little piece of wizardry can be configured to provide our controller with the proper repository whenever needed. In this example we use Autofac. We can easily install the Autofac ASP.NET MVC3 Integration package from Nuget. It also works with MVC4 and takes care of the installation of all core dependencies needed by our MVC application.
這是很好的,但是我們在哪裏實例化存儲庫?爲此,我們可以使用一個IoC容器。這個神奇的東西可以通過配置,在必要時,可爲控制器提供合適的存儲庫庫時。在這個示例中,我們使用Autofac。我們可以很容易的從Nuget安裝 Autofac ASP.NET MVC3 Integration package。它還適用於MVC4並負責安裝所需的所有的我們的MVC應用程序需要的核心依賴。

Once installed we can configure Autofac to resolve all dependencies as needed. For this we create a configuration class in the App_start folder of our MVC project. It has a static method called RegisterDependencies where we can wire up Autofac.
一旦安裝,我們可以根據需要配置Autofac來解析所有依賴項。爲了演示,我們在MVC項目App_start文件夾下創建一個配置類。它有一個靜態的名爲RegisterDependencies Autofac的方法,在這裏我們可以啓用Autofac。
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using TestableMvcApp.DataContext;
using TestableMvcApp.Repositories;
 
namespace TestableMvcApp.App_Start
{
    public class IocConfig
    {
        public static void RegisterDependencies()
        {
            var builder = new ContainerBuilder();
            builder.RegisterControllers(typeof (MvcApplication).Assembly);
 
            builder.RegisterType<GoldMedalWinnersRepository>()
                .As<IGoldMedalWinnersRepository>()
                .InstancePerHttpRequest();
 
            builder.RegisterType<GoldMedalWinnersContext>()
                .As<IGoldMedalWinnersContext>()
                .InstancePerHttpRequest();
 
            IContainer container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }
}
We new up an Autofac ContainerBuilder and call RegisterControllers for the MVC assembly. This method registers all controllers in the application with the AutofacDependencyResolver. Whenever MVC asks for a controller this resolver return the proper controller.
我們新建一個Autofac ContainerBuilder,然後調用RegisterControllers來(註冊整個) MVC程序集。這種方法用AutofacDependencyResolver註冊所有在應用程序的控制器。每當MVC要求訪問控制器時,這個解析器就返回適當的控制器。

Then we call RegisterType on GoldMedalWinnersRepository. Here we configure that whenever we come across a type of IGoldMedalWinnersRepository, Autofac must return an instantiated GoldMedalWinnersRepository. We set the lifetime scope to InstancePerHttpRequest. We register the GoldMedalWinnersContext the same way. We build the container and set the MVC DependencyResolver to an AutofacDependencyResolver for our container.
然後我們調用RegisterType註冊 GoldMedalWinnersRepository。在這裏我們配置成:每當我們需要一個IGoldMedalWinnersRepository, Autofac必須返回一個GoldMedalWinnersRepository的實例。我們將生命週期設置爲了InstancePerHttpRequest。我們同樣的方式註冊了GoldMedalWinnersContext。我們構建的容器,併爲我們的容器的AutofacDependencyResolver設置瞭解析器MVC DependencyResolver 

In the Global.asax of our MVC application, we call the IoC configuration class RegisterDependencies method before all other MVC registrations and we’re ready to roll.
MVC應用程序中的全局的.asax文件中,在所有其他MVC註冊之前,我們調用了IoC配置類的RegisterDependencies方法,這樣就可以了(我們準備好了)。
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using TestableMvcApp.App_Start;
 
namespace TestableMvcApp
{
    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            IocConfig.RegisterDependencies();
 
            AreaRegistration.RegisterAllAreas();
 
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

And that’s all (for now). When we fire up our application, Autofac will do the magic and provide our controller with the proper repository instantiated with the proper dbcontext. If we write unit tests, however, Autofac will not provide our controller with the repository, so we can inject some fake, stub or mock ourselves. And that’s just what we need.
這就是全部(目前爲止)。當我們啓動應用程序,Autofac會做一個事情然後爲控制器提供適當的實例化了帶有適當的dbcontext存儲庫。如果我們編寫單元測試,然而,Autofac不會爲我們的控制器提供存儲庫,因此我們可以注入一些假的,副本或仿製版本。這正是我們需要的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章