In Prod

← Back to Posts

Architecture and Dependency Injection in .NET Framework

April 14, 2020

One way to achieve a good separation of concerns in our code is by use of Dependency Injection (DI). Reusability and readability of our code becomes more and more important when dealing with larger projects, and starting with good project architecture is a great first step. It can help with writing code that is predictable and maintainable for the rest of your team and lead to fewer headaches in the long run.

DI using interface also makes setting up testing and mocking a lot easier, as we can inject a separate instance that implements the expected interface instead of our actual code.

ASP.NET core seems to follow an 'inject everything' paradigm using it's Startup.cs file for dependency resolution, which makes setting up DI in a project nice and easy. Framework however requires a bit more setup.

Architecture

For our API we will be using the following folder structure when setting out our files:

  • Models
  • Controllers
  • Interfaces
  • Services

When building a Web API, I usually divide up the code into sets of operations that apply to a model (separation of concerns). This is usually as simple as CRUD operations on a table in a database. At a high level, we have our model that defines the table, a service that performs the data operations, the interface that describes the service layer, and the controller that gives our client access to these operations. We would then have a set of these files per model that we need to operate on.

I quite like this architecture as it's nice and simple and can be used as a basis for a readable and maintainable project.

Burgers

As a simple example we will be implementing a controller that retrieves a list of Burgers from a table in a database.

Burger.cs

_5
public class Burger {
_5
public string Name { get; set; }
_5
public double Price { get; set; }
_5
public string Ingredients { get; set; }
_5
}

BurgerService.cs

_7
public class BurgerService : IBurgerService {
_7
public List<Burger> GetBurgers() {
_7
var db = new DBContext();
_7
var items = db.Burgers.ToList();
_7
return items;
_7
}
_7
}

IBurgerService.cs

_3
public interface IBurgerService {
_3
List<Burger> GetBurgers();
_3
}

BurgersController.cs

_13
[ApiController]
_13
[Route("api/burgers")]
_13
public class BurgersController : ControllerBase
_13
{
_13
[HttpGet]
_13
[Route("")]
_13
public IActionResult GetBurgers()
_13
{
_13
var service = new BurgerService();
_13
var items = service.GetBurgers();
_13
return Ok(items);
_13
}
_13
}

The above files set up a rudimentary endpoint that returns a list of burgers from the database (example using Entity Framework).

The issue we are seeing here is that each of our services and db contexts need to be instantiated each time they are called. This means we would need to new up a service to be defined for each separate method it's involved in. This causes a lot of bloat and repetition in the code (DRY), which makes readability and maintainability an issue.

Dependency Injection

To help with our issue, we turn to DI. In this example we will be able to refactor to inject our DBContext and BurgerService classes for consumption when our service and controller are instantiated.

BurgersController.cs

_21
[ApiController]
_21
[Route("api/burgers")]
_21
public class BurgersController : ControllerBase
_21
{{
_21
// setup a hoisted variable our endpoints can use
_21
public readonly IBurgerService service;
_21
_21
// add a contructor that assigns our injected service
_21
public BurgersController(IBurgerService _service) {
_21
service = _service;
_21
}
_21
_21
[HttpGet]
_21
[Route("")]
_21
public IActionResult GetBurgers()
_21
{
_21
// we can then refer to our private var
_21
var items = service.GetBurgers();
_21
return Ok(items);
_21
}
_21
}

BurgerService.cs

_15
public class BurgerService : IBurgerService {
_15
// setup a hoisted variable our methods can use
_15
private readonly DBContext db;
_15
_15
// add a contructor that assigns our injected context
_15
public BurgerService(DBContext _db) {
_15
db = _db;
_15
}
_15
_15
public List<Burger> GetBurgers() {
_15
// we can then refer to our private var
_15
var items = db.Burgers.ToList();
_15
return items;
_15
}
_15
}

With the above changes, we now have reusable and maintainable injection of our services that can be consumed easily by our controller and service methods. However, if we run this code we will be greeted with an error as our API currently doesn't know how to resolve our dependencies. Unlike dotnet core, where resolution is enabled by default, in Framework, we need to configure a dependency container to assist with resolving our dependencies.

Unity

Unity is an open source dependency injection container originally developed by Microsoft. By adding and configuring it in our project, it will automatically tell our application how to resolve our dependencies.

To do so, we need to tell the application to load Unity, and then provide it with a list of dependencies to resolve.

Install Unity through NuGet and then configure as follows:

Global.asax

_13
public class WebApiApplication : System.Web.HttpApplication
_13
{
_13
protected void Application_Start()
_13
{
_13
AreaRegistration.RegisterAllAreas();
_13
// add unity config to startup
_13
UnityConfig.RegisterComponents();
_13
GlobalConfiguration.Configure(WebApiConfig.Register);
_13
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
_13
RouteConfig.RegisterRoutes(RouteTable.Routes);
_13
BundleConfig.RegisterBundles(BundleTable.Bundles);
_13
}
_13
}

Create a new file (in App_Start works well):

UnityConfig.cs

_12
public static class UnityConfig
_12
{
_12
public static void RegisterComponents()
_12
{
_12
var container = new UnityContainer();
_12
_12
// register our service file, our dbcontext will then be resolved through autowiring
_12
container.RegisterType<IBurgerService, BurgerService>(new HierarchicalLifetimeManager());
_12
_12
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
_12
}
_12
}

Now when we run our Web API, the correct constructors will be resolved, and we will be able to CRUD all the burgers we want.


Andrew McMahon
These are a few of my insignificant productions
by Andrew McMahon.