Wire-Up View-Presenter Pattern Like Web Client Software Factory - Castle Windsor for Dependency Injection
by David Hayden
The Web Client Software Factory has a pretty cool application block called the Composite Web UI Application Block ( CWAB ) that does a number of things including using ObjectBuilder for dependency injection. One of the really cool features I like is the automatic injection of the presenter into the view class. The view class in this instance is the ASP.NET Page / Code-Behind.
public partial class News : Page, INewsView
{
private NewsPresenter _presenter;
[CreateNew]
public NewsPresenter Presenter
{
get { return _presenter; }
set { _presenter = value; }
}
public IList NewsHeadlines
{
set
{
NewsList.DataSource = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
}
ObjectBuilder is responsible for injecting the Presenter into the View and knows to do this because of the [CreateNew] Attribute on the property. It injects the dependency right before the Page LifeCycle starts in Application_PreRequestHandlerExecute, and then cleans up during Application_PreRequestHandlerExecute which runs right after the Page LifeCycle
Last night before going to bed I could not resist writing my own code to wire up the View and Presenter as well as replacing ObjectBuilder with Castle Windsor for some dependency injection functionality. It turns out it is really simple although I do have some questions at the end of the post :)
First thing I needed to do was create a CreateNewAttribute that will signal the need for Dependency Injection:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CreateNewAttribute : Attribute {}
I then just need to add the Attribute on a View Class as mentioned above:
public partial class News : Page, INewsView
{
private NewsPresenter _presenter;
[CreateNew]
public NewsPresenter Presenter
{
get { return _presenter; }
set { _presenter = value; }
}
public IList NewsHeadlines
{
set
{
NewsList.DataSource = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
}
When the Presenter is injected into the View we will also probably want the View injected into the Presenter. I am using Convention over Configuration with the Presenter Class. If the Presenter Class wants the View injected into it, it needs to declare a View Property ( no attributes necessary ).
public class NewsPresenter
{
private NewsController _controller;
public INewsView _view;
public INewsView View
{
set { _view = value; }
}
public NewsPresenter(NewsController controller)
{
_controller = controller;
}
public void OnViewInitialized()
{
_view.NewsHeadlines = _controller.GetLatestNewsHeadlines();
}
public void OnViewLoaded()
{
}
}
Now I just need to create a custom HttpApplication Class ( or HttpModule ) that intercepts Application_PreRequestHandlerExecute and Application_PreRequestHandlerExecute to properly wire up the View and Presenter as well as use Windsor for help with other Dependency Injection needs.
During Application_PreRequestHandlerExecute I am looking for [CreateNew] Attributes on View Properties and then injecting the proper instances into it. I am focusing on injecting just the Presenter, but the code is general enough to inject any type that you have registered with Windsor.
While in there I am also looking to see if the Presenter wants the view injected into it ( looking for a View Property on the Presenter ) and doing that as well.
Note this code is raw and needs some additional work but does the trick for our simple prototype which is all I had time for last night:
public class Global : System.Web.HttpApplication
{
protected virtual void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
this.InnerPreRequestHandlerExecute(context);
}
private void InnerPreRequestHandlerExecute(HttpContext context)
{
Page view = context.Handler as Page;
if (view != null)
{
PropertyInfo[] viewProperties = view.GetType().GetProperties();
foreach (PropertyInfo viewProperty in viewProperties)
{
// Look for Properties with [CreateNew] Attribute
object[] attributes = viewProperty.GetCustomAttributes(typeof (CreateNewAttribute), true);
if (attributes != null && attributes.Length > 0)
{
// Get Presenter from Windsor
object presenter = IoC.Resolve(viewProperty.PropertyType);
// Inject Presenter Into View
viewProperty.SetValue(view, presenter, null);
// Get Presenter Properties
PropertyInfo[] presenterProperties = presenter.GetType().GetProperties();
// Look for View Property on Presenter
// If Found, Inject View into Presenter.
// Convention over Configuration
foreach (PropertyInfo presenterProperty in presenterProperties)
{
if (presenterProperty.Name.Equals("View"))
presenterProperty.SetValue(presenter, view, null);
}
}
}
}
}
protected virtual void Application_PostRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
this.InnerPostRequestHandlerExecute(context);
}
private void InnerPostRequestHandlerExecute(HttpContext context)
{
// Not Sure What To Do Here if Anything.
// Does anything need to be cleaned up????
}
}
The IoC class is just a wrapper around Windsor:
public static class IoC
{
private static IWindsorContainer _container =
new WindsorContainer(new XmlInterpreter("Windsor.config"));
public static T Resolve<T>()
{
return _container.Resolve<T>();
}
public static object Resolve(Type service)
{
return _container.Resolve(service);
}
}
and my Windsor Configuration looks as follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="NewsPresenter" type="Core.Views.NewsPresenter, Core" lifestyle="transient" />
<component id="NewsController" type="Core.NewsController, Core" lifestyle="transient" />
<component id="NewsService" service="Core.INewsService, Core" type="Core.SqlNewsService, Core" lifestyle="singleton" />
</components>
</configuration>
Windsor is coming into play during the creation of the Presenter Class. The Presenter needs to be injected with an Application Controller class and in most cases the Application Controller Class will have dependencies as well. Windsor takes care of all that. I don't believe Windsor has a BuildUp Method similar to ObjectBuilder that will inject dependencies on an existing class, so I wired the View and Presenter Myself.
Anyway, I really like the idea of having the View and Presenter Classes wired up for me automagically like done in the Composite Web UI Application Block and in the above code. Actually, I like the way MonoRail does it, but this is certainly a decent alternative when I need to work with webforms.
Questions and Thoughts
In WCSF, ObjectBuilder does a TearDown during Application_PostRequestHandlerExecute, but I am not sure what really needs to be cleaned up. Does anyone have any thoughts on what, if anything, should be done during Application_PostRequestHandlerExecute?
Also, does anyone know of any other / better ways to do this and any open source projects on the Internet that do this with webforms? Any enhancements to the code above are also of interest :)
Source: David Hayden
Posted
04-01-2007 11:09 AM
by
David Hayden