6

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

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

示例页面

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 框架会有所帮助吗?它会负责处理此类物品吗?

4

2 回答 2

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 时,您将收到在整个请求中使用的同一个,您可以将其丢弃。

于 2009-11-07T17:54:58.023 回答
2

是的,非常值得您研究将 DI 与 ASP.NET 结合使用的演练之一

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

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

关于测试的 triead 安排,我发现Justin Etheredge 的这篇 MSDN Mag 文章中关于将 Ninject 与 xUnit.net 结合使用非常有用,尽管它针对的是 ASP.NET MVC。

于 2009-11-05T16:25:16.563 回答