Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVP pattern using webforms and DI object instantiation

I am using the generic repository pattern to persist my data. On the PageLoad, I am creating a new Repository (from IRepository) object, and on PageUnload, I dispose of it.

Should the MasterPage/Page be in charge of instantiating the objects to pass to the presenter or should the presenter be in charge of this? I'm more concerned with testing the presenter than the page (View) since it's easier to mock the interfaces passed to the presenter.

Example 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();
    }
}

Would a DI Framework such as StructureMap or Ninject help in this case? Would it be in charge of disposing objects such as this?

like image 370
Steve Wright Avatar asked May 15 '09 14:05

Steve Wright


1 Answers

Neither the Page class nor the presenters should have to deal directly with managing the construction or lifecycle of any of its dependencies - that should all be handled by your container. Since constructor injection does not work with WebForms, you will need to expose any needed dependencies as properties on the class. For example, you could change your class to:

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

    public _DefaultPresenter Presenter { get; set; }
}

The page should not need any reference to the repository, as it will be injected into the presenter.

The rest of this answer is specific to StructureMap - details may differ for other containers.

To enable setter injection, you need to tell StructureMap which properties to populate. One way is to apply the [SetterProperty] attribute to the property itself. However, this can feel a bit invasive to have StructureMap details within your classes. Another way is to configure StructureMap so that it knows which property types to inject. For example:

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>());
    });
}

The SetAllProperties method allows you to tell StructureMap how to recognize the properties it should populate. In this case, I'm telling StructureMap to inject all presenters (assuming they are all in the same namespace).

You still need to perform the setter injection on each request. With StructureMap, you use the BuildUp() method to inject dependencies into an existing instance. You could do it in the Init or Load events of each page or a page base class, but again, that feels invasive. To keep the container out of your page classes completely, you can use the PreRequestHandlerExecute event of the application (in global.asax or an 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);
}

Finally, if you want to explicitly Dispose of your IRepository, you could handle that in the EndRequest event:

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

Note that this works properly because in the initialization we told StructureMap to cache IRepository by Hybrid, which means "give me the same instance for each HTTP Request (or thread, if not running within a website)". When you retrieve the IRepository in EndRequest, you will receive the same one used throughout the request, and you can dispose it.

like image 186
Joshua Flanagan Avatar answered Sep 21 '22 10:09

Joshua Flanagan