Dependency Injection with classes other than a Controller class
Asked Answered
R

7

118

At this point I'm injecting things into my Controllers with ease, in some cases building my own ResolverServices class. Life is good.

What I cannot figure out how to do is get the framework to automatically inject into non-controller classes. What does work is having the framework automatically inject into my controller IOptions, which is effectively the configuration for my project:

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

    public MessageCenterController(IOptions<MyOptions> options)
    {
        _options = options.Value;
    }
}

I'm thinking whether I can do the same for for my own classes. I assume I'm close when I mimic the controller, like this:

public class MyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    }

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

I think where I'm failing is when I call it like this:

public void DoSomething()
{
    var helper = new MyHelper(??????);

    if (helper.CheckIt())
    {
        // Do Something
    }
}

The problem I have tracking this down is practically everything that talks about DI is talking about it at the controller level. I tried hunting down where it happens in the Controller object source code, but it gets kinda crazy in there.

I do know I can manually create an instance of IOptions and pass it to the MyHelper constructor, but it seems like I should be able to get the framework do that since it works for Controllers.

Reasoning answered 12/5, 2016 at 14:30 Comment(3)
When you use dependency injection you don't call new. Never for objects that should be resolvedZeidman
When I'm trying to create an instance of MyHelper, I don't call new? (1) That sounds too easy, (2) It's a syntax error. :-)Reasoning
Yes, that's the whole point of dependency injection (especially if using inversion of control containers which manage and do this instantiation). To push the instantation outside of your services/classes up to the point where the ioc container does that internally. In cases where you can't inject it via constructor you create a factory and pass the factory's interface to your service. the implementation of it uses the container to resolve it, in ASP.NET Core case injecting IServiceProvider in your factory and calling IMyHelper helper = services.RequestService<IMyHelper>()Zeidman
R
65

Below is a working example of using DI without anything that involves MVC Controllers. This is what I needed to do to understand the process, so maybe it will help somebody else.

The ShoppingCart object gets, via DI, an instance of INotifier (which notifies the customer of their order.)

using Microsoft.Extensions.DependencyInjection;
using System;

namespace DiSample
{
    // STEP 1: Define an interface.
    /// <summary>
    /// Defines how a user is notified. 
    /// </summary>
    public interface INotifier
    {
        void Send(string from, string to, string subject, string body);
    }

    // STEP 2: Implement the interface
    /// <summary>
    /// Implementation of INotifier that notifies users by email.
    /// </summary>
    public class EmailNotifier : INotifier
    {
        public void Send(string from, string to, string subject, string body)
        {
            // TODO: Connect to something that will send an email.
        }
    }

    // STEP 3: Create a class that requires an implementation of the interface.
    public class ShoppingCart
    {
        INotifier _notifier;

        public ShoppingCart(INotifier notifier)
        {
            _notifier = notifier;
        }

        public void PlaceOrder(string customerEmail, string orderInfo)
        {
            _notifier.Send("[email protected]", customerEmail, $"Order Placed", $"Thank you for your order of {orderInfo}");
        }

    }

    public class Program
    {
        // STEP 4: Create console app to setup DI
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();

            // ConfigureServices(serviceCollection)
            serviceCollection.AddTransient<INotifier, EmailNotifier>();

            // create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // This is where DI magic happens:
            var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider);

            myCart.PlaceOrder("[email protected]", "2 Widgets");

            System.Console.Write("Press any key to end.");
            System.Console.ReadLine();
        }
    }
}
Reasoning answered 30/5, 2017 at 2:50 Comment(12)
What if I want to instantiate the ShoppingCart in another class or method that we don't access the serviceProvider object?Overword
Thank you, I had to search far too wide to get to ActivatorUtilities.CreateInstance.Averett
this also defeats the purpose, using the ActivatorUtilities is probably the same as "new" code but I needed to see it to believe itConchoidal
what if I don't have serviceProvider??!!Toothpaste
Thank you! I'm using it with Microsoft.AspNetCore.TestHost, Creating a TestServer and calling ActivatorUtilities.CreateInstance<MyCustomerController>(_server.Host.Services);Oversee
@Toothpaste If you don't have a service provider you cannot use dependency injection. It's required by definition. The question "What if I don't have a ServiceProvider?" is similar to asking "What if I don't have milk?" in a discussion about how to pour a glass of milk. The ServiceProvider is the thing that holds your services - without it you have no services to "pour" into your new object.Tautog
Why AddTransient? This creates a new instance each time the type is requested. Do you really need a new instance?Reverend
You could use AddScope too.Reasoning
@GonzaloBustamante Feel free to name a child after me. (Preferably one of yours)Reasoning
What is ActivatorUtilities ?Haight
Can someone separate the 'create service provider' and using that class in another service class and not inside the main method.Rejoinder
I wish I could upvote this multiple times, bless you.Suannesuarez
V
43

Let's say MyHelper is used by MyService which in turn is used by your controller.

The way to resolve this situation is:

  • Register both MyService and MyHelper in Startup.ConfigureServices.

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • The controller receives an instance of MyService in its constructor.

    public HomeController(MyService service) { ... }
    
  • MyService constructor will in turn receive an instance of MyHelper.

    public MyService(MyHelper helper) { ... }
    

The DI framework will be able resolve the whole object graph without problems. If you are worried about new instances being created every time an object is resolved, you can read about the different lifetime and registration options like the singleton or request lifetimes.

You should be really suspicious when you think you have to manually create an instance of some service, as you might end up in the service locator anti-pattern. Better leave creating the objects to the DI Container. If you really find yourself in that situation (let's say you create an abstract factory), then you could use the IServiceProvider directly (Either request an IServiceProvider in your constructor or use the one exposed in the httpContext).

var foo = serviceProvider.GetRequiredService<MyHelper>();

I would recommend reading the specific documentation about the ASP.Net 5 DI framework and about dependency injection in general.

Voltmer answered 12/5, 2016 at 16:5 Comment(2)
The problem I have with this is that I use background services that interact with the database, so the scoped lifetime with dbcontext doesnt work, right? How do you use DI properly with EF core and background services?Pi
Apply to minimal API ?Haight
D
9

Unfortunately there is no direct way. The only way I managed to make it work is by creating a static class and using that everywhere else as below:

public static class SiteUtils
{

    public static string AppName { get; set; }

    public static string strConnection { get; set; }
 
}

Then in your startup class, fill it in as below:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //normal as detauls , removed for space 
    // set my variables all over the site

    SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection");
    SiteUtils.AppName = Configuration.GetValue<string>("AppName");
}

Although this is bad pattern, as this will stay for the whole life cycle of the application and I couldn't find better way to use it outside controller.

Dunton answered 23/5, 2017 at 16:31 Comment(1)
For me this worked well. Helpful and very simple.Lilt
C
3

Here's a more complete example to directly answer the OP's question, based on the current .NET Core 2.2 DI documentation here. Adding this answer since it may help someone that's new to .NET Core DI, and because this question is Google's top search result.

First, add an interface for MyHelper:

public interface IMyHelper
{
    bool CheckIt();
}

Second, update the MyHelper class to implement the interface (in Visual Studio, press ctrl-. to implement the interface):

public class MyHelper : IMyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    {

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

Third, register the interface as a framework-provided service in the DI service container. Do this by registering the IMyHelper service with the concrete type MyHelper in the ConfigureServices method in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IMyHelper, MyHelper>();
    ...
}

Fourth, create a private variable to reference an instance of the service. Pass the service as an argument in the constructor (via constructor injection) then initialize the variable with the service instance. Reference any properties or call methods on this instance of the custom class via the private variable.

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;
    private readonly IMyHelper _myHelper;

    public MessageCenterController(
        IOptions<MyOptions> options,
        IMyHelper myHelper
    )
    {
        _options = options.value;
        _myHelper = myHelper;
    }

    public void DoSomething()
    {
        if (_myHelper.CheckIt())
        {
            // Do Something
        }
    }
}
Chatty answered 22/8, 2019 at 15:55 Comment(0)
E
1

TL;DR: You can save a singleton in a static var and then access it form other classes, but this an anti-pattern, use with caution.

Long version:

As per this question Resolving instances with ASP.NET Core DI from within ConfigureServices

Any services registered in ConfigureServices() can then be injected into the Configure() method

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<FooService>();
}

public void Configure(IApplicationBuilder app, FooService fooService)
{
    FooServiceInstance = fooService;
}

public static FooService FooServiceInstance { get; private set; }

And then call it from your other code MyStartupClass.FooService.DoStuff()

Edi answered 28/8, 2021 at 18:1 Comment(1)
if I want to initialize an event listener which will then poll events since no controller invokes this listener, how do I bring it up, especially when using a container like unity which only injects classes when requested?Supernational
J
0

You may use Activator.CreateInstance(). Here is a wrapper function for it. The way you use this is as follows.

var determinedProgrammatically = "My.NameSpace.DemoClass1"; // implements IDemo interface
var obj = CreateInstance<My.NameSpace.IDemo, string>(determinedProgrammatically, "This goes into the parameter of the constructor.", "Omit this parameter if your class lives in the current assembly");

Now you have an instance of obj which is instantiated from type determined programmatically. This obj can be injected into non controller classes.

public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null)
{
    var type = dllName == null ? System.Type.GetType(typeName) :
            System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName);
    return (TInterface)System.Activator.CreateInstance(type, constructorParam);

}

PS: You may iterate through System.AppDomain.CurrentDomain.GetAssemblies() to determine the name of the assembly that houses your class. This name is used in the 3rd parameter of the wrapper function.

Jimenez answered 17/6, 2020 at 13:15 Comment(0)
H
0

ASP.NET Core DI will instantiate you Controller object, as well as objects in the Controller's constructor's argument list, as long as their types are registered in Program.cs using AddScoped/Transient/Singleton. And it will also instantiate objects in those class's constructor's argument list, as long as they are registered. And so on ad nauseum. It is a chain, all started from the Controller class (or from a View Component class).

Hoe answered 13/4 at 1:2 Comment(3)
But they’re explicitly asking about non-controller classes. You’re offering a basic explanation of how DI in .NET works without showing how to solve the specific problem.Millinery
My answer describes how DI can be used with non-controller classes as long as they're in the calling sequence beginning with a controller.Hoe
Yes, I understand that. But that’s not what the OP is asking about.Millinery

© 2022 - 2024 — McMap. All rights reserved.