ASP.NET MVC与LinFu IoC容器的集成

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实例作为参数。到目前为止,这是经典的构造函数注入...

使用IoC容器创建控制器

现在让更具体地讨论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容器进行模型绑定

控制器创建是使用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);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485