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.
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:
- Alex has familiarity with WinForms
- Modern .NET supports it
- (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.
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.