在没有任何DI库的情况下使用依赖注入 [英] Using Dependency Injection without any DI Library

查看:131
本文介绍了在没有任何DI库的情况下使用依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很喜欢Repository和DI,并试图在我的MVC 5项目中实现。



我实现了构造函数注入,我的控制器中有一个这样的构造函数: / p>

  IBook _ibook; 
public Test(IBook ibook)
{
_ibook = ibook;
}

没有任何DI库,它会抛出一个错误:没有空构造函数。



为了避免这种情况,我添加了一个以上的构造函数:

  public Test():this(new Book())
{
}

由于我是DI的新人,我不想使用DI库冒犯我的项目,后者可能会抛出一些可能无法解决的错误。



如果我不使用DI图书馆,我想知道我可能遇到什么问题。



如果推荐,哪个DI库对初学者有好处?我看过很少的NInject和Unity的视频。

解决方案

推迟任何决定使用某种工具是一个好主意或图书馆直到最后一个负责任的时刻。有了很好的设计,你可以在以后添加一个DI库。这意味着您练习纯DI



MVC中的首选拦截点是 IControllerFactory 抽象,因为它允许您拦截MVC控制器的创建,从而阻止您执行第二个构造函数(其中是反模式)。虽然可以使用 IDependencyResolver ,但是使用该抽象方法更不方便,因为它也被MVC调用来解决通常不感兴趣的内容。



将作为您的 IControllerFactory 07/28 / CompositionRoot />组成根可以实现如下:

  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){
//不幸的是,由于MVC的设计,控制器不是无状态的,而
//你将不得不创建他们每个请求。

if(type == typeof(OrderBookController))
返回新的HomeController(orderBookHandler);

if(type == typeof(BooksController))
return new BooksController(bookRepo);

// [其他控制器]

返回base.GetControllerInstance(_,type);
}

private static BooksContext GetCurrentBooksContext(){
return GetRequestItem< BooksContext>(()=> new BooksContext(connectionString));
}

私有静态T GetRequestItem< T>(Func< T> valueFactory)其中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());

//通常的东西在这里
}
}

当您练习Pure DI时,通常会看到您的组合根由 c $ c>的大列表语句组成。您的应用程序中每个根对象的一个​​声明。



从Pure DI开始有一些有趣的优势。最突出的一个是编译时支持,因为这是您在开始使用DI库时立即丢失的东西。一些库可以通过允许您以编译器的方式验证您的配置来尽量减少此损失;但是这个验证是在运行时完成的,反馈周期从来没有编译器可以给你的那么短。



请不要试图通过实现来简化开发一些允许使用反射创建类型的机制,因为在这样做时,您正在构建自己的DI库。这有很多缺点,例如您丢失编译时支持,而不能恢复现有DI库可以提供的任何优点。



当您的组合根开始难以维护时,是您应该考虑从Pure DI切换到DI库的那一刻。



请注意,在我的示例组合根中,所有应用程序组件除了控制器)被定义为单例。 Singleton意味着应用程序将只有每个组件的一个实例。这种设计需要您的组件为无状态(因此线程安全),任何具有状态的组件(例如 BooksContext 不能通过构造函数注入。在该示例中,我使用 Func< T> 作为每个请求存储的 BooksContext 的提供者。 p>

使对象图形单例具有许多有趣的优点。例如,它阻止您发生常见的配置错误,例如强制依赖关系,并强制您进入更多的SOLID设计。此外,一些DI库非常慢,并且使所有的单例都可能会在以后切换到DI库时阻止性能问题。另一方面,这种设计的缺点是,团队中的每个人都应该明白,所有组件都必须是无状态的。在组件中存储状态将导致不必要的悲伤和恶化。我的经验是,有状态的组件比大多数DI配置错误更容易检测。我也注意到,单个组件是大多数开发人员感觉自然的东西,特别是没有DI经验的人。



请注意,在本例中我手动实现 BooksContext 的每个请求生活方式。虽然所有的DI图书馆都支持范围广泛的生活方式,例如每个请求的生活方式,但我会反对使用这些范围的生活方式(除非图书馆保证抛出异常而不是默默地失败)。当您在活动范围的上下文之外解析范围限定的实例(例如,解决后台线程上的每个请求的实例)时,大多数库不会警告您。一些容器将返回一个单例实例,其他容器会在您每次询问时返回一个新的实例。这真的很麻烦,因为它隐藏错误,可能会导致你花很多时间尝试调试你的应用程序(我从这里的经验中说出来)。


I am new to Repository and DI and trying to implement in my MVC 5 project.

I implemented Constructor Injection where in my controller has a constructor like this:

IBook _ibook;
public Test(IBook ibook)
{
   _ibook = ibook;
}

Without any DI library, it throws an error: There is no empty constructor.

To avoid this, I added one more constructor as below:

public Test ():this(new Book())
{    
}

Since I am new to DI, I don't want to risk my project by using DI library which can later throw some error that I may not be able to resolve.

I want to know what issues I might encounter if I am not using DI library.

In case it is recommended, which DI library is good for beginners? I have seen few videos of NInject and Unity.

解决方案

It is a good idea to delay any decision to use some kind of tool or library until the last responsible moment. With a good design you can add a DI library later on. This means that you practice Pure DI.

The preferred interception point in MVC is the IControllerFactory abstraction since it allows you to intercept the creation of MVC controllers, and doing so prevents you from having to implement a second constructor (which is an anti-pattern). Although it is possible to use IDependencyResolver, the use of that abstraction is much less convenient because it is also called by MVC to resolve things you are typically not interested in.

A custom IControllerFactory that will act as your composition root can be implemented as follows:

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;
    }
}

Your new controller factory can be hooked into MVC as follows:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start() {
        ControllerBuilder.Current.SetControllerFactory(new CompositionRoot());

        // the usual stuff here
    }
}

When you practice Pure DI, you will typically see your composition root consist of a big list of if statements. One statement per root object in your application.

Starting off with Pure DI has some interesting advantages. The most prominent one is compile time support, because this is something you will lose immediately when you start using a DI library. Some libraries try minimize this loss by allowing you to verify your configuration in a way that the compiler would do; but this verification is done at runtime and the feedback cycle is never as short as that which the compiler can give you.

Please don't be tempted to simplify development by implementing some mechanism that allows creating types using reflection, because in doing so you are building your own DI library. There are many downsides to this, e.g. you lose compile time support while not getting back any of the benefits that an existing DI library can give you.

When your composition root is starting to get hard to maintain, that is the moment you should consider switching from Pure DI to a DI library.

Do note that in my example composition root, all application components (except for the controllers) are defined as singleton. Singleton means that the application will only have one instance of each component. This design needs your components to be stateless (and thus thread-safe), anything that has state (such as the BooksContext) should not be injected through the constructor. In the example I used a Func<T> as the provider of the BooksContext which is stored per request.

Making your object graphs singletons 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.

Note that in the example I manually implemented a per-request lifestyle for the BooksContext. Although all DI libraries have out-of-the-box support for scoped lifestyles such as per-request lifestyles, I would argue against using those scoped lifestyles (except perhaps when the library guarantees to throw an exception instead of failing silently). Most libraries do not warn you when you resolve a scoped instance outside the context of an active scope (for instance resolving a per-request instance on a background thread). Some containers will return you a singleton instance, others return you a new instance each time you ask. This is really troublesome because it hides bugs and can cause you many hours trying to debug your application (I speak from experience here).

这篇关于在没有任何DI库的情况下使用依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