在没有任何 DI 库的情况下使用依赖注入
Using Dependency Injection without any DI Library
我是 Repository 和 DI 的新手,正在尝试在我的 MVC 5 项目中实施。
我实现了构造函数注入,我的控制器中有这样一个构造函数:
IBook _ibook;
public Test(IBook ibook)
{
_ibook = ibook;
}
没有任何DI库,会报错:没有空的构造函数。
为了避免这种情况,我又添加了一个构造函数,如下所示:
public Test ():this(new Book())
{
}
由于我是 DI 的新手,我不想使用 DI 库来冒险我的项目,这以后可能会抛出一些我可能无法解决的错误。
我想知道如果我不使用 DI 库可能会遇到什么问题。
万一推荐一下,哪个DI库适合新手?我看过一些 NInject 和 Unity 的视频。
Ninject unity提供了对象容器,里面包含了你在应用启动时注册的对象,
但是为什么你需要使用 di,Di 指出两个对象不应该依赖于它的创建,它应该依赖于它的抽象,所以如果假设将来你需要将 Book class 替换为电子书,在这里class 都具有相同的功能,但它具有不同的构造,当时您只需要您的 di 配置,您不需要为电子书重新编码控制器。
我在我的大多数项目中都使用 unity di 我没有遇到任何我无法轻松解决的问题并练习使用它,不要为此害怕。
如果您只对依赖注入感兴趣以实现某种程度的抽象,您绝对不需要使用任何 IoC 框架。
如果您不关心作用域、生命周期和嵌套依赖项,您最终可能会得到像这样原始的东西:
internal class MyBasicResolver : IDependencyResolver
{
private readonly Dictionary<Type, Type> _services = new Dictionary<Type, Type>()
{
{ typeof(IBook), typeof(Book) }
// more services registrations
};
public object GetService(Type serviceType)
{
return _services.ContainsKey(serviceType) ? Activator.CreateInstance(_services[serviceType]) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
yield return GetService(serviceType);
}
}
然后将其注册为 MVC 的当前依赖解析器:
DependencyResolver.SetResolver(new MyBasicResolver());
见MSDN
最简单和最明智的解决方案是使用 Pure DI. With ASP.NET MVC, this is most easily done by deriving from DefaultControllerFactory 并覆盖 GetControllerInstance
:
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
if (controllerType == typeof(Test))
return new Test(new Book());
return base.GetControllerInstance(requestContext, controllerType);
}
然后像这样在您的 Global.asax 中注册您的新控制器工厂:
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
不幸的是,很多文档会告诉您使用 IDependencyResolver
或 Bastard Injection 来处理依赖注入,但是 these will not make your code more maintainable.
在 my book.
中有更多详细信息,包括如何通过 ASP.NET MVC 正确使用依赖注入的示例
最好将使用某种工具或库的任何决定推迟到 the last responsible moment. With a good design you can add a DI library later on. This means that you practice Pure DI。
MVC 中的首选拦截点是 IControllerFactory
抽象,因为它允许您拦截 MVC 控制器的创建,并且这样做可以防止您必须实现第二个构造函数(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;
}
}
您的新控制器工厂可以挂接到 MVC 中,如下所示:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start() {
ControllerBuilder.Current.SetControllerFactory(new CompositionRoot());
// the usual stuff here
}
}
当您练习 Pure DI 时,您通常会看到您的 Composition Root 包含一大串 if
语句。应用程序中每个根对象一条语句。
从 Pure DI 开始有一些有趣的优势。最突出的是编译时支持,因为这是您在开始使用 DI 库时会立即失去的东西。一些库通过允许您以编译器会做的方式验证您的配置来尝试将这种损失降到最低;但是这种验证是在运行时完成的,反馈周期永远不会像编译器给你的那样短。
请不要试图通过实现某种允许使用反射创建类型的机制来简化开发,因为这样做是在构建您自己的 DI 库。这有很多缺点,例如您失去了编译时支持,同时没有取回现有 DI 库可以给您带来的任何好处。
当您的 Composition Root 开始变得难以维护时,这就是您应该考虑从 Pure DI 切换到 DI 库的时刻。
请注意,在我的示例组合根中,所有 应用程序组件(控制器除外)被定义为单例。单例意味着应用程序将只有每个组件的一个实例。这种设计需要您的组件是无状态的(因此是线程安全的),任何有状态的组件(例如 BooksContext
)should not be injected through the constructor。在示例中,我使用 Func<T>
作为 BooksContext
的提供者,每个请求都会存储。
使您的对象图成为单例has many interesting advantages. For instance, it prevents you from making common configuration errors such as Captive Dependencies and it forces you into a more SOLID design. And besides, some DI libraries are very slow, and making everything a singleton might prevent performance problems when switching to a DI library later on. On the other hand, the downside of this design is that everybody on the team should understand that all components must be stateless. Storing state in components will cause needless grief and aggravation. My experience is that stateful components are much easier to detect than most DI configuration errors. I have also noticed that having singleton components is something that feels natural to most developers, especially those who aren't experienced with DI. For a detailed discussion on the two composition models to choose from and their downsides and advantages, take a look at this serie of blog posts。
请注意,在示例中,我为 BooksContext
手动实现了按请求的生活方式。尽管所有 DI 库都对范围内的生活方式(例如按请求的生活方式)提供开箱即用的支持,但我反对使用这些范围内的生活方式(除非库保证抛出异常而不是默默地失败)。当您在活动范围的上下文之外解析范围内的实例时,大多数库不会警告您(例如在后台线程上解析每个请求的实例)。有些容器会 return 给你一个单例实例,其他容器 return 你每次询问时都会给你一个新实例。这真的很麻烦,因为它隐藏了错误并且可能会导致您花费很多时间来尝试调试您的应用程序(我是根据这里的经验说的)。
我是 Repository 和 DI 的新手,正在尝试在我的 MVC 5 项目中实施。
我实现了构造函数注入,我的控制器中有这样一个构造函数:
IBook _ibook;
public Test(IBook ibook)
{
_ibook = ibook;
}
没有任何DI库,会报错:没有空的构造函数。
为了避免这种情况,我又添加了一个构造函数,如下所示:
public Test ():this(new Book())
{
}
由于我是 DI 的新手,我不想使用 DI 库来冒险我的项目,这以后可能会抛出一些我可能无法解决的错误。
我想知道如果我不使用 DI 库可能会遇到什么问题。
万一推荐一下,哪个DI库适合新手?我看过一些 NInject 和 Unity 的视频。
Ninject unity提供了对象容器,里面包含了你在应用启动时注册的对象,
但是为什么你需要使用 di,Di 指出两个对象不应该依赖于它的创建,它应该依赖于它的抽象,所以如果假设将来你需要将 Book class 替换为电子书,在这里class 都具有相同的功能,但它具有不同的构造,当时您只需要您的 di 配置,您不需要为电子书重新编码控制器。
我在我的大多数项目中都使用 unity di 我没有遇到任何我无法轻松解决的问题并练习使用它,不要为此害怕。
如果您只对依赖注入感兴趣以实现某种程度的抽象,您绝对不需要使用任何 IoC 框架。
如果您不关心作用域、生命周期和嵌套依赖项,您最终可能会得到像这样原始的东西:
internal class MyBasicResolver : IDependencyResolver
{
private readonly Dictionary<Type, Type> _services = new Dictionary<Type, Type>()
{
{ typeof(IBook), typeof(Book) }
// more services registrations
};
public object GetService(Type serviceType)
{
return _services.ContainsKey(serviceType) ? Activator.CreateInstance(_services[serviceType]) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
yield return GetService(serviceType);
}
}
然后将其注册为 MVC 的当前依赖解析器:
DependencyResolver.SetResolver(new MyBasicResolver());
见MSDN
最简单和最明智的解决方案是使用 Pure DI. With ASP.NET MVC, this is most easily done by deriving from DefaultControllerFactory 并覆盖 GetControllerInstance
:
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
if (controllerType == typeof(Test))
return new Test(new Book());
return base.GetControllerInstance(requestContext, controllerType);
}
然后像这样在您的 Global.asax 中注册您的新控制器工厂:
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
不幸的是,很多文档会告诉您使用 IDependencyResolver
或 Bastard Injection 来处理依赖注入,但是 these will not make your code more maintainable.
在 my book.
中有更多详细信息,包括如何通过 ASP.NET MVC 正确使用依赖注入的示例最好将使用某种工具或库的任何决定推迟到 the last responsible moment. With a good design you can add a DI library later on. This means that you practice Pure DI。
MVC 中的首选拦截点是 IControllerFactory
抽象,因为它允许您拦截 MVC 控制器的创建,并且这样做可以防止您必须实现第二个构造函数(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;
}
}
您的新控制器工厂可以挂接到 MVC 中,如下所示:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start() {
ControllerBuilder.Current.SetControllerFactory(new CompositionRoot());
// the usual stuff here
}
}
当您练习 Pure DI 时,您通常会看到您的 Composition Root 包含一大串 if
语句。应用程序中每个根对象一条语句。
从 Pure DI 开始有一些有趣的优势。最突出的是编译时支持,因为这是您在开始使用 DI 库时会立即失去的东西。一些库通过允许您以编译器会做的方式验证您的配置来尝试将这种损失降到最低;但是这种验证是在运行时完成的,反馈周期永远不会像编译器给你的那样短。
请不要试图通过实现某种允许使用反射创建类型的机制来简化开发,因为这样做是在构建您自己的 DI 库。这有很多缺点,例如您失去了编译时支持,同时没有取回现有 DI 库可以给您带来的任何好处。
当您的 Composition Root 开始变得难以维护时,这就是您应该考虑从 Pure DI 切换到 DI 库的时刻。
请注意,在我的示例组合根中,所有 应用程序组件(控制器除外)被定义为单例。单例意味着应用程序将只有每个组件的一个实例。这种设计需要您的组件是无状态的(因此是线程安全的),任何有状态的组件(例如 BooksContext
)should not be injected through the constructor。在示例中,我使用 Func<T>
作为 BooksContext
的提供者,每个请求都会存储。
使您的对象图成为单例has many interesting advantages. For instance, it prevents you from making common configuration errors such as Captive Dependencies and it forces you into a more SOLID design. And besides, some DI libraries are very slow, and making everything a singleton might prevent performance problems when switching to a DI library later on. On the other hand, the downside of this design is that everybody on the team should understand that all components must be stateless. Storing state in components will cause needless grief and aggravation. My experience is that stateful components are much easier to detect than most DI configuration errors. I have also noticed that having singleton components is something that feels natural to most developers, especially those who aren't experienced with DI. For a detailed discussion on the two composition models to choose from and their downsides and advantages, take a look at this serie of blog posts。
请注意,在示例中,我为 BooksContext
手动实现了按请求的生活方式。尽管所有 DI 库都对范围内的生活方式(例如按请求的生活方式)提供开箱即用的支持,但我反对使用这些范围内的生活方式(除非库保证抛出异常而不是默默地失败)。当您在活动范围的上下文之外解析范围内的实例时,大多数库不会警告您(例如在后台线程上解析每个请求的实例)。有些容器会 return 给你一个单例实例,其他容器 return 你每次询问时都会给你一个新实例。这真的很麻烦,因为它隐藏了错误并且可能会导致您花费很多时间来尝试调试您的应用程序(我是根据这里的经验说的)。