我是Repository和DI的新手,正在尝试在我的MVC 5项目中实现。
我实现了构造函数注入(inject),其中在我的 Controller 中有一个类似这样的构造函数:
IBook _ibook;
public Test(IBook ibook)
{
_ibook = ibook;
}
没有任何DI库,它将引发错误:没有空的构造函数。
为了避免这种情况,我添加了一个如下的构造函数:
public Test ():this(new Book())
{
}
由于我是DI的新手,所以我不想冒险使用DI库,因为它以后可能会引发一些我可能无法解决的错误。
我想知道如果我不使用DI库,可能会遇到什么问题。
如果有推荐的话,哪个DI库适合初学者?我看过一些NInject和Unity的视频。
最佳答案
将决定使用某种工具或库的决定推迟到the last responsible moment之前是一个好主意。通过良好的设计,您可以稍后添加一个DI库。这意味着您练习Pure DI。
MVC中首选的拦截点是IControllerFactory
抽象,因为它允许您拦截MVC Controller 的创建,并且这样做避免了您必须实现第二个构造函数(is an anti-pattern)。尽管可以使用IDependencyResolver
,但是使用该抽象方法要方便得多,因为MVC也会调用它来解决您通常不感兴趣的事物。
可以按以下方式实现将用作您的Composition Root的自定义IControllerFactory
:
public sealed class CompositionRoot : DefaultControllerFactory
{
private static string connectionString =
ConfigurationManager.ConnectionStrings["app"].ConnectionString;
private static Func<BooksContext> bookContextProvider = GetCurrentBooksContext;
private static IBookRepository bookRepo = new BookRepository(bookContextProvider);
private static IOrderBookHandler orderBookHandler = new OrderBookHandler(bookRepo);
protected override IController GetControllerInstance(RequestContext _, Type type) {
// Unfortunately, because of MVC's design, controllers are not stateless, and
// you will have to create them per request.
if (type == typeof(OrderBookController))
return new HomeController(orderBookHandler);
if (type == typeof(BooksController))
return new BooksController(bookRepo);
// [other controllers here]
return base.GetControllerInstance(_, type);
}
private static BooksContext GetCurrentBooksContext() {
return GetRequestItem<BooksContext>(() => new BooksContext(connectionString));
}
private static T GetRequestItem<T>(Func<T> valueFactory) where T : class {
var context = HttpContext.Current;
if (context == null) throw new InvalidOperationException("No web request.");
var val = (T)context.Items[typeof(T).Name];
if (val == null) context.Items[typeof(T).Name] = val = valueFactory();
return val;
}
}
您可以将新的 Controller 工厂挂接到MVC中,如下所示:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start() {
ControllerBuilder.Current.SetControllerFactory(new CompositionRoot());
// the usual stuff here
}
}
当您练习Pure DI时,通常会看到您的“合成根”包含一大串
if
语句。应用程序中每个根对象一个语句。从Pure DI开始具有一些有趣的优势。最突出的一个是编译时支持,因为这在开始使用DI库时会立即丢失。一些库通过允许您以编译器可以执行的方式来验证配置,尝试将这种损失降到最低。但是此验证是在运行时完成的,反馈周期永远不会比编译器可以给您的周期短。
请不要尝试通过实现某种允许使用反射创建类型的机制来简化开发,因为这样做是在构建自己的DI库。这有很多缺点,例如您会失去编译时间支持,而又无法获得现有DI库可以为您带来的任何好处。
当您的合成根目录开始变得难以维护时,您应该考虑从Pure DI切换到DI库。
请注意,在我的示例“合成根”中,将所有应用程序组件( Controller 除外)都定义为 singleton 。单例意味着该应用程序每个组件只有一个实例。这种设计需要您的组件是无状态的(因此是线程安全的),任何具有状态的东西(例如
BooksContext
)should not be injected through the constructor。在示例中,我使用了Func<T>
作为BooksContext
的提供者,该请求是按请求存储的。使对象图成为单例has many interesting advantages。例如,它可以防止您犯常见的配置错误(例如Captive Dependencies),并迫使您进入更坚固的设计。此外,某些DI库非常慢,将所有内容设置为单例可能会在以后切换到DI库时避免性能问题。另一方面,该设计的缺点是团队中的每个人都应该了解所有组件都必须是无状态的。将状态存储在组件中会导致不必要的悲伤和加剧。我的经验是,与大多数DI配置错误相比,有状态组件要容易得多。我还注意到,对于大多数开发人员来说,拥有单例组件是很自然的事情,尤其是那些没有DI经验的开发人员。要详细讨论两个组成模型可供选择以及它们的缺点和优势,请看一看this serie of blog posts。
请注意,在示例中,我为
BooksContext
手动实现了每个请求的生活方式。尽管所有DI库都对诸如请求的生活方式之类的范围内的生活方式提供了开箱即用的支持,但我还是反对使用这些范围内的生活方式(也许当图书馆保证抛出异常而不是默默地失败时除外)。当您在事件范围的上下文之外解析作用域实例时(例如,在后台线程上解析每个请求实例),大多数库都不会发出警告。某些容器会在您每次询问时返回您一个单例实例,另一些容器会返回您一个新实例。这确实很麻烦,因为它隐藏了错误,并且可能导致您花费很多时间来尝试调试应用程序(我从这里的经验出发)。关于asp.net-mvc - 在没有任何DI库的情况下使用依赖注入(inject),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32032771/