WinForms中基于MVP模式与Lamar依赖注入的Presenter创建时机与方法
C# WinForms MVP+Lamar DI 实践问题解析
背景概述
我在C# Windows Forms应用中采用Model-View-Presenter(MVP)观察式Presenter风格,结合Lamar依赖注入(DI)框架开发,解决方案分为Infrastructure、Domain、Presentation三个项目:
- Presentation项目的窗体和用户控件按MVP模式拆分,视图采用无参构造函数以支持设计时拖拽控件
- 所有业务逻辑由Presenter处理,Presenter构造函数需传入Domain项目接口及视图接口,完全解除视图对Presenter的依赖
Program.cs 容器注册代码
var container = new Container(x => { x.AddSingleton<IInterfaceFromDomainProject, ClassFromDomainProject>(); x.AddSingleton<IMainView, MainView>(); x.AddSingleton<IMainPresenter, MainPresenter>(); x.AddSingleton<ISubView, SubView>(); x.AddSingleton<ISubPresenter, SubPresenter>(); }); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var mainView = new MainView(); Application.Run(mainView);
主视图代码示例
public interface IMainView { event EventHandler MyCustomEvent; void ShowMessage(); } public partial class MainView : Form, IMainView { public event EventHandler MyCustomEvent; public MainView() { InitializeComponent(); } private void button_Click(object sender, EventArgs e) { MyCustomEvent.Invoke(sender, EventArgs.Empty); } public void ShowMessage() { MessageBox.Show("Hello!"); } }
主Presenter代码示例
public interface IMainPresenter { void ShowMessageHandler(object sender, EventArgs e); void ShowData(); } public class MainPresenter: IMainPresenter { private readonly IMainView _view; private readonly IInterfaceFromDomainProject _foo; public MainPresenter(IMainView view, IInterfaceFromDomainProject foo) { _view = view; _foo = foo; _view.MyCustomEvent += ShowMessageHandler; } public void ShowMessageHandler(object sender, EventArgs e) { _view.ShowMessage(); } public void ShowData() { // 调用_foo获取数据并展示到视图 } }
核心问题
- 基于当前MVP与DI实现,应如何及何时创建Presenter对象?
- 是否需要在Program.cs中为所有Presenter手动获取依赖实例创建?
- 对DI或MVP模式是否存在理解偏差?
- 补充:通过容器获取依赖创建Presenter时,
MyCustomEvent.Invoke(sender, EventArgs.Empty)处抛出NullReferenceException;仅直接传入实例化视图能正常运行;不知如何在视图构造函数中创建Presenter并传入Domain接口依赖。
解决方案
1. Presenter的正确创建时机与方式
不需要手动创建Presenter,应完全交由Lamar容器管理:
- 不要直接
new MainView(),而是从容器解析Presenter或视图,容器会自动完成依赖注入与事件绑定 - 修改Program.cs的启动逻辑,两种可选实现:
方式1:解析Presenter关联视图
var container = new Container(x => { x.AddSingleton<IInterfaceFromDomainProject, ClassFromDomainProject>(); x.AddSingleton<IMainView, MainView>(); x.AddSingleton<IMainPresenter, MainPresenter>(); x.AddSingleton<ISubView, SubView>(); x.AddSingleton<ISubPresenter, SubPresenter>(); }); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 从容器解析Presenter,容器自动注入View与Domain依赖并完成事件绑定 var mainPresenter = container.GetInstance<IMainPresenter>(); // 获取Presenter关联的View(可在IMainPresenter中添加View属性优化此逻辑) var mainView = (IMainView)typeof(MainPresenter).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(mainPresenter); Application.Run((Form)mainView);
方式2:用Lamar初始化器自动绑定
var container = new Container(x => { x.AddSingleton<IInterfaceFromDomainProject, ClassFromDomainProject>(); x.AddSingleton<IMainView, MainView>(); x.AddSingleton<IMainPresenter, MainPresenter>(); x.AddSingleton<ISubView, SubView>(); x.AddSingleton<ISubPresenter, SubPresenter>(); // 解析View时自动初始化对应的Presenter,完成事件绑定 x.For<IMainView>().OnCreation((context, view) => { context.GetInstance<IMainPresenter>(); }); }); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 直接从容器获取View,初始化器自动创建并绑定Presenter var mainView = container.GetInstance<IMainView>(); Application.Run((Form)mainView);
2. 空引用异常的原因与解决
- 异常原因:手动
new MainView()时,对应的Presenter未被实例化,MyCustomEvent无订阅者,触发时为null - 解决:通过容器解析View或Presenter,确保Presenter在View使用前完成实例化与事件订阅
3. MVP与DI的理解修正
- 你的观察式MVP设计是正确的:View不依赖Presenter,仅暴露事件与展示接口,Presenter处理业务逻辑并订阅View事件
- DI的核心是容器管理依赖生命周期与注入,无需手动创建Presenter,让容器自动处理所有依赖的创建与绑定
- 不要在View构造函数中创建Presenter,这会破坏View与Presenter的解耦设计,违背MVP的核心原则
4. 子视图/子Presenter的处理
对于SubView和SubPresenter,遵循相同逻辑:
- 确保容器已注册对应的接口与实现
- 在主Presenter中需要使用子视图时,从容器解析
ISubPresenter或ISubView,容器会自动注入依赖并完成事件绑定
内容的提问来源于stack exchange,提问作者jrn6270
相关产品推荐
相关产品推荐

