在 ASP.NET MVC 中使用 Autofac 实现 IoC
在 MVC 应用程序中使用 Autofac IoC 容器来解决依赖关系。包括的场景有:控制器、自定义过滤器和视图依赖注入
引言
本文将演示如何在 MVC 应用程序中使用 Autofac IoC 容器。本文将涵盖 MVC 中的各种依赖注入场景,即:控制器、自定义过滤器和视图依赖注入。
解决方案设置
在大型应用程序开发中,通常会有包含不同架构部分的多个项目,例如数据、服务、UI 等。这就是为什么本文创建了一个包含多个项目的解决方案,以便尽可能接近实际应用程序开发。图 1 显示了解决方案结构。
图 1:解决方案设置
MVCWithAutofac.Core 包含在 MVCWithAutofac.Data (数据) 项目中使用的模型和各种接口。数据项目包含 EF Code First 上下文和 MVCWithAutofac.Core 中定义的存储库接口的实现。这两个项目的实现细节在我题为《带有 Unit of Work、IoC 和单元测试的存储库》的文章中进行了详细解释。我将指出本文中添加的这两个项目中的任何新文件/实现。
本文的重点将放在 MVCWithAutofac.Web 的实现上。在这里,我们将 Autofac IoC 付诸实践,以实现您可能希望在 MVC 应用程序中使用的各种依赖注入 (DI) 场景。
使用 Autofac 模块
建议对 IoC 容器的构建进行结构化,以便易于维护。一种方法是使用 Autofac 模块。Autofac 模块允许您将 IoC 不同部分的构建委托给每个具有您可能希望使用的依赖关系的项目。这样,您就不必将数十个依赖关系堆在一个地方,这使得长期维护变得更加困难。
我已经在 DataModule
中使用了此功能。DataModule 负责注册在数据项目中定义并将用于 MVCWithAutofac.Web 的依赖关系。清单 1 显示了 DataModule 的实现。
using Autofac;
using MVCWithAutofac.Core.Interfaces;
namespace MVCWithAutofac.Data
{
public class DataModule : Module
{
private string connStr;
public DataModule(string connString)
{
this.connStr = connString;
}
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new EFContext(this.connStr)).
As<IDbContext>().InstancePerRequest();
builder.RegisterType<SqlRepository>().As<IRepository>().InstancePerRequest();
builder.RegisterType<TeamRepository>().As<ITeamRepository>().InstancePerRequest();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
base.Load(builder);
}
}
}
清单 1:DataModule 实现
该类继承自 Module 并重写了 Load 方法。Load 方法接受一个 ContainerBuilder 实例。
请注意,构造函数接受一个连接字符串名称。这用于创建 EF 上下文。这非常方便,因为您可以从构建 IoC 容器的地方传递连接字符串(我们稍后将看到)。在我们的例子中,这将是 Web 项目。传递连接字符串名称的用处在于,您可以根据所处的环境使用各种连接字符串。此外,您还消除了数据项目获取连接字符串的责任。
Load 方法使用 ContainerBuilder 注册各种数据依赖关系,例如 EF 上下文和存储库。
构建 IoC 容器
连接我们 Autofac IoC 容器构建的理想位置是 Global.asax 文件中的 Application_Start 方法。清单 2 显示了 Application_Start 方法的实现。
protected void Application_Start()
{
AutofacConfig.ConfigureContainer();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
清单 2:Application_Start 实现
除了这行:AutofacConfig.ConfigureContainer(),此方法的实现对 MVC 开发人员来说非常熟悉。这里将构建我们的 IoC。清单 3 显示了 AutofacConfig 类的实现。
using Autofac;
using Autofac.Integration.Mvc;
using System.Web.Mvc;
using MVCWithAutofac.Data;
namespace MVCWithAutofac.Web
{
public class AutofacConfig
{
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
// Register dependencies in controllers
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// Register dependencies in filter attributes
builder.RegisterFilterProvider();
// Register dependencies in custom views
builder.RegisterSource(new ViewRegistrationSource());
// Register our Data dependencies
builder.RegisterModule(new DataModule("MVCWithAutofacDB"));
var container = builder.Build();
// Set MVC DI resolver to use our Autofac container
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
}
清单 3:AutofacConfig 实现
注意 using 语句。我们正在使用 Autofac 以及 Autofac.Integration.Mvc。后者是一个单独的包,您需要从 nuget 包管理器中单独下载。
顾名思义,Autofac.Integration.Mvc 包含将 Autofac 容器集成到 MVC 应用程序所需的逻辑。RegisterControllers、RegisterFilterProvider 和 ViewRegistrationSource 的实现都在 Autofac.Integration.Mvc 中。
控制器 DI
我正在使用 Autofac 来解决 TeamController 中的依赖关系。清单 4 显示了 TeamController 实现的一部分。
public class TeamController : Controller
{
private IRepository repo;
public TeamController(IRepository repo)
{
this.repo = repo;
}
[HttpGet]
public ActionResult Index()
{
var teams = repo.GetAll<Team>().ToList();
return View(teams);
}
}
清单 4:TeamController 中的 DI
这种类型的注入称为构造函数注入。我正在运行时解析 IRepository
实现。Index 操作正在使用 repo
字段来检索团队。
请注意,我使用的是 IRepository 接口而不是具体实现。这在单元测试中很有用。它还有助于切换实现或为同一个接口使用多个实现并根据上下文解析特定的实现。
过滤器属性 DI
我创建了一个自定义操作过滤器属性来演示过滤器属性中的 DI。LogActionFilter
是一个可以应用于操作的过滤器属性。清单 5 显示了 LogActionFilter 的实现。
using MVCWithAutofac.Core.Interfaces;
using MVCWithAutofac.Core.Model;
using System.Web.Mvc;
namespace MVCWithAutofac.Web.Filters
{
public class LogActionFilter : ActionFilterAttribute
{
public IRepository repo { set; get; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
repo.Insert<Log>(new Log
{
Action = string.Format("Controller: {0} , Action: {1}",
filterContext.ActionDescriptor.ActionName,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName),
Name = "Log entry"
});
repo.SaveChanges();
}
}
}
清单 5:LogActionFilter 实现
LogActionFilter 继承自 ActionFilterAttribute
并重写了 OnActionExecuted 方法。请注意如何使用属性解析存储库依赖关系。这称为属性注入。
OnActionExecuted 的实现正在使用存储库在 Log 表中创建日志条目。日志条目详细说明了操作和控制器名称。Log
实体在数据项目中定义。清单 6 显示了日志实体。
namespace MVCWithAutofac.Core.Model
{
public class Log : BaseModel<int>
{
public string Action { get; set; }
}
}
清单 6:Log 实体
我正在插入团队操作上使用此过滤器属性,如清单 7 所示。此操作执行后,我们的操作过滤器将启动,向日志表添加一个条目。
[HttpPost]
[LogActionFilter]
public ActionResult InsertTeam(Team team)
{
if (ModelState.IsValid)
{
repo.Insert<Team>(team);
repo.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
清单 7:应用 LogActionFilter 属性
自定义视图 DI
Razor 视图引擎允许您在 MVC 应用程序中使用自定义基视图作为视图。您这样做的原因因您的需求而异。这是一篇很好的文章,它演示了如何以及为什么要这样做。不过,我们这里的重点是如何在自定义视图基类中实现 DI。
CustomBasePage
继承自 WebViewPage
并定义了两个属性,即:teamRepo
和 repo
。这两个属性将从我们的 Autofac IoC 容器在运行时解析。清单 8 显示了 CustomBasePage 的实现。
using MVCWithAutofac.Core.Interfaces;
using System.Web.Mvc;
namespace MVCWithAutofac.Web.BasePages
{
public class CustomBasePage : WebViewPage
{
public ITeamRepository teamRepo { get; set; }
public IRepository repo { get; set; }
public override void Execute() { }
}
}
清单 8:CustomBasePage 实现
我在我的 TeamDetails.cshtml 视图中使用了这个自定义基视图来显示给定团队中的团队成员。清单 9 显示了 TeamDetails.cshtml 的实现。
@inherits MVCWithAutofac.Web.BasePages.CustomBasePage
@{
ViewBag.Title = "Team Details";
var team = repo.GetById<MVCWithAutofac.Core.Model.Team>(ViewBag.TeamId);
}
<h2>Team Details: @team.Name</h2>
<ul>
@foreach (var teamMember in teamRepo.GetUsersInTeam(team.Id))
{
<li style="margin-left: 20px;">@teamMember.Name</li>
}
</ul>
清单 9:TeamDetails.cshtml 实现
请注意,此视图如何使用 @inherits Razor 语法从 CustomBasePage 继承。此视图获取通过 ViewBag 集合传递的带有 id 的团队,然后使用 CustomBasePage 上的 teamRepo 属性获取该团队的团队成员。此视图的操作如清单 10 所示。
public ActionResult TeamDetails(int id) { ViewBag.TeamId = id; return View(); }
清单 10:TeamDetails 操作
如您所见,此操作只获取 teamId 并将其存储在 ViewBag 集合中,然后返回 View (TeamDetails)。
为完整起见,这是 GetById 泛型方法的实现,这是数据项目中的新方法。
public TEntity GetById<TEntity>(int id) where TEntity : BaseModel<int>
{
return (from item in GetAll<TEntity>()
where item.Id == id
select item).SingleOrDefault();
}
清单 11:GetById 方法
附注
本文的目的是解释如何在 MVC 应用程序中实现 DI,包括控制器、过滤器属性和自定义视图。因此,并未遵循所有最佳实践,因为这并非本文的目的。
例如,日志过滤器属性应该有一个记录器依赖项,而不指定该记录器将把信息记录到哪里。记录器反过来可以使用 EF 上下文依赖项或调用服务来记录操作。另一个例子是从 TeamDetails.cshtml 中的视图调用存储库。显然,这不是您希望在生产代码中做的事情,但目的是解释如何在自定义视图中使用 DI。
结论
在本文中,我解释了如何利用 Autofac 为您的 MVC 应用程序创建 IoC 容器。应用 DI 的场景包括控制器、过滤器属性和自定义视图。我解释了如何在 Application_Start 方法中构建您的 IoC 容器,以及如何通过利用 Autofac 的 Module 类来减少创建 IoC 容器的开销。