WinForms MVC与依赖注入

Moo*_*ght 8 c# model-view-controller dependency-injection ninject winforms

我正在从头开始重写WinForms应用程序(它必须是WinForms,因为我想使用WPF和MVVM).这样做我选择使用MVC模式并尽可能使用依赖注入(DI)来提高可测试性,可维护性等.

我遇到的问题是使用MVC和DI.使用baisic MVC模式,控制器必须能够访问视图,并且视图必须能够访问控制器(有关WinForms示例,请参阅此处); 这导致使用Ctor-Injection时的循环引用,这是我的问题的关键.首先请考虑我的代码

Program.cs(WinForms应用程序的主要入口点):

static class Program
{
    [STAThread]
    static void Main()
    {
        FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath());
        Log.LogHandler = fileLogHandler;
        Log.Trace("Program.Main(): Logging initialized");

        CompositionRoot.Initialize(new DependencyModule());
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(CompositionRoot.Resolve<ApplicationShellView>());
    }
}
Run Code Online (Sandbox Code Playgroud)

DependencyModule.cs

public class DependencyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IApplicationShellView>().To<ApplicationShellView>();

        Bind<IDocumentController>().To<SpreadsheetController>();
        Bind<ISpreadsheetView>().To<SpreadsheetView>();
    }
}
Run Code Online (Sandbox Code Playgroud)

CompositionRoot.cs

public class CompositionRoot
{
    private static IKernel ninjectKernel;

    public static void Initialize(INinjectModule module)
    {
        ninjectKernel = new StandardKernel(module);
    }

    public static T Resolve<T>()
    {
        return ninjectKernel.Get<T>();
    }

    public static IEnumerable<T> ResolveAll<T>()
    {
        return ninjectKernel.GetAll<T>();
    }
}
Run Code Online (Sandbox Code Playgroud)

ApplicationShellView.cs(应用程序的主要形式)

public partial class ApplicationShellView : C1RibbonForm, IApplicationShellView
{
    private ApplicationShellController controller; 

    public ApplicationShellView()
    {
        this.controller = new ApplicationShellController(this);
        InitializeComponent();
        InitializeView();
    }

    public void InitializeView()
    {
        dockPanel.Extender.FloatWindowFactory = new CustomFloatWindowFactory();
        dockPanel.Theme = vS2012LightTheme;
    }

    private void ribbonButtonTest_Click(object sender, EventArgs e)
    {
        controller.OpenNewSpreadsheet();
    }

    public DockPanel DockPanel
    {
        get { return dockPanel; }
    }
}
Run Code Online (Sandbox Code Playgroud)

哪里:

public interface IApplicationShellView
{
    void InitializeView();

    DockPanel DockPanel { get; }
}
Run Code Online (Sandbox Code Playgroud)

ApplicationShellController.cs

public class ApplicationShellController
{
    private IApplicationShellView shellView;

    [Inject]
    public ApplicationShellController(IApplicationShellView view)
    {
        this.shellView = view;
    }

    public void OpenNewSpreadsheet(DockState dockState = DockState.Document)
    {
        SpreadsheetController controller = (SpreadsheetController)GetDocumentController("new.xlsx");
        SpreadsheetView view = (SpreadsheetView)controller.New("new.xlsx");
        view.Show(shellView.DockPanel, dockState);
    }

    private IDocumentController GetDocumentController(string path)
    {
        return return CompositionRoot.ResolveAll<IDocumentController>()
            .SingleOrDefault(provider => provider.Handles(path));
    }

    public IApplicationShellView ShellView { get { return shellView; } }
}
Run Code Online (Sandbox Code Playgroud)

SpreadsheetController.cs

public class SpreadsheetController : IDocumentController 
{
    private ISpreadsheetView view;

    public SpreadsheetController(ISpreadsheetView view)
    {
        this.view = view;
        this.view.SetController(this);
    }

    public bool Handles(string path)
    {
        string extension = Path.GetExtension(path);
        if (!String.IsNullOrEmpty(extension))
        {
            if (FileTypes.Any(ft => ft.FileExtension.CompareNoCase(extension)))
                return true;
        }
        return false;
    }

    public void SetViewActive(bool isActive)
    {
        ((SpreadsheetView)view).ShowIcon = isActive;
    }

    public IDocumentView New(string fileName)
    {
        // Opens a new file correctly.
    }

    public IDocumentView Open(string path)
    {
        // Opens an Excel file correctly.
    }

