在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); } } }
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 }
使用IoC容易来解析依赖
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)); } } }
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); } } }