Special Offer: My C#/.NET Bootcamp Course is out now. Get 10% OFF using the code FRIENDS10.

In this video, you’re going to learn how to build an extensible C# application using Autofac. We take an existing application and refactor it from compile-time dependency definitions to runtime dependency definitions. Do you want to improve the extensibility of your applications? I’ll explain a simple solution.

Content

0:42 Video Overview
1:27 What happened so far in this series?
2:15 Limitation of the current solution
2:53 The concept of defining dependencies at runtime
4:01 Coding Activity: Refactor the sample application
11:10 Project dependencies overview
11:34 What we have learned

This video is part of the Dependency Injection playlist:

Subscribe to my channel, if you do not want to miss a video:

Transcript

The following part of this article contains the full transcript of the video for people who want to search the content or are not able to properly hear.

In this video, you’re going to learn how to build an extensible C# application using Autofac. We take an existing application and refactor it from compile-time dependency definitions to runtime dependency definitions. Do you want to improve the extensibility of your applications? Let’s go!

Introduction

Hi, I’m a Software Engineer with more than ten years experience on the .NET platform. On this channel, you find videos about software development focusing on the .NET platform.

My goal is to help you succeed as a .NET developer. If you want to improve your skills, subscribe to the channel to make sure you catch watch my future content for free.

Overview

This video is the third part of my Dependency Injection video series. If you want to learn the fundamentals of Autofac and Dependency Injection in general, check out the other videos in this playlist.

In this video, we are going to improve the sample application we have built in the last video. Our goal is to make our sample application more extensible.

In the first part of this video, I quickly summarize what we already did in this video series and where this video takes you.

In the second part of this video, we take a look at the limitations of the current solution and discuss how we can potentially improve the extensibility of our application.

In the last part of this video, we open Visual Studio and refactor our sample application to change our application to be extensible after the source code has built.

Earlier in this series

First, let’s review what we have already achieved in this series.

We started in the first part of this series with a simple application with hard-coded dependencies. We changed the classes to receive dependencies as a constructor argument instead of creating instances inside the consumer classes. This step improved the reusability of our classes and their maintainability in general.

In the second part of this series, we introduced Autofac as a dependency injection framework and we took a look at the benefits of using a framework to resolve dependencies.

When we run the application the dependencies get set up in the Program class while we build the Autofac container. Once set-up, we can consume our dependencies using Dependency Injection.

We have a central place to set up our dependencies. This central place makes it easy to manage our dependencies.

Current Limitation

The current limitation is that we still define our code dependencies at compile time. If we want to change how the dependencies get wired-up, we need to change the Program class and rebuild the entire application.

Imagine an application in production. The application not only needs to be rebuilt on the developer machine or build on a continuous integration system but also deployed onto the user machines.

This process can take quite some time, needs bandwidth and depending on how complex the infrastructure is, it can cost a lot of money.

Wouldn’t it be great if we could define the dependencies at runtime instead of compile time?

Define code dependencies at runtime

We want to refactor our sample application to improve its extensibility by defining code dependencies at runtime instead of compile time. It takes us three simple steps to achieve our goal.

  1. We extract our interface, and it’s implementations to separate assemblies. This step allows us to deploy the different implementations as we need them after the project has built. If this does not make any sense right now, don’t worry – I show you exactly how and why it works.
  2. We load the implementation dynamically at runtime using Reflection. We do not know at compile-time which implementation we want to use. Therefore, we need another solution how we load the implementation when our program starts.
  3. We register our dynamically loaded implementation to the Autofac container. Once we have an implementation at hand, we need to register it to the Autofac container. This step remains the same as we set it up in the previous video of this series.

If everything works out, all we need to do to exchange our implementation is to swap a file in the application folder.

Now let’s get our hands dirty and refactor the sample application!

Split code into multiple projects

First of all, we need to create a new class library project for our INotificationService interface. We name the project DependencyInjection.Notification and add it to the solution.

Next, we delete the default class generated by Visual Studio and move the INotificationService interface to our new class library project. We fix the namespace definition, and we’re good to go.

To make our application compile again, we need to add a reference from our application, the DependencyInjection project, to the new DependencyInjection.Notification project.

We need to use our User data class from both projects. Therefore, we need to create another class library that contains the domain types which are used in the NotificationService interface definition as well as in the application.

We create a new class library and name it DependencyInjection.Core. Next, we move the User class from our application to the new Core project and fix its namespace definition.

We can now import the User class namespace into our INotificationService interface definition by adding a project reference to the Core class library. We need to make sure that our User class’s access modifier is set to public. Finally, we can import the DependencyInjection.Core namespace in our interface definition.

Now, to make the application compile again, we need to import both namespaces in our Program class. To import the namespace for the User class, we need to add the Core project as a dependency to the application.