    public IEnumerable<DocumentFileType> FileTypes
    {
        get
        {
            return new List<DocumentFileType>()
            {
                new DocumentFileType("CSV",  ".csv" ),
                new DocumentFileType("Excel", ".xls"),
                new DocumentFileType("Excel10", ".xlsx")
            };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

实现的接口是:

public interface IDocumentController
{
    bool Handles(string path);

    void SetViewActive(bool isActive);

    IDocumentView New(string fileName);

    IDocumentView Open(string path);

    IEnumerable<DocumentFileType> FileTypes { get; }
}
Run Code Online (Sandbox Code Playgroud)

现在与该控制器关联的视图是:

public partial class SpreadsheetView : DockContent, ISpreadsheetView
{
    private IDocumentController controller;

    public SpreadsheetView()
    {
        InitializeComponent();
    }

    private void SpreadsheetView_Activated(object sender, EventArgs e)
    {
        controller.SetViewActive(true);
    }

    private void SpreadsheetView_Deactivate(object sender, EventArgs e)
    {
        controller.SetViewActive(false);
    }

    public void SetController(IDocumentController controller)
    {
        this.controller = controller;
        Log.Trace("SpreadsheetView.SetController(): Controller set successfully");
    }

    public string DisplayName
    {
        get { return Text; }
        set { Text = value; }
    }

    public WorkbookView WorkbookView
    {
        get { return workbookView; }
        set { workbookView = value; }
    }

    public bool StatusBarVisible
    {
        get { return statusStrip.Visible; }
        set { statusStrip.Visible = value; }
    }

    public string StatusMessage
    {
        get { return statusLabelMessage.Text; }
        set { statusLabelMessage.Text = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

视图界面是:

public interface ISpreadsheetView : IDocumentView
{
    WorkbookView WorkbookView { get; set; } 
}
Run Code Online (Sandbox Code Playgroud)

和:

public interface IDocumentView
{
    void SetController(IDocumentController controller);

    string DisplayName { get; set; }

    bool StatusBarVisible { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我是DI和Ninject的新手,所以我有两个问题:

  1. 我怎么能阻止自己使用this.view.SetController(this);,在SpreadsheetController这里感觉我应该使用IoC容器,但使用纯Ctor-Injection导致循环引用和a StackOverflowException.这可以用纯DI完成吗?

因为我没有像WPF那样的绑定框架(或者ASP.NET具有隐式链接视图和控制器的能力),所以我必须明确地将视图和控制器相互暴露.这不是"感觉"正确,我认为这应该是可能的Ninject IoC容器,但我没有经验来确定如何做到这一点(如果可以).

  1. 我在这里使用Ninject/DI是否正确.我使用我CompositionRoot和方法的方式GetDocumentController(string path)感觉就像服务定位器反模式,我怎么能做到这一点?

目前这段代码工作得很好,但我想说得对.非常感谢你花时间陪伴.

Chr*_*igl 5

我正在开发一个具有类似架构的项目。

我猜您的主要问题是您的视图的事件处理程序直接调用控制器。例如:

private void ribbonButtonTest_Click(object sender, EventArgs e)
{
    controller.OpenNewSpreadsheet();
}
Run Code Online (Sandbox Code Playgroud)

尽量避免这种情况。让您的控制器对象成为您应用程序的主人。让视图和模型“盲听”。

当您的视图遇到用户操作时,只需引发另一个事件。让控制器负责注册到这个事件并处理它。你的观点将是这样的:

public event EventHandler<EventArgs> RibbonButtonTestClicked ;

protected virtual void ribbonButtonTest_Click(object sender, EventArgs e)
{
    var handler = RibbonButtonTestClicked;
    if (handler != null) handler(this, EventArgs.Empty);
}
Run Code Online (Sandbox Code Playgroud)

如果这样做,您应该能够摆脱视图中的所有控制器引用。您的控制器构造函数将如下所示:

[Inject]
public ApplicationShellController(IApplicationShellView view)
{
    this.shellView = view;
    this.shellView.RibbonButtonTestClicked += this.RibbonButtonTestClicked;
}
Run Code Online (Sandbox Code Playgroud)

由于您无法再从视图中解析对象树,请向控制器添加一个方法“GetView()”并更改您的 Program.Main() 方法:

CompositionRoot.Initialize(new DependencyModule());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var appCtrl = CompositionRoot.Resolve<ApplicationShellController>()
Application.Run(appCtrl.GetView());
Run Code Online (Sandbox Code Playgroud)