Dependency Injection using Generic HostBuilder in .NET Core Windows Form
In this post, we shall learn and attempt to perform Dependency Injection in .NET Core Windows Form using Generic HostBuilder.
We already learned the one approach of DI using Service collection and ServiceProvider for Windows forms app which works perfectly fine.
Until now and still many implementation-prefer and rely on service collection classes for registering the dependency and using it through DI container.
Today in this post we will be using Generic HostBuilder to create DI Container and inject the required dependency within the Windows Forms app.
Today in this article, we will cover below aspects,
Getting started
Here I am using a Forms/Windows .NET Core 3.1 application.
Creating Generic HostBuilder
Generic HostBuilder was introduced in .NET Core 2.1 and designed to be used for both Non-HTTP and HTTP workloads.
The HostBuilder class is available from the following namespace,
using Microsoft.Extensions.Hosting;
HostBuilder implements the IHostBuilder interface.
Please install the NuGet package from Nuget Package manager or PMC,
PM> Install-Package Microsoft.Extensions.Hosting -Version 3.1.1
Please create Generic HosBuilder and register the dependencies that need to inject. These changes can be done in the Main() method.
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<Form1>();
services.AddLogging(configure => configure.AddConsole());
services.AddScoped<IBusinessLayer, BusinessLayer>();
services.AddScoped<IDataAccessLayer, CDataAccessLayer>();
})
Initialize the Host
Initialize the Host with all dependencies,
var host = builder.Build();
Or
By using generic host builder CreateDefaultBuilder
host = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
}).Build();
Configure IoC Container- Dependency Injection in .NET Core Windows Form
We shall be injecting Logger and BusinessLayer objects for more precise results. Please see below the complete code which we need to put in in Main() method application,
Here we are assuming Form1 as the master Form that will be an entry point for all other forms and their action.
However, you can add an additional layer separating actual UI components if needed.
All the services registered using HostBuilder are now available to use as per need. The lifetime management of those services will be taken care of by HostBuilder and we need not have disposed off them.
Form class for your references as below,
Let’s execute the application and try a button click action,
Note: Above console logs were intentionally made it to the console view so that events can be captured.
If interested to know on one more approach, please see below post,
Can we make the above code better?
Please let me know if you have any better suggestions for performing DI in the Desktop application.
Please sound off your comments below.
Summary
Today we learned and attempted one more approach for leveraging the Dependency Injection (DI) in newly supported Windows forms applications in the .NET Core ecosystem. We learned that using Hostbuilder we can easily leverage DI in Desktop or Forms application and can create highly maintainable and extensible Win Form or Desktop applications.
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
Hello, I did the same process, but when I need to open another Form I can open it once, the second time it sends an exception “Cannot access a discarded object”
In this case where I have several forms and they receive dependency injection in their constructors, how can I do it? I believe that this behavior is because at the time I hurt the Show () and left it makes the Disposable () and destroys my instance.
public partial class MainForm : Form
{
private readonly IProdutoRepository _produtoRepository;
private readonly FormProduto _formProduto;
public MainForm(IProdutoRepository produtoRepository,
FormProduto formProduto)
{
InitializeComponent();
_produtoRepository = produtoRepository;
_formProduto = formProduto;
}
private void btnProduto_Click(object sender, EventArgs e)
{
_formProduto.Show();
}
}
Hi Leonardo- Thanks for your query. It seems the Form object you are creating is getting garbage collected. As per my understanding, In such scenarios see if you can pass the right lifetime instance of your form object. You could try passing singleton or transient as feasible in your scenario. Please let me know if that helps in resolving your issue.
Hi, I changed lifecycle for two types, but Forms implement IDisposable and its show my form just one time, do you have any idea to fix this?
As per my understanding DI of unmangaed resources like Windows handles Form wont be a good idea. It might make sense to inject other composite types and services but not unmanged resources which holds Windows handle , GDI object etc. as they alredy implment IDisposable and well optimized to work that way.
That means Garbage collector will take care of those resources using Forms Dispose overriden method(default implementation).
I see using Dipsoable here might go against and violate IoC or ISP principles for such objetcs. And any change to this pattern might require some refactoring.
I will be happy to learn if you have identified any other better approach. Considering the above limiation this is how you can fix the issue.
if (_formProduto == null || _formProduto .IsDisposed)
{
_formProduto = new FormProduto();
}
_formProduto .Show();
Would you then handle Form instances as normal and the other services would be injected?
See that I set up a scenario where I have a generic repository, so I had to inject the Form builder to be able to save, edit, delete etc … but I’ve been getting a message, now I don’t know if it has anything to do with it, I’ve tried to put it as AsNoTracking but it did not work.
private readonly ISistemaRepository _sistemaRepository;
private readonly IEmpresaRepository _empresaRepository;
public SelecionarEmpresa(ISistemaRepository sistemaRepository,
IEmpresaRepository empresaRepository)
{
_sistemaRepository = sistemaRepository;
_empresaRepository = empresaRepository;
InitializeComponent();
}
//Edit
sistemaSelecao.EmpresaId = Guid.Parse(id.ToString());
_sistemaRepository.Update(sistemaSelecao);
//Message exception
System.InvalidOperationException: ‘The instance of entity type ‘Sistema’ cannot be tracked because another instance with the key value ‘{Id: 1ca5875d-93e3-45b1-8a21-acf0fa8f1fd2}’ is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.’
Hey Leonardo- Based on the code you shared my input will be – Please make sure to use your services Ex. Repository etc. with proper lifetime scoped vs transient vs singleton if using IoC. That means If your Repository is Scoped then other instances in your repository should also be scoped or transient minimum.
Great couple of posts!
just one question, which of both methods do you consider it’s a ‘best practice’?
Thanks
Hey Victor, Thanks for the great feedback.
I would go with Generic HostBuilder, as it provides better control and the ability to inject services as required. If you create your Host using CreateDefaultHostBuilder it loads configuration as well.