Dependency injection in .NET Core 2.0

Say I have a controller and I want it to pull something from Amazon. Say that I also have unit tests and I don't want to hit Amazon whenever they're run.

I'm not going to explain Dependency Injection / Inversion of Control - Martin Fowler (and a thousand other people) have done that well enough already. But you can see that .NET Core already uses dependency injection by default - just try using the Configuration. It's actually pretty nice, and feels a lot like Google's Guice for Java except maybe even easier to use. I can't say for sure, though - I haven't used this one much yet.

If you haven't already, you'll need to start using an interface and then create a service class to implement that interface with your default behavior. If you had just stuffed all your code into your controller before, this refactoring will look and feel a lot nicer.

So, for example:

public interface IAmazonSqsService  
{
    string QueueUrl { get; set; }
    Task<List<Message>> ReceiveMessages();
    void DeleteMessages(List<Message> messages);
}
public class AmazonSqsService : IAmazonSqsService  
{
    // implemented code here!
}

Then from your controller you can add it to your constructor...

public class ValuesController : Controller  
{
    private IConfiguration config;
    private IAmazonSqsService sqsService;

    public ValuesController(IConfiguration config, IAmazonSqsService sqsService)
    {
        this.config = config;
        this.sqsService = sqsService;
        this.sqsService.QueueUrl = this.config["Amazon:Transactions:QueueUrl"];
    }
}

And now for the sauce. In your Startup.cs you tie in the classes like so:

public class Startup  
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();  // this was here by default
        services.AddTransient<IAmazonSqsService, AmazonSqsService>();
    }
}

Pretty easy, right? You just tell the dependency injection manager that whenever it sees that interface, to use that implementing class by default. Of course, you'll be supplying the argument yourself in your unit tests, so you can use a mock or whatever.

With regards to the AddTransient part of that snippet - there are a couple of different ways that you can have your service passed to your object. In this case, we're telling the dependency manager to instantiate a new AmazonSqsService object for each object that needs it. You can also have singletons and a request-based scope, but Transient seems like your most common use-case. More information here.

Resources