如何在ASP.NET MVC 2+中使用带有模型绑定器的DI/IoC容器?

And*_*nea 8 asp.net-mvc dependency-injection inversion-of-control model-binding custom-model-binder

假设我有一个User实体,我想在构造函数中将它的CreationTime属性设置为DateTime.Now.但作为单元测试采用者,我不想直接访问DateTime.Now,而是使用ITimeProvider:

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; } }
}
Run Code Online (Sandbox Code Playgroud)

我在我的ASP.NET MVC 2.0应用程序中使用NInject 2.我有一个UserController和两个Create方法(一个用于GET,一个用于POST).用于GET的那个是直接的,但是用于POST的那个不是那么直接而不是那么向前:P因为我需要弄乱模型绑定器告诉它获得ITimeProvider的实现的引用以便能够构造用户实例.

public class UserController : Controller {

    [HttpGet]
    public ViewResult Create() {
         return View();
    }

    [HttpPost]
    public ActionResult Create(User user) {

         // ...

    }
}
Run Code Online (Sandbox Code Playgroud)

我还希望能够保留默认模型绑定器的所有功能.

有机会解决这个简单/优雅/等?:d

Mar*_*ann 20

几点意见:

不要仅仅为了在构造函数中查询它们而注入依赖项

没有理由将ITimeProvider注入用户只是为了Now立即调用.只需直接注入创建时间:

public User(DateTime creationTime)
{
     this.CreationTime = creationTime;
}
Run Code Online (Sandbox Code Playgroud)

与DI相关的一个非常好的经验法则是构造函数不应该执行任何逻辑.

不要将DI与ModelBinders一起使用

ASP.NET MVC ModelBinder是一个非常糟糕的做DI的地方,特别是因为你不能使用Constructor Injection.唯一剩下的选项是静态服务定位器反模式.

ModelBinder将HTTP GET和POST信息转换为强类型对象,但从概念上讲,这些类型不是域对象,而是类似于数据传输对象.

ASP.NET MVC更好的解决方案是完全放弃自定义ModelBinder,而是明确地接受从HTTP连接收到的内容不是您的完整域对象.

您可以使用简单的查找或映射器来检索控制器中的域对象:

public ActionResult Create(UserPostModel userPost)
{
    User u = this.userRepository.Lookup(userPost);
    // ...
}
Run Code Online (Sandbox Code Playgroud)

this.userRepository注入依赖项在哪里.

  • 关键是它没关系.您没有收到实体,您收到HTTP POST(或GET).拥抱协议.还有许多其他方法可以避免代码重复. (2认同)
  • 由于asp.net mvc 3支持imodelbinderprovider,它表明他们正在思考正确的路线,因此我不同意你的意见,但了解你的来源.另一种选择可能是创建一个处理重复逻辑的基本控制器...... (2认同)
  • 在协议级别上,发布了一些非常具体的内容(字符串).它甚至不是一个对象,所以它肯定不是一个界面.将其建模为接口很难说是接受协议. (2认同)

Dar*_*rov 5

怎么样而不是ITimeProvider尝试这个:

public class User 
{
    public Func<DateTime> DateTimeProvider = () => DateTime.Now;

    public User() 
    {
        this.CreationTime = DateTimeProvider();
    }
}
Run Code Online (Sandbox Code Playgroud)

在你的单元测试中:

var user = new User();
user.DateTimeProvider = () => new DateTime(2010, 5, 24);
Run Code Online (Sandbox Code Playgroud)

我知道这不是很优雅,但不是弄乱模型绑定器,这可能是一个解决方案.如果这不是一个好的解决方案,您可以实现自定义模型绑定器并覆盖CreateModel方法,您将在模型的构造函数中注入依赖项.

  • 然后覆盖`DefaultModelBinder`是您必须采取的路线. (2认同)