使用webforms和DI对象实例化的MVP模式

时间:2009-05-15 14:59:08

标签: webforms dependency-injection structuremap ninject

我使用通用存储库模式来保存我的数据。在PageLoad上,我正在创建一个新的Repository(来自IRepository)对象,而在PageUnload上,我处理它。

MasterPage / Page是否应该负责实例化对象以传递给演示者,还是演示者应该负责这个?我更关心测试演示者而不是页面(View),因为它更容易模拟传递给演示者的接口。

示例页面

public partial class _Default : System.Web.UI.Page
{
    private IRepository _repo;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (_repo == null)
            _repo = new Repository();
        ConnectPresenter();
    }

    private void ConnectPresenter()
    {
        _DefaultPresenter presenter = new _DefaultPresenter(_repo);
    }

    private void Page_Unload(object sender, EventArgs e)
    {
        if (_repo != null)
            _repo.Dispose();
    }
}

在这种情况下,诸如StructureMap或Ninject之类的DI框架会有帮助吗?它会负责处理这样的物体吗?

2 个答案:

答案 0 :(得分:6)

Page类和演示者都不应该直接处理任何依赖项的构造或生命周期 - 这应该由容器处理。由于构造函数注入不适用于WebForms,因此您需要将任何所需的依赖项公开为类的属性。例如,您可以将班级更改为:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    public _DefaultPresenter Presenter { get; set; }
}

该页面不需要对存储库的任何引用,因为它将被注入到演示者中。

本答案的其余部分特定于StructureMap - 其他容器的详细信息可能有所不同。

要启用setter注入,您需要告诉StructureMap要填充哪些属性。一种方法是将[SetterProperty]属性应用于属性本身。但是,在类中包含StructureMap详细信息可能会让您觉得有点难以理解。另一种方法是配置StructureMap,以便它知道要注入哪些属性类型。例如:

protected void Application_Start(object sender, EventArgs e)
{
    ObjectFactory.Initialize(x =>
    {
        x.Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.WithDefaultConventions();
        });
        x.ForRequestedType<IRepository>().TheDefaultIsConcreteType<Repository>().CacheBy(InstanceScope.Hybrid);
        x.SetAllProperties(set => set.WithAnyTypeFromNamespaceContainingType<IRepository>());
    });
}

SetAllProperties方法允许您告诉StructureMap如何识别它应该填充的属性。在这种情况下,我告诉StructureMap注入所有演示者(假设它们都在同一名称空间中)。

您仍需要对每个请求执行setter注入。使用StructureMap,您可以使用BuildUp()方法将依赖项注入现有实例。您可以在每个页面或页面基类的Init或Load事件中执行此操作,但同样,这会感觉具有侵入性。要使容器完全不在页面类中,可以使用应用程序的PreRequestHandlerExecute事件(在global.asax或IHttpModule中):

protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
    var application = (HttpApplication)sender;
    var page = application.Context.CurrentHandler as Page;
    if (page == null) return;
    ObjectFactory.BuildUp(page);
}

最后,如果要显式处理IRepository,可以在EndRequest事件中处理:

protected void Application_EndRequest(object sender, EventArgs e)
{
    var disposable = ObjectFactory.GetInstance<IRepository>() as IDisposable;
    if (disposable != null) disposable.Dispose();
}

请注意,这是正常的,因为在初始化时我们告诉StructureMap通过Hybrid缓存IRepository,这意味着“为每个HTTP请求(或者线程,如果不在网站中运行)给我相同的实例”。当您在EndRequest中检索IRepository时,您将收到整个请求中使用的相同的IRepository,您可以将其丢弃。

答案 1 :(得分:2)

是的,值得您调查one of the walkthroughs out there of using DI with ASP.NET

是的,在适当的位置处置每个请求行为对象通常由Container与ASP.NET的集成来管理。

典型的安排是对象创建从Page向内流入Application / Module。通常,您在[Inject]课程上标记属性Page,但这取决于您如何安排三位一体。 Presenter通常可以使用Constructo Injection来声明它需要什么,无论它是测试还是ASP.NET cotext。然后在运行时,DI将满足依赖性。在测试时,你仍然可以使用DI,但在其他情况下,与SUT一起创建一堆Fakes并将它们传递给Presenter可能更自然。

关于测试的triead安排,我发现this MSDN Mag article on using Ninject with xUnit.net by Justin Etheredge非常有用,即使它的目标是ASP.NET MVC。