WPF + Castle Windsor + MVVM: Locator-DataContext

半腔热情 提交于 2019-12-04 18:47:44

You are using the Service Locator pattern, where you register services, pass around a reference to the container, and explicitly call resolve all over you code. If you take a quick tour through the Castle Windsor wiki, they discourage this use of the container.

Generally, you should register all your types (via installers), resolve only one root object (maybe your main view, maybe some sort of startup/MVC controller style code), and let the rest be resolved by the container. The next time you will call the container will almost always be container.Dispose, when your application exits.

See the Windsor wiki page regarding the Three Calls Pattern.

If you find cases where you need to pull from the container during runtime to create specific instances (where you have to pass specific parameters to create that instance), use the Typed Factory Facility instead of directly resolving dependencies.

MVVM with Windsor:

In my first MVVM app I wrote with Windsor (just finished the first version), I simply registered my main view and view model without specifying the lifestyle. This defaulted them to be singletons.

The view took a view model instance as a required dependency (in the constructor), and used it to set the data context. I did this in code-behind because it was non-intrusive and painless.

// In my program I used interfaces for everything.  You don't actually have to...
public interface IMainView
{
    void Show();
}

public class MainView : Window, IMainView
{
    public MainView(IMainViewModel viewModel)
    {
        Initialize();
        this.DataContext = viewModel;
    }
}

public interface IMainViewModel
{
    int SomeProperty { get; set; }
    ICommand ShowSubViewCommand { get; }
    // ...
}

public class MainViewModel : IMainViewModel
{
    public MainViewModel(SomeOtherSubComponent subComponent)
    {
        this.subComponent = subComponent;
        // ...
    }

    // ...
}

Where I had sub-views that I wanted to create multiple instances of, I registered them with a transient life-cycle. Then I created a view factory and view model factory, and used them to get instances of the sub-view and sub-viewmodel from the parent view. I registered an event handler for the view's close event, and called a Release method on the factory classes.

public interface ISubView
{
    void Show();
    event Action OnDismissed;
}

public class SubView : Window, ISubView
{
    public SubView(ISubViewModel viewModel)
    {
        Initialize();
        this.DataContext = viewModel;
        // Would do this in the view model,
        // but it is a pain to get Window.Close to call an ICommand, ala MVVM
        this.OnClose += (s, a) => RaiseDismissed();
    }

    public event Action OnDismissed;

    private void RaiseDismissed()
    {
        if(OnDismissed != null)
            OnDismissed();
    }
}

public interface ISubViewModel
{
    string SomeProperty { get; }
    // ...
}

// Need to create instances on the fly, so using Typed Factory facility.
// The facility implements them, so we don't have to :)
public interface IViewFactory
{
    ISubView GetSubView(ISubViewModel viewModel);
    void Release(ISubView view);
}

public interface IViewModelFactory
{
    ISubViewModel GetSubViewModel();
    void Release(ISubViewModel viewModel);
}

// Editing the earlier class for an example...
public class MainViewModel : IMainViewModel
{
    public MainViewModel(IViewFactory viewFactory, IViewModelFactory viewModelFactory)
    {
        this.viewFactory = viewFactory;
        this.viewModelFactory = viewModelFactory;
        // Todo: Wire up ShowSubViewCommand to call CreateSubView here in ctor
    }

    public ICommand ShowSubViewCommand { get; private set; }

    private void CreateSubView()
    {
        var viewModel = viewModelFactory.GetSubViewModel();
        var view = viewFactory.GetSubView(viewModel);

        view.OnDismissed += () =>
        {
            viewModelFactory.Release(viewModel);
            viewFactory.Release(view);
        };

        view.Show();
    }

    // Other code, private state, etc...
}

At the end of all this, the only calls to the container are:

void App_Startup()
{
    this.container = new WindsorContainer();
    container.Install(Configuration.FromAppConfig());

    var mainView = container.Resolve<IMainView>();
    mainView.Show();
}

public override OnExit()
{
    container.Dispose();
}

The benefit to all this rigmarole is that it is independent of the container (and can be used without a container), it is obvious which dependencies each of my components take, and most of my code doesn't ever have to ask for its dependencies. The dependencies are just handed to each component as it needs it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!