We still have an error on line 21 where we call the UserService. Let’s take a look at the UserService class. We need to add a using statement for the User class as well as the INotificationService interface.

We also need to take a look at our implementation of the INotificationService interface, the ConsoleNotification class. We also need to add a using statement for the interface definition as well as the User class.

Once again, we try to build the application, and we still have a build error. The registration of the ConsoleNotification class to the Autofac container does not build. As a quick fix, we add a using statement once again, but we will come back later and remove the registration in the ProgramModule since getting rid of the registration in the ProgramModule is the primary goal of this video.

We build again, and finally, the build is successful.

We start the application, and the console output tells us that the ConsoleNotification class can be found and it prints the message to the console that the username has been changed to Robert.

We are still not able to exchange the implementation of the INotificationService interface because the ConsoleNotification class is part of our application project and there built into the executable.

Let’s change that by adding another project to the solution. We call it DependencyInjection.ConsoleNotification and move the ConsoleNotification class from the application to its class library and fix the namespace definition. Once again, we need to add using statements and add the required project references.

Our application won’t compile because it cannot find the ConsoleNotification class anymore which we try to register in the ProgramModule class. We fix this by removing the registration from the module.

The application builds but when we try to run it we immediately get a ComponentNotRegistered exception with a message meaning that the Autofac container does not contain a registration for the INotificationService type.

It leads us to the next refactoring step. We need to load and register the implementations for our INotificationService interface dynamically at runtime.

Dynamically load the implementation at runtime

We add a new class to our application project and call it NotificationModule. We want to implement a new Autofac module which loads and registers the available implementation for our IServiceNotification interface at runtime.

We need to extend from Autofac’s Module class and override the Load method. We get our notificationServiceTypes by reading the files within the execution path of the application.

We are only interested in files containing the “DependenyInjection” string and ending with “Notification.dll”. This filter improves the performance of the next step. We use Reflection to load the assembly by providing its name. Next, we load all types that implement the INotificationService and that are of type class.

As the last step in our NotificationModule class, we register each notificationServiceType to the Autofac container. We use the RegisterType method without a generic type parameter because we provide an instance of the Type class instead of the type definition.

Now let’s make sure we add the NotificationModule to the application by registering the Module to the Container in our Program class.

We start our application, and it runs again. Well done!

Adding a post-build script

As a side note. The ConsoleNotification DLL file has to be in the execution path of the application. The application does not have a reference to the ConsoleNotification project. Therefore if we build the application, the ConsoleNotification.dll file will not be copied to the execution folder of the application. We can fix this by adding the following post-build script to the ConsoleNotification project.

Of course, choosing and adding a NotificationService implementation can now be done after the build process, and in a real application this step would be part of the deployment process instead of the build process, but we want to keep things as simple as possible for now.

Add a new implementation

We now want to implement another NotificationService. We add a new project to our solution, and we call it DependencyInjection.FancyNotification. We use the default class, rename it and implement the INotifcationService interface. To make things work, we need to add project references to both the Notification and the Core projects.

We add our implementation, add the after-build script and build the project. We press F5 to launch the application, and we’ll see our fancy notification in the console.

That’s it. We now have an application which can be developed and build without making the decision on which NotificationService implementation should be used. Using the technique we learned in this video, we delay this decision to the deployment process.

We only need to make sure that we have a single implementation assembly in our project folder. Otherwise, the last registration will win and will be used.

Let’s take a look at the project dependencies.

First, we extracted the INotificationService interface to its own Notification project. Next, we extracted the User class to its Core project. To make the implementation exchangeable, we created a new project for the ConsoleNotification. Last but not least, we added another implementation in its own project.

Summary

In this video, we’ve learned how we can set up our code dependencies to be defined at runtime instead of compile-time. That gives us more flexibility for our application by improving its extensibility.

Imagine you want to release an interface to the public for other software developers to contribute implementations to your system. This technique allows you to build a simple plug-in system.

Maybe you want to release your interface to another team on your company instead of the public. That works too. It allows you to have an extension point for your software while you do not need to rebuild your entire application everytime a new feature is added.

Should you do this for every interface in your application? Absolutely not! Take this technique and add it to your toolbox. Use it where you think it helps your application’s extensibility and use compile-time defined dependencies where a lower level of extensibility is enough.

Outro

If you find this approach useful or if you use other techniques to improve the extensibility of your .NET applications, please write it in the comments below.

If you like this video, please hit the like button as it helps the channel grow and allows me to produce more free content in the future. If you want to improve your .NET developer skills, subscribe to the channel, and I see you in the next.

 

Claudio Bernasconi

I'm an enthusiastic Software Engineer with a passion for teaching .NET development on YouTube, writing articles about my journey on my blog, and making people smile.