In this post I’m going to move on and extend the post I wrote about the MVP and the Supervising Controller pattern.
When I create my ASP.Net apps, I often remove the code that will navigate to another page or save data into state into separated classes. In that case I make sure a separate class will instead handle the navigation and the state of my ASP.Net application. For example if we have a button on my page that should Save a News and then navigate to another page, we should probably write something like this:
protected void SaveButton_OnClick(object sender, EventArgs args)
{
//... Get the news info from the view
NewsRepository.Save(news);
Response.Redirect(“listnews.aspx”);
}
There is simply nothing wrong with that. But instead of letting the View handle the update and also the navigation to a “fixed” URL, I move it into a separate class (I will refer to the class as a controller also to make you more confused ;)):
protected void SaveButton_OnClick(object sender, EventArgs args)
{
//... Get the news info from the view
NewsController.SaveNews(news);
}
private string const LIST_NEWS = “listnews.aspx”;
public static Save(News news)
{
NewsRepository.Save(news);
HttpContext.Current.Response.Redirect(LIST_NEWS);
}
If I now want to change the flow of my app, I will not need to care about the code in the View, instead I handle the changes in my Controllers. Let’s assume I have several Views where I need to save news, all those Views should always navigate so the same View when the News is saved. If I put that code into my Views, I need to made changes in several Views if the flow of my app will be changed; now with the separation of the navigation etc into separated Controller, I only have one place to change the code.
How should this be implemented into the Controllers I use in my post about the MVP – Supervising Controller pattern?
Well, the answer is, we add the navigation into our controllers. So for example is we take a look at the NewsController’s SaveNews method, we can do the following to handle the navigation in our Controller:
public void SaveNews()
{
this._newsRepository.Save(this.CreateNewsFromView());
HttpContext.Current.Response.Redirect(LIST_NEWS);
}
But this will cause a problem when we start to run our tests against the View, because our tests will use the NewsController, and in this case we have code that will have a dependency to the System.Web namespace and try to call the Redirect method. If we want to reuse the NewsController for a Win Form, we also will have some problem because a Win Form should not do a redirect, it should open a new Win Form after the SaveNews method is called. So we need to move the Redirect code out from our NewsController. We put it back into the View. But isn’t that be strange, we just moved it from the View into the controller and now we moved it back. The thing is that the View is the component that has the dependency to the System.Web namespace in a Web app, if we use Win Form app, the Win Form View will know about the Win Form namespaces. In our case we will still make sure the navigation is handled form the Controller, so what we need to do is to put the code that should call the Redirect method into the View:
public void ShowView(string view)
{
Response.Redirect(view);
}
And the controller will make a call to the View’s ShowView method:
public void SaveNews()
{
this._newsRepository.Save(this.CreateNewsFromView());
this.NewsView.ShowView(LIST _NEWS);
}
Still the navigation is handled by the Controller. If we use a Win Form, the ShowView will be added to each Win Form View, and the Win Form View will have the implementation that should display the Win Form View.
Note: There is only one problem with the code above. In the code the controller have the name of the view, in this example it’s a constant with the value “listnews.aspx”. So what we need to do here is to make sure we pass the name of the view, and the ShowView method in the View will have the responsibility to use the name to get the correct View to show. If we use a Web application, we can for example use the web.config AppSettings section we can map the name of the view to the URL of the page:
<appSettings>
<add key=”ListNews” value=”listnews.aspx”/>
…
</appSettings>
The ShowView in the View of a Web app. would now look like:
public void ShowView(string view)
{
HttpContext.Current.Response.Redirect(WebConfigurationManager.AppSettings[view]);
}
The best thing with this solution is that we can now easily change the URL of a specific view to another one by only changing the settings in the web.config file.
At the beginning of this post I also mentioned that I move the State management out form the View into separated classes also. If we decide to save some state into the Session variables, we can’t directly call the Session object from our Controllers, because the Session object is not used in Win Form apps, and will also add a dependency to the Session variables in the Controllers. So we also need to make sure the View have the implementation of accessing the State management objects.
public void SaveSate(string key, object value)
{
Session[key] = value;
}
public object GetSate(string key)
{
return Session[key];
}
In our controllers we can now use the following code to save the state:
this.NewsView.SaveState(news.ID, news);
and the following code to load the value out from the state:
News news = this.NewsView.GetState(newsID) as News;
To avoid adding the code for the ShowView, SaveState and GetSate into every single View, we can create a base class for our View. In a Web From we need to make sure it will inherit the Page object. I will call the new base class for the Views for View, and here is the implementation of the View base class and IView interface:
public interface IView
{
IViewController Controller { get; set; }
void ShowView(string view);
object GetState(string key);
void SaveState(string key, object value);
}
public class View : Page, IView
{
private IViewController _controller;
public IViewController Controller
{
get { return this._controller; }
set { this._controller = value; }
}
public void ShowView(string view)
{
HttpContext.Current.Response.Redirect(WebConfigurationManager.AppSettings[view]);
}
public object GetState(string key)
{
return HttpContext.Current.Session[key];
}
public void SaveState(string key, object value)
{
HttpContext.Current.Session[key] = value;
}
}
Our Web Form View would now look like:
public partial class _Default : View, INewsView
{
public int NewsID
{
get
{
int id;
if (Int32.TryParse(Request.QueryString["ID"], out id))
return id;
else
return -1;
}
}
public string NewsTitle
{
get { return this.titleTextBox.Text; }
set { this.titleTextBox.Text = value; }
}
public string Body
{
get { return this.bodyTextBox.Text; }
set { this.bodyTextBox.Text = value; }
}
public bool Display
{
get { return this.dispalyCheckBox.Checked; }
set { this.dispalyCheckBox.Checked = value; }
}
public _Default()
{
base.Controller = new NewsController(this);
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
base.Controller.LoadView();
}
protected void saveButton_Click(object sender, EventArgs e)
{
((NewsController)base.Controller).SaveNews();
}
protected void deleteButton_Click(object sender, EventArgs e)
{
((NewsController)base.Controller).DeleteNews();
}
}
If we want to write our unit-test, we need to make sure our Mock View’s inherits the View base class also. But in this case we need to create a Test Double to avoid the dependency to the Page object. So we create a base class for the Mock View that we will inherit instead of the View in the code above.
public class MockView : IView
{
private Dictionary<string, object> _state = new Dictionary<string, object>();
private IViewController _controller;
public IViewController Controller
{
get { return this._controller; }
set { this._controller = value; }
}
public void ShowView(string view)
{
}
public object GetState(string key)
{
return this._state[key];
}
public void SaveState(string key, object value)
{
if (!this._state.ContainsKey(key))
this._state.Add(key, value);
else
this._state[key] = value;
}
}
You can download the source code for this post here.
Summary
We have now extended the Controllers to also handle navigation of the application and also handle the state. In our web app. example we have put the URL to our View’s into the Web.coinfig, which will make it easier for us to replace the URL to a View if needed. If we want to we could now go further and extend the code with our own configuration section, where we could also not only specify the View to show, but also what Controller the view should use. In that case we don’t need care about the instantiate the controller on each view. For example:
<views>
<view name="News" path="news.aspx" Controller=”NewsController”/>
</views>
But this is implementation need another post ;)