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

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

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

我们的代码中打破(抽离)依赖无非是为了增强可维护性、可测试性和灵活性。在前面的帖子我在MVC应用程序创建了一些严重的依赖性。HomeController实例化一个存储库。这个存储库调用外部系统(来获取)数据。其情形是位于Windows Azure的存储表。过于依赖于外部系统,单元测试将变得冗长繁琐(甚至不可靠)。Jeremy Miller 关于一个好的单元测试的质量写了一篇不错的博客中,他说:

但是在我们能做到这一点之前,我们首先需要重构出所有这些依赖项。为此,我们在这些依赖对象之间创建了一些小接口。然后我们配置一个IoC容器,负责注入适当的依赖关系以便于在需要的时候用到。在这个示例中,我们使用Autofac,但它也可以是任何其他的.NET的loc容器。

在构造函数中明确声明依赖

在这个例子中,我们有一个简单的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);
        }
    }
}

测试该控制器迫使我们使用/测试存储库逻辑。让我们先解决这个依赖。为了打破与外部类之间的依赖,可以让构造函数应该要求这些依赖项(作为参数传入)。所以我们可以从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);
        }
    }
}
这是往正确方向的一小步。

调用抽象(接口/抽象类)

好的,现在构造函数明确阐述了它的所有依赖项。但是如果我们测试控制器我们仍然需要处理存储库调用数据库的(逻辑)。为了使控制器的测试独立于存储库(逻辑),我们需要传递一个虚拟存储库版本。这种方式我们完全控制对象的内部运作,而测试真正的控制器。要做到这一点,我们需要创建一个存储库的抽象;下面是接口:

using System.Collections.Generic;
using TestableMvcApp.Pocos;
 
namespace TestableMvcApp.Repositories
{
    public interface IGoldMedalWinnersRepository
    {
        GoldMedalWinner GetById(int id);
        IEnumerable<GoldMedalWinner> GetAll();
        void Add(GoldMedalWinner goldMedalWinner);
    }
}
让我们改变控制器的构造函数要求传入一个抽象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);
        }
    }
}
 
稍后我们将看到,存储库的构造函数也有一个依赖项。它需要一个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();
    }
}
在让我们的存储库(类)的构造函数要求一个这个接口的实现(作为传入阐述),这样我们就成功的解耦了。
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
    }
这时我们可以传入实现了接口的任何存储库类。在我们的单元测试中,很容易伪造,复制或模拟存储库和dbcontext(来做一个伪库以便于排除数据库的因素)。

使用IoC容易来解析依赖

这是很好的,但是我们在哪里实例化存储库?为此,我们可以使用一个IoC容器。这个神奇的东西可以通过配置,在必要时,可为控制器提供合适的存储库库时。在这个示例中,我们使用Autofac。我们可以很容易的从Nuget安装 Autofac ASP.NET MVC3 Integration package。它还适用于MVC4并负责安装所需的所有的我们的MVC应用程序需要的核心依赖。
 
一旦安装,我们可以根据需要配置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));
        }
    }
}
我们新建一个Autofac ContainerBuilder,然后调用RegisterControllers来(注册整个) MVC程序集。这种方法用AutofacDependencyResolver注册所有在应用程序的控制器。每当MVC要求访问控制器时,这个解析器就返回适当的控制器。
 
然后我们调用RegisterType注册 GoldMedalWinnersRepository。在这里我们配置成:每当我们需要一个IGoldMedalWinnersRepository, Autofac必须返回一个GoldMedalWinnersRepository的实例。我们将生命周期设置为了InstancePerHttpRequest。我们同样的方式注册了GoldMedalWinnersContext。我们构建的容器,并为我们的容器的AutofacDependencyResolver设置了解析器MVC DependencyResolver 。
 
在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);
        }
    }
}
这就是全部(目前为止)。当我们启动应用程序,Autofac会做一个事情然后为控制器提供适当的实例化了带有适当的dbcontext的存储库。如果我们编写单元测试,然而,Autofac不会为我们的控制器提供存储库,因此我们可以注入一些假的,副本或仿制版本。这正是我们需要的。
上一篇:Vue:过滤器filter的使用方式


下一篇:《思科UCS服务器统一计算》一2.5 芯片组虚拟化支持