Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Autofac integration library for WinForm

I am working on a Win form application with Autofac Here we resolve the dependencies as following: As seen in doc

using (var scope = DIConfig.container.BeginLifetimeScope())
            {
                var us = scope.Resolve<IUsersService>();
                usersGrid.DataSource = us.GetUsers();
            }

However in Web MVC project we could resolve all dependencies Eg as in

DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

So that there is no need to resolve the scope each time as in and use the simple code

usersGrid.DataSource = us.GetUsers();

How could we do a similar resolution in Winform so that there is no need of more code in use? How to Resolve dependency in Winforms?Is there a Autofac integration library for WinForm ?

like image 622
Thunder Avatar asked Jan 04 '15 06:01

Thunder


People also ask

What is Autofac library?

¶ Autofac is an addictive IoC container for . NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .

How do I get Autofac containers?

From Visual Studio, you can get it via NuGet. The package name is Autofac. Alternatively, the NuGet package can be downloaded from the GitHub repository (https://github.com/autofac/Autofac/releases).


2 Answers

I've successfully migrated a WinForms application to use Autofac in the near past. I wanted a design which has the following motivations.

  • Find a suitable scope similar to a web request in case of an ASP.NET MVC application for example
  • Be as abstract as possible to avoid the service locator pattern

I came up with choosing a Form to serve as a scope, so for example if I inject a view model or a repository, it should get disposed when the form is closed, etc. This way I can use constructor injection in my Forms just as in Controllers in a web app. For example

public class UserEditForm : Form {

    private reaodnly UserEditViewModel viewModel;

    public UserEditForm(UserEditViewModel viewModel) {
        this.viewModel= viewModel;
    }

    public void Load(int userId) {
        this.viewModel.Load(userId);
    }

    ...
}

public class UserEditViewModel {

    private readonly UserRepository repository;

    public UserEditViewModel(UserRepository repository) {
        this.repository = repository;
    }

    ...
}

To avoid the service locator pattern as much as possible, I've designed an IFormFactory interface which looks as simple as this.

public interface IFormFactory
{
    Form CreateForm(Type formType);
}

I decided to allow accessing this statically thorough the UI layer (but only there), so I've implemented the following static class.

public static class FormFactory
{
    private static IFormFactory factory;

    public static void Use(IFormFactory factory)
    {
        if (FormFactory.factory != null)
        {
            throw new InvalidOperationException(@"Form factory has been already set up.");
        }

        FormFactory.factory = factory;
    }

    public static Form Create(Type formType)
    {
        if (factory == null)
        {
            throw new InvalidOperationException(@"Form factory has not been set up. Call the 'Use' method to inject an IFormFactory instance.");
        }

        return factory.CreateForm(formType);
    }

    public static T Create<T>() where T : Form
    {
        return (T)Create(typeof(T));
    }
}

Now at this point I abstracted the creation process of forms. Note, that until this point I didn't even care about any potantial DI container. Here I already could start refactoring the entire view layer to use this pattern, for example like this.

using (var form = FormFactory.Create<UserEditorForm>()) {
    form.Load(userId);
    form.ShowDialog();
}

And here came Autofac (or any other DI container). I've implemented the IFormFactory using Autofac like this.

public class AutofacFormFactory : IFormFactory
{
    private readonly ILifetimeScope currentScope;

    public AutofacFormFactory(ILifetimeScope currentScope)
    {
        this.currentScope = currentScope;
    }

    public Form CreateForm(Type formType)
    {
        // begin a new lifetime scope for each form instance
        var scope = this.currentScope.BeginLifetimeScope();

        var form = (Form) scope.Resolve(formType);

        form.Disposed += (s, e) =>
        {
            // finish the scope when the form is disposed (closed)
            scope.Dispose();
        };

        return form;
    }
}

Also, don't forget to include the binding in the Autofac configuration.

builder.RegisterType<AutofacFormFactory>().As<IFormFactory>();

Then finally I initialize this all at Program.cs like this.

var builder = new ContainerBuilder();

builder.RegisterModule<MyModule>();

var container = builder.Build();

FormFactory.Use(container.Resolve<IFormFactory>());

Hope it can still be helpful for anyone.

like image 76
Zoltán Tamási Avatar answered Sep 19 '22 12:09

Zoltán Tamási


I think that in general, for a WinForms or other non-request based application, using a specific scope with using is appropriate: when needing operation-specific components.

Divy up the component lifetimes

The first step, is to first identify components that can be resolved for the entire application - trivially this includes "singleton" services, of which a good chunk should be. These components are added to the default Autofac scope builder.

Then each form (eg. in the OnLoad event) uses property injection to load the form/application-wide components that it requires - this can itself be wrapped in a global method, but keeps knowing the Container to one place in the Form to avoid a Service Locator pattern bleed.

(If all the Forms are created via the IoC container then constructor injection can also be used; I've found that using property injection is "sufficient" for WinForms and WebForms.)

Resolve from the injected "OperationScope" component

One of the injected components knows how to create the appropriate operation scopes. In this case the injected property might be the following, where IOperationScope is an ILifetimeScope that handles the "operation UoW", whatever that may be. This scope replaces the pre-request scope as found in MVC/WCF:

public Func<IOperationScope> BeginOperationScope { get; set; }

(The OperationScope should probably be a named scope so that components can be registered to just this named scope, instead of globally.)

Then in the operation itself, we have:

using (var op = BeginOperationScope())
{
    op.Resolve<..>(..);
}

This has a bit of bleed in that there is still a Resolve directly in the WinForm event code - it's not terrible, but still leaves the feeling of not-DI (as it's a very slight SL bleed) and mixing-UI-and-logic.

Variant 1: Create a method-injection-resolver

It is possible to create a simple method-injection-resolver so that the actual code could be called as so:

public void OperationMethod(ISomeOperationService service)
{
   // "method parameter injection"
}

using (var op = BeginOperationScope())
{
   op.ExecuteMethod(OperationMethod);
}

There is no standard form of this method-injection-resolver but it can be written with just a little bit of reflection for a trivial case.

While at first this might appear to be a duplicate method per Event, it actually helps add isolation between the UI and any "Controller" logic established. The UI events then only wrap to the applicable logic methods that have dependencies resolved automatically.

Variant 2: Make the "scope" a Controller for the action

In this case the scope itself might simply be an injected disposable Component with a composite ILifetimeScope that it created, used to inject components, and cleans up itself in Dispose.

using (var op = BeginOperationScope())
{
   op.DoSomething();
}

This case is arguably the most "pure" and it has a nice separation. However, it should still be noted that the using approach is still used and the Controller/scope itself controls the applicable per-Operation Autofac lifetime.

Since the lifetime is still limited to the operation, make sure not to bleed IQueryable or non-forced/lazy IEnumerable objects.

like image 40
user2864740 Avatar answered Sep 19 '22 12:09

user2864740