在asp.net mvc4控制器中使用Autofac来解析依赖

来源:互联网 发布:nba2konline软件刷球星 编辑:程序博客网 时间:2024/05/16 09:03

原文地址: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不会为我们的控制器提供存储库,因此我们可以注入一些假的,副本或仿制版本。这正是我们需要的。

0 0
原创粉丝点击