LinFu IoC容器以其简洁易用而著称,几乎不需要任何配置,同时具有很高的灵活性和可扩展性。本文将探讨如何将LinFu IoC容器应用于ASP.NET MVC框架的整个MVC堆栈中,从而更接近基于接口的编程,并深入探索依赖注入的奇妙世界,例如用于模型或控制器的创建。
假设有如下的“领域模型”(从LinFu示例中复制并稍作修改):
public interface IVehicle
{
IEngine Engine { get; }
IPerson Driver { get; }
}
public interface IEngine
{
string SerialNumber { get; }
}
public interface IPerson
{
string Name { get; }
int Age { get; }
}
使用LinFu的ServiceContainer,创建汽车及其相关引擎和驾驶员的代码如下:
[Test]
public void CanCreateCarWithDependenciesFromContainer()
{
var car = container.GetService();
Assert.Multiple(() =>
{
Assert.IsNotNull(car);
Assert.IsNotNull(car.Engine);
Assert.IsNotNull(car.Driver);
});
}
这是因为LinFu容器看到实现ICar的类有一个构造函数,该构造函数接受IEngine和IPerson实例作为参数。到目前为止,这是经典的构造函数注入...
现在让更具体地讨论MVC相关的内容:创建一个自定义的ControllerFactory类,通过LinFu容器进行控制器创建。在ASP.NET MVC应用程序中,控制器工厂负责根据URL请求创建控制器实例。MVC的默认工厂要求请求的控制器类声明一个无参数构造函数。使用LinFu容器,没有这个限制,但可以使用依赖注入。以下是新工厂类的声明:
public class LinFuControllerFactory : DefaultControllerFactory
{
protected ServiceContainer Container { get; private set; }
public LinFuControllerFactory(ServiceContainer serviceContainer)
{
if (serviceContainer == null)
{
throw new ArgumentNullException("serviceContainer");
}
this.Container = serviceContainer;
}
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404,
string.Format("The controller for path '{0}' could not be found or it does not implement IController.",
RequestContext.HttpContext.Request.Path));
}
return (IController)controllerType.AutoCreateFrom(this.Container);
}
}
如所见,通过LinFu容器创建所需类型的控制器本质上是一行代码,使用AutoCreateFrom()方法。如果控制器有非默认构造函数,那么容器也会处理控制器的依赖项。不需要额外的配置或其他东西 - 只要已经向LinFu容器声明了所需的程序集(包含控制器)...
控制器创建是使用IoC容器的第一个有用领域,第二个是模型绑定。特别喜欢这种技术,因为它允许在视图和控制器中使用接口而不是具体类。
在视图的.aspx文件中,可以这样写:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<My.Model.IVehicle>" %>
相关的控制器操作可能被声明如下:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(IVehicle car)
{
...
}
要使这成为可能,使用一个自定义模型绑定器,将表单的值转换为ICar实例:
public class VehicleModelBinder : TypedLinFuModelBinder<IVehicle>
{
public VehicleModelBinder(ServiceContainer serviceContainer) : base(serviceContainer)
{
}
protected internal override IVehicle CreateModelFromFormValues(NameValueCollection formValues)
{
var engine = this.GetService<IEngine>(formValues["Engine.SerialNumber"]);
var driver = this.GetService<IPerson>(formValues["Driver.Name"], Convert.ToInt32(formValues["Driver.Age"]));
return this.GetService<IVehicle>(engine, driver);
}
}
泛型TypedLinFuModelBinder基类是用服务容器实例初始化的,并声明了一些便利方法,特别是CreateModelFromFormValues()方法,它用于处理常见的“取表单的值并从中创建新对象”的场景。它包含在示例解决方案中,可以从这里下载。(内部声明是为了使测试更容易...)
首先,将LinFu ServiceContainer放在一个单例外观后面是有帮助的,这样就可以在应用程序的整个过程中有一个唯一的访问点。通常有一个额外的程序集用于这些事情(IoC,Logging,自定义属性等;命名为Infrastructure或其他类似名称)。持有ServiceContainer实例的单例可能看起来像这样:
public static class DI
{
public static ServiceContainer ServiceContainer { get; private set; }
static DI()
{
string directory = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
if (string.IsNullOrEmpty(directory))
{
directory = AppDomain.CurrentDomain.BaseDirectory;
}
ServiceContainer = new ServiceContainer();
ServiceContainer.LoadFrom(directory, "My.DomainAssembly.dll");
ServiceContainer.LoadFrom(directory, "My.AspNetMvcAssembly.dll");
}
}
ControllerBuilder.Current.SetControllerFactory(new LinFuControllerFactory(DI.ServiceContainer));
ModelBinders.Binders[typeof(IVehicle)] = new VehicleModelBinder(DI.ServiceContainer);