简体   繁体   中英

Using Castle.Windsor with Windows Forms Applications

Up until this point, I have been learning IoC/DI with Castle.Windsor using ASP.NET MVC, but I have a side project that is being done in Windows Forms, and I was wondering if there is an effective way to use it for that.

My problem is in the creation of forms, services, etc. In ASP.NET MVC, there is a sort of 'Activator' that does this under the hood, but this isn't the case in Windows Forms. I have to create a new Form like var form = new fclsMain(); , so a Form like ..

class fclsMain : System.Windows.Forms.Form
{
 private readonly ISomeRepository<SomeClass> someRepository;
 fclsMain(ISomeRepository<SomeClass> someRepository)
 {
  this.someRepository = someRepository;
 }
}

Falls kind of short. I would basically have to do ...

var form = new fclsMain(IoC.Resolve<ISomeRepository<SomeClass>);

Which as I have had pointed out in at least three of my questions isn't smart, because it's supposedly not the 'correct' usage of IoC.

So how do I work with Castle.Windsor and Windows Forms? Is there some way to design a Form Activator or something? I'm really lost, if I can't make a static IoC container that I can resolve from, what can I do?

Here you are doing something that are not very "Dependency Injection"...

var form = new fclsMain(IoC.Resolve<ISomeRepository<SomeClass>);

The "new" is the problem... You have to call

var form = IoC.Resolve<fcls>();

the form of type fcls must be correctly configured via Fluent Registration API o

You don't "have to" new-up a form, as you've said. I use WinForms and never call "new FormName()". It's always a dependency itself. Otherwise I'd have to stuff the constructor full of service locator calls.

I might use a ServiceLocator (as in another answer) BUT only at the very top level. For example I have a Command pattern implemented to intercept toolbar buttons. Looks something like this:

public void Handle(string commandName)
{
    var command = IoC.Resolve<ICommand>(RegisteredCommands[commandName]);
    command.Execute();
}

Then, in a simplified case, this is the kind of code written everywhere else:

public class ShowOptionsCommand : Command, ICommand
{
    private readonly IOptionsView _optionsView;

    public ShowOptionsCommand(IOptionsView optionsView)
    {
        _optionsView = optionsView;
    }

    public void Execute()
    {
        _optionsView.Show();
    }
}

Yes, I use a "service locator" but you will hardly ever see it. That's important to me, because having service locator calls all throughout the code (eg in every class) defeats some of the point of using dependency inversion of control & needs extra work to be testable etc

In order to use the same Castle container throughout your entire application, create a static class like:

public static class CastleContainer {
    private static IWindsorContainer container;

    public static IWindsorContainer Instance {
        get {
            if (container == null) {
                container = new WindsorContainer();
            }
            return container;
        }
        // exposing a setter alleviates some common component testing problems
        set { container = value; }
    }

    // shortcut to make your life easier :)
    public static T Resolve<T>() {
        return Instance.Resolve<T>();
    }

    public static void Dispose() {
        if (container != null) 
            container.Dispose();
        container = null;
    }
}

Then register/install all your components in the Main() method. You can also hook into the application shutdown event to call Dispose() (although this isn't critical).

Castle actually uses a Windows Forms app in their quick-start guide .

Edit:

The pattern I showed above is a variant of the service locator, which some people refer to as an anti-pattern. It has a bad reputation because, among other reasons, it liters your code base with references to Windsor. Ideally, you should only have a single call to container.Resolve<...>() to create your root form. All other services & forms are injected via constructors.

Realistically, you'll probably need a few more calls to Resolve, especially if you don't want to load every single corner of the application at startup. In the web world, the best practice is to hand off the container to the web framework. In the Windows Forms world you'll need to implement your own service locator, like above. (Yes, handing the container to the ASP.NET MVC framework is still a service locator pattern).

I've edited the above code example so that the static container is injectable; no resources are tied up in a static context. If you do end up creating your own service locator, you might also want to create a test utility like this one to make testing easier.

public static class TestUtilities 
{
    public static IContainer CreateContainer(Action<IContainer> extraConfig = null) 
    {
        var container = new WindsorContainer();
        // 1. Setup common mocks to override prod configuration
        // 2. Setup specific mocks, when provided
        if (extraConfig != null)
            extraConfig(container);
        // 3. Configure container with production installers
        CastleContainer.Instance = container;
        return container;
    }
}

This makes a shortcut for creating a new container that looks a lot like the production version, but with some services replaced with mocks. Some example tests might look like:

[Test]
public void SubComponentWorksGreat() 
{
    using (var container = TestUtilities.CreateContainer())
    {
        var subComponent = container.Resolve<SubComponent>();
        // test it...
    }
}

[Test]
public void SubComponentWorksGreatWithMocks() 
{
    var repoMock = new Mock<IRepository>();
    using (var container = TestUtilities.CreateContainer(c => 
            c.Register(Component.For<IRepository>().Instance(repoMock.Object))))
    {
        var subComponent = container.Resolve<SubComponent>();
        // test it with all IRepository instances mocked...
    }
}

One last note. Creating a full container for every test can get expensive. Another option is to create the full container but only using nested containers for the actual tests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM