How to use a DI / IoC container with the model binder in ASP.NET MVC 2+?
假设我有一个 User 实体,我想在构造函数中将它的 CreationTime 属性设置为 DateTime.Now。但是作为单元测试采用者,我不想直接访问 DateTime.Now,而是使用 ITimeProvider :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class User { public User(ITimeProvider timeProvider) { // ... this.CreationTime = timeProvider.Now; } // ..... } public interface ITimeProvider { public DateTime Now { get; } } public class TimeProvider : ITimeProvider { public DateTime Now { get { return DateTime.Now; } } } |
我在我的 ASP.NET MVC 2.0 应用程序中使用 NInject 2。我有一个 UserController 和两个 Create 方法(一个用于 GET,一个用于 POST)。 GET 的一个是直截了当的,但 POST 的那个不是那么直,也不是那么向前:P 因为我需要弄乱模型绑定器来告诉它获取 ITimeProvider 实现的参考,以便能够构造一个用户实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class UserController : Controller { [HttpGet] public ViewResult Create() { return View(); } [HttpPost] public ActionResult Create(User user) { // ... } } |
我还希望能够保留默认模型绑定器的所有功能。
有机会解决这个简单/优雅/等问题吗? :D
一些观察:
不要仅仅为了在构造函数中查询而注入依赖
没有理由为了立即调用
1 2 3 4 | public User(DateTime creationTime) { this.CreationTime = creationTime; } |
与 DI 相关的一个非常好的经验法则是构造函数不应执行任何逻辑。
不要将 DI 与 ModelBinders 一起使用
ASP.NET MVC ModelBinder 是一个非常糟糕的地方来做 DI,特别是因为你不能使用构造函数注入。唯一剩下的选项是静态服务定位器反模式。
ModelBinder 将 HTTP GET 和 POST 信息转换为强类型对象,但从概念上讲,这些类型不是域对象,而是类似于数据传输对象。
ASP.NET MVC 的一个更好的解决方案是完全放弃自定义 ModelBinders,而是明确接受您从 HTTP 连接收到的不是您的完整域对象。
您可以使用简单的查找或映射器来检索控制器中的域对象:
1 2 3 4 5 | public ActionResult Create(UserPostModel userPost) { User u = this.userRepository.Lookup(userPost); // ... } |
其中
不如使用
1 2 3 4 5 6 7 8 9 | public class User { public Func<DateTime> DateTimeProvider = () => DateTime.Now; public User() { this.CreationTime = DateTimeProvider(); } } |
在你的单元测试中:
1 2 | var user = new User(); user.DateTimeProvider = () => new DateTime(2010, 5, 24); |
我知道这不是很优雅,但不是弄乱模型绑定器,这可能是一个解决方案。如果这不是一个好的解决方案,您可以实现自定义模型绑定器并覆盖 CreateModel 方法,在该方法中您将在模型的构造函数中注入依赖项。
另一种选择是创建一个不同的类来表示尚未持久化但根本没有创建日期属性的用户。
即使
毕竟,这可能无关紧要,但创建日期属性是否应该真正代表您构建用户实例的时刻,还是代表用户提交数据的时刻更合适?