Saturday, January 8, 2022

WinForms Dependency Injection in .NET6

This entry is motivated by the discussion in one of StackOverflow's questions, the How to use dependency injection in WinForms. A correct answer by Reza Aghaei shows how the container can be created and used, however, one of drawbacks of Reza's answer is that the container is used directly, which makes this approach fall under the Service Locator antipattern.
I've decided then to create a follow up and show one of possible correct solutions. This solution is based on the Local Factory pattern.
We'll start with a form factory
public interface IFormFactory
{
	Form1 CreateForm1();
	Form2 CreateForm2();
}

public class FormFactory : IFormFactory
{
	static IFormFactory _provider;

	public static void SetProvider( IFormFactory provider )
	{
		_provider = provider;
	}

	public Form1 CreateForm1()
	{
		return _provider.CreateForm1();
	}

	public Form2 CreateForm2()
	{
		return _provider.CreateForm2();
	}
}
From now on, this factory is the primary client's interface to creating forms. The client code is no longer supposed to just call
var form1 = new Form1();
No, it's forbidden. Instead, the client should always call
var form1 = new FormFactory().CreateForm1();
(and similarily for other forms).
Note that while the factory is implemented, it doesn't do anything on its own! Instead it delegates the creation to a somehow mysterious provider which has to be injected into the factory. The idea behind this is that the provider will be injected, once, in the Composition Root which is a place in the code, close to the startup, and very high in the application stack so that all dependencies can be resolved there. So, the form factory doesn't need to know what provider will be ultimately injected into it.
This approach has a significant advantage - depending on actual requirements, different providers can be injected, for example you could have a DI-based provider (we'll write it in a moment) for an actual application and a stub provider for unit tests.
Anyway, let's have a form with a dependency:
public partial class Form1 : Form
{
	private IHelloWorldService _service;

	public Form1(IHelloWorldService service)
	{
		InitializeComponent();

		this._service = service;
	}
}
This form depends on a service and the service will be provided by the constructor. If the Form1 needs to create another form, Form2, it does it it a way we already discussed:
var form2 = new FormFactory().CreateForm2();
Things become more complicated, though, when a form needs not only dependant services but also, just some free parameters (strings, ints etc.). Normally, you'd have a constructor
public Form2( string something, int somethingElse ) ...
but now you need something more like
public Form2( ISomeService service1, IAnotherService service2, string something, int somethingElse ) ...
This is something we should really take a look into. Look once again, a real-life form possibly needs
  1. some parameters that are resolved by the container
  2. other parameters that should be provided by the form creator (not known by the container!).
How do we handle that?
To have a complete example, let's then modify the form factory
public interface IFormFactory
{
	Form1 CreateForm1();
	Form2 CreateForm2(string something);
}

public class FormFactory : IFormFactory
{
	static IFormFactory _provider;

	public static void SetProvider( IFormFactory provider )
	{
		_provider = provider;
	}

	public Form1 CreateForm1()
	{
		return _provider.CreateForm1();
	}

	public Form2 CreateForm2(string something)
	{
		return _provider.CreateForm2(something);
	}
}
And let's see how forms are defined
public partial class Form1 : Form
{
	private IHelloWorldService _service;

	public Form1(IHelloWorldService service)
	{
		InitializeComponent();

		this._service = service;
	}

	private void button1_Click( object sender, EventArgs e )
	{
		var form2 = new FormFactory().CreateForm2("foo");
		form2.Show();
	}
}

public partial class Form2 : Form
{
	private IHelloWorldService _service;
	private string _something;

	public Form2(IHelloWorldService service, string something)
	{
		InitializeComponent();

		this._service = service;
		this._something = something;

		this.Text = something;
	}
}
Can you see a pattern here?
  1. whenever a form (e.g. Form1) needs only dependand services, it's creation method in the FormFactory is empty (dependencies will be resolved by the container).
  2. whenever a form (e.g. Form2) needs dependand services and other free parameters, it's creation method in the FormFactory contains a list of arguments corresponding to these free parameters (service dependencies will be resolved by the container)
Now finally to the Composition Root. Let's start with the service
public interface IHelloWorldService
{
	string DoWork();
}

public class HelloWorldServiceImpl : IHelloWorldService
{
	public string DoWork()
	{
		return "hello world service::do work";
	}
}
Note that while the interface is supposed to be somewhere down in the stack (to be recognized by everyone), the implementation is free to be provided anywhere (forms don't need reference to the implementation!). Then, follows the starting code where the form factory is finally provided and the container is set up
internal static class Program
{
	[STAThread]
	static void Main()
	{
		var formFactory = CompositionRoot();

		ApplicationConfiguration.Initialize();
		Application.Run(formFactory.CreateForm1());
	}

	static IHostBuilder CreateHostBuilder()
	{
		return Host.CreateDefaultBuilder()
			.ConfigureServices((context, services) => {
				services.AddTransient<IHelloWorldService, HelloWorldServiceImpl>();
				services.AddTransient<Form1>();
				services.AddTransient<Func<string,Form2>>(
					container =>
						something =>
						{
							var helloWorldService = 
                                container.GetRequiredService<IHelloWorldService>();
							return new Form2(helloWorldService, something);
						});
			});
	}

	static IFormFactory CompositionRoot()
	{
		// host
		var hostBuilder = CreateHostBuilder();
		var host = hostBuilder.Build();

		// container
		var serviceProvider = host.Services;

		// form factory
		var formFactory = new FormFactoryImpl(serviceProvider);
		FormFactory.SetProvider(formFactory);

		return formFactory;
	}
}

public class FormFactoryImpl : IFormFactory
{
	private IServiceProvider _serviceProvider;

	public FormFactoryImpl(IServiceProvider serviceProvider)
	{
		this._serviceProvider = serviceProvider;
	}

	public Form1 CreateForm1()
	{
		return _serviceProvider.GetRequiredService<Form1>();
	}

	public Form2 CreateForm2(string something)
	{
		var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>();
		return _form2Factory( something );
	}
}
First note how the container is created with Host.CreateDefaultBuilder, an easy task. Then note how services are registered and how forms are registered among other services.
This is straightforward for forms that don't have any dependencies, it's just
services.AddTransient<Form1>();
However, if a form needs both services and free parameters, it's registered as ... form creation function, a Func of any free parameters that returns actual form. Take a look at this
services.AddTransient<Func<string,Form2>>(
	container =>
		something =>
		{
			var helloWorldService = container.GetRequiredService<IHelloWorldService>();
			return new Form2(helloWorldService, something);
		});
That's clever. We register a form factory function using one of registration mechanisms that itself uses a factory function (yes, a factory that uses another factory, a Factception. Feel free to take a short break if you feel lost here). Our registered function, the Func<string, Form2> has a single parameter, the something (that corresponds to the free parameter of the form constructor) but its other dependencies are resolved ... by the container (which is what we wanted).
This is why the actual form factory needs to pay attention of what it resolves. A simple form is resolved as follows
return _serviceProvider.GetRequiredService<Form1>();
where the other is resolved in two steps. We first resolve the factory function and then use the creation's method parameter to feed it to the function:
var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>();
return _form2Factory( something );
And, that's it. Whenever a form is created, is either
new FormFactory().CreateForm1();
for "simple" forms (with service dependencies only) or just
new FormFactory().CreateForm2("foo");
for forms that need both service dependncies and other free parameters.
Happy coding. A simple excercise for you is to provide a different form factory, a factory that doesn't use a container but composes dependencies directly, for example for unit tests.

6 comments:

Nelson Marro said...

Great Post, help me a lot! Thanks

Ka.Sa. said...

A czy da sie uzyc IoC we wzorcu MVP czy musze pomyslec o MVVM.

Masoud said...

Hi Wiktor
What is Host class, used in the CreateHostBuilder() method?

KBJensen said...

Supper stuff. Works like a charm!
Thank you for sharing.
\Klaus

chris riley said...

Where does the SetProvider get called?

Thanks

Wiktor Zychla said...

In general, the Composition Root is called from the Main method of your app.