preloader

Apprentice Dev Blog: Week 2 - Injecting Dependencies

A sepia toned image of a person's decking and back yard. The image has been stretched via a fish-eye lens, giving it a rounded look.

The cover image for this post is by James Peacock

This blog post was written by Alex. Alex is our apprentice for 2022, and will be providing frequent developer logs.


Jamie here - to jump directly to Alex’s log for this week, click here

Today marks the completion of Alex’s second week with us.

remember that Alex is NOT their real name

Alex is studying a Digital T-Level course and has joined us for their mandatory eight weeks in industry.

We are incredibly proud to be helping with Alex’s technical education. And as a reminder, I was assigned to be their PM on the project that they are working on and someone to reach out to for help in completing it. The Digital T-Level course that Alex has taken is very comprehensive and covers a number of different development technologies, but there are certain gaps in their knowledge. This is not meant as a negative point towards either the course designers or those who are delivering it, there is simply not enough time to teach all of the things required of a developer in just two short years.

in fact, development is one of the career choices which require a person to be constantly learning new things

With this, and their knowledge from the end of the first week in mind, I decided that week two would be a good time to introduce Inversion of Control, Dependency Injection, loading config, and the idea of a Composition Root.

You can think of a composition root as a place where you you wire up all of you services and inject them into your Dependecy Inection (DI) container; and a DI container is a system which will inject any dependencies into any classes which require them.

As a reminder, we started a project with the following goal in mind:

"Let’s start with Framework, then move to modern .NET. And if there’s time, we’ll investigate .NET Maui when it’s released next week."

- Jamie

What I didn’t say last time (but Alex did) was that we would be working with WinForms, which is a 20+ year old technology. This was for three reasons:

  1. Alex has familiarity with WinForms
  2. Modern .NET supports it
  3. (most importantly) there’s 20+ years of documentation and examples out there

The last point is the most important one. There’s no worse feeling than getting stuck and knowing that there’s almost no help out there. Plus, Alex is building the project out using the Single-responsibility Principle; as such the project that they are building is built up of several composable, yet completely separate projects. We’ll be swapping that UI framework out for something more exciting very soon.

Anyway, you didn’t come here to read what I have to say. So here is a development log which represents Alex’s second week, in their own words:

one note, I’ve edited for presentation not content


Alex’s Devlog

Hi, I’m Alex and am currently studying a Digital T-Level course, am taking my industry experience with RJJ Software, and these are some of the things that I learned this week.

The first thing that Jamie wanted to talk me through was some more of the letters used in SOLID. From last week, I knew that the full acronym meant:

  • Single-responsibility Principle
  • Open-closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

But we’d only used the “S” so far, and the others looked quite complex. So we started by looking at what Dependency Inversion is. Jamie always likes to use examples, and the example he shared was:

"Imagine that you have a lamp, and that the bulb on it blows. in order to make the lamp work again, you just swap out the bulb for one that works. You don’t have to rewirire the lamp to work with the electrical socket, as you’ve inverted the dependency. You no longer directly depend on the power coming out of the socket because, for 99% of situations, you can assume that power will always come out of it, in DC at around 230V at 50Hz. The folks who wired up the lamp took care of that for you, and you don’t have to take the raw wires and plumb them into the power grid directly."

- Jamie

The reason that Jamie had brought this up is that I was violating a rule called “New is Glue” - even though I didn’t know about the rule. I was “newing” up every class or service that I dependended on, when I depended on them. Here’s an example of what I mean:

public void btnSomeButton(object sender, EventArgs e)
{
    // When handling this button click, we need to call a method on a service
    using (var myservice = new ServiceClass())
    {
        var response = myService.HandleUserRequest();
    }
}

There’s nothing inherantly wrong with this. With the only exception that I’ve directly tied my button click handler to the class ServiceClass. This means that if I had 15 buttons, each using the same service, and I wanted to change the service then I would need to manually change each of those 15 button handlers.

So what’s the answer? Interfaces, apparently.

“Interface” here doesn’t mean user interface, and has nothing to do with a GUI. An interface is a C# contract, if a class inherts from an interface that is required to implement all of the methods on that interface. Here’s an example using two classes and one interface:

public interface IService
{
    int HandleUserRequest();
    int SaveChanges();
}

// This class will not stop our program compiling, because it does
// implement the entire contract provided by the IService interface
public class ImplementsServiceInterface : IService
{
    public int HandleUserRequest()
    {
        // do something here
        return default(int);
    }

    public int SaveChanges()
    {
        // do something here
        return default(int);
    }
}

// This class will stop our program from compiling, because it does not
// implement the entire contract provided by the IService interface
public class DoesNotImplemenServiceInterface : IService
{
    public int HandleUserRequest()
    {
        // do something here
        return default(int);
    }
}

In the above, IService is an interface which lists two methods HandleUserRequest and SaveChanges, both return ints. If a class inherits (i.e uses the : followed by the name of something) from an interface it HAS to include all of the methods and properties that the interface lists. If it does not implement all of the methods and properties of that interface, the C# compiler will not compile the code.

So in the example ImplementsServiceInterface will not cause a compiler error, but DoesNotImplemenServiceInterface will. This is because DoesNotImplemenServiceInterface does not implement the SaveChanges method. The method doesn’t actually have to do anything, just being present is enough. We can change the DoesNotImplemenServiceInterface class to read like this:

// This class will not stop our program compiling, because it does
// implement the entire contract provided by the IService interface
public class DoesNotImplemenServiceInterface : IService
{
    public int HandleUserRequest()
    {
        // do something here
        return default(int);
    }

    public int SaveChanges()
    {
        throw new NotImplementedException();
    }
}

and the C# compiler will be happy. This is because DoesNotImplemenServiceInterface now implements the interface’s contract correctly.

NOTE: It does not matter whether the methods on the class actually do anything. Just being there is enough.

But why is it important to use interfaces? It’s important because that’s how you implement dependency injection in .NET. Or at least, one way that you can implement depenedncy injection.

Jamie here: it also allows us to program to interfaces, and to implement test-driven development with mocking/faking easier.

And implementing dependency injection in .NET 6 is done by adding some things to your Program.cs or Startup.cs class. I’m currently working with WinForms, so I don’t have a Startup.cs - becauase that’s mostly used with ASP .NET, which we’re not using. Here’s what my Program.cs looked like before I implemented dependency injection:

using System;
using System.Windows.Forms;

namespace AlexProject
{
    internal static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new StartScreen());
        }
    }
}

For people who have done WinForms development in the past, this is nothing new. For those who haven’t, the main form window for this application is in a class StartScreen; we create a new instance of it, and pass that new instance into Application.Run(). My StartScreen class relied on a service called MyService and called a method on it when a button was clicked, like this:

using AlexProject.Models;
using System;

namespace AlexProject
{
    public partial class StartScreen : Form
    {
        public StartScreen()
        {
            InitializeComponent();
        }

        private void btn_Create_Click(object sender, EventArgs e)
        {
            // new is glue
            var service = new MyService();

            var response = service.Create();
        }
    }
}

The big problem is that I was using new, and not injecting my dependencies. So, I created an interface for my MyService class and set about injecting that. First off the interface was really simple:

using AlexProject.Models;
using System;

namespace AlexProject
{
    public interface IMyService
    {
        CreatedThing Create();
    }
}

CreatedThing here is a class that I had elsewhere, and is the type of thing that I wanted the service to return. Now my MyService class had to change slightly:

using AlexProject.Models;
using System;

namespace AlexProject
{
    public class MyService : IMyService
    {
        public CreatedThing Create()
        {
            // create the thing
            return default(CreatedThing);
        }
    }
}

The code here is really cut down because I didn’t want to have to explain what the MyService class is actually doing.

With the interface in place, wiring up the dependency injection was a case of making some small tweaks. First the Program.cs file:

using System;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;

namespace AlexProject
{
    internal static class Program
    {
        /// <summary>
        /// This is the composition root. We will add all of our dependencies here
        /// to be injected into the running application for us.
        /// </summary>
        /// <param name="services"></param>
        private static void ConfigureServices(IServiceCollection services)
        {
            // Adding a dependency to the IServiceCollection will ensure that .NET
            // can inject it into any class we need. You will see this in the class
            // constructors - a concrete class will be injected in where we expect
            // an interface.

            services
                // First, inject the Start Screen class, otherwise we can't start the application
                .AddTransient<StartScreen>()
                // Here we are saying that whenever a constructor calls for an IMyService, the
                // DI Container should create a new instance of the MyService and pass that in
                .AddTransient<IMyService, NyService>();
        }
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.SetCompatibleTextRenderingDefault(false);

            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider())
            {
                // Manually get the instance of the StartScreen class which was injected
                // into our DI container and use that to run the application with
                var startScreen = serviceProvider.GetService<StartScreen>();
                Application.Run(startScreen);
            }
        }
}

The comments here explain what’s going on

Jamie here: and the why; which is more important to explain in comments than the what

Then a small change is made to the StartScreen class like this:

using AlexProject.Models;
using System;

namespace AlexProject
{
    public partial class StartScreen : Form
    {
        private readonly IMyService _myService;

        // We're asking the DI container to find a class which satisfies the
        // IMyService interface and inject it in here.
        public StartScreen(IMyService myService)
        {
            InitializeComponent();
            _myService = myService;
        }

        private void btn_Create_Click(object sender, EventArgs e)
        {
            // No more need for new, as the DI container will pass a class which
            // implements the IMyService contract in to the constructor of this
            // class for us.
            var response = _myService_.Create();
        }
    }
}

With this in place, we had only one major problem left: the connection string for the database.

When we started this project, the connection string for the database was added into source control.

Jamie here: committing secrets into source control is something which happens quite a lot.

This wasn’t a huge problem initially, but we wanted to fix that. So we needed to read the connection string from a config file. Jamie suggested that we use the appsettings.json from ASP .NET Core. So I created an appsettings.json file in the AlexProject folder which looked like this:

{
  "ConnectionStrings": {
    "connectionString": "<CONNECTION_STRING_HERE>"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

then committed that file to source control (using git add appsettings.json && git commit -m "Added appsettings.json"). Then immediately told git to forget about tracking it with git update-index --assume-unchanged appsettings.json. This tells git to assume that the appsettings.json file is never changed, regardless of how often it actually changes. This means that I’ll never be able to commit a changed version of this file into source control

Jamie here: that is unless you run git update-index --no-assume-unchanged appsettings.json which will undo the previous command

After that, we needed to change the Program.cs one more time. Not only were we going to read the config from a file, but we were also going to hook up the DbContext at the composition root (i.e. Program.cs), too. Previously, I’d been using the DbContext like this:

// MyService.cs
public void SaveNewCreatedThingToDatabase(CreatedThing newCreatedThing)
{
    using(var context = new AlexProjectDbContext())
    {
        context.CreatedThings.Add(newCreatedThing);
    }
}

I’ve simplified the above code to focus on the point that I was using new. But we could do away with new by doing two new things in Program.cs:

// Program.cs

// Most of this method was unchanged, except that is now has a second argument (i.e. connectionString)
private static void ConfigureServices(IServiceCollection services, string connectionString)
{
    services
        .AddTransient<StartScreen>()
        .AddTransient<IClientService, ClientService>()

        // And this is where we inject the DbContext
        .AddDbContext<AlexProjectDbContext>(options => options.UseSqlServer(connectionString));
}

// This method builds an IConfiguration object and fills it using
// the contents of the appsettings.json file
private static IConfiguration BuildDefaultConfiguration()
{
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
    return builder.Build();
}

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetHighDpiMode(HighDpiMode.SystemAware);
    Application.SetCompatibleTextRenderingDefault(false);

    var serviceCollection = new ServiceCollection();
    ConfigureServices(serviceCollection);

    // build the IConfiguration object and get the connection string from memory
    var connectionString = BuildDefaultConfiguration().GetConnectionString("connectionString");

    using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider())
    {
        // Manually get the instance of the StartScreen class which was injected
        // into out DI container and use that to run the application with
        var startScreen = serviceProvider.GetService<StartScreen>();
        Application.Run(startScreen);
    }
}

With all of that in place, we could pass the DbContext into the MyService class:

using AlexProject.Models;
using System;

namespace AlexProject
{
    public class MyService : IMyService
    {
        private AlexProjectDbContext _dbContext;

        public MyService(AlexProjectDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public CreatedThing Create()
        {
            // create the thing
            return default(CreatedThing);
        }

        public void SaveNewCreatedThingToDatabase(CreatedThing newCreatedThing)
        {
            // no more new
            _dbContext.CreatedThings.Add(newCreatedThing);
        }
    }
}

These changes made it easy for me to speed up my progress with this project, but also so that I could check off learning the following principles in SOLID:

  • Single-responsibility Principle
  • Open-closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Because we now have interfaces, I’ll be able to learn about test-driven development next week. Jamie has told me that this will greatly increase the speed that I can write code, but it will also de-couple the code base even further. This is great news, because I’m looking forward to playing around with .NET Maui soon.


Jamie again.

In the next week, we’re going to be looking:

  • Creating either an IRepository interface to wrap up our DbContext
  • Exploring Test-Driven Development using our new interfaces
  • Looking at swapping the UI for one built with .NET Maui (if there’s time)

It’s been an incredibly fun ride so far, and Alex is flying through the tasks and learning that they have had to do.