Introduction
In the previous parts (1, 2, 3) we have constructed a quite usable and functional framework. However, it still has some drawbacks. One of them is the necessity of typecasting when accessing controllers, views and tasks. The example below demonstrates such typecasting:
public class ProductsView : WebFormView, IProductsView
...
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
(Controller as ProductsController).ShowProductDetails();
}
As a system grows such typecasts may bloat code, excessively decreasing its readability and leading to errors. To eliminate this drawback we need a means of explicitly specifying the type of the associated controller (or view/task). In other words we need to make associations between tasks, controllers and views strongly typed.
Solution Outline
The most obvious solution is to isolate the typecasing operation in a new property of the required type:
public class ProductsView : WebFormView, IProductsView
...
private new ProductsController Controller
{
get { return base.Controller as ProductsController; }
set { base.Controller = value; }
}
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
Controller.ShowProductDetails();
}
Although acceptable, this solution requires several additional lines of code. A more elegant solution can be constructed with a handy feature of .NET framework called Generics. The generics mechanism allows varying a class members' types by specifying those types in the class declaration. For example, it is possible to adjust a return type for certain properties by writing that type in brackets in the class definition line.
Applying Generics we could strictly specify the type of the association between a view and its controller as in the code below:
public class ProductsView : WebFormView<ProductsController>, IProductsView
...
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
Controller.ShowProductDetails();
}
To make the above code workable we need to extend our framework, adding the generics support.
Solution Implementation
First of all we will add a generic view and controller interfaces to the framework. They will extend old IView and IController interfaces with new strongly typed generic associations:
public interface IView<T> : IView where T : IController
{
new T Controller
{
get;
set;
}
}
public interface IController<TTask, TView> : IController where TTask : ITask
{
new TTask Task
{
get;
set;
}
new TView View
{
get;
set;
}
}
These interfaces alone do provide strongly typed associations, however, we also need to write some base generic implementation classes for these interfaces. So a developer will only inherit these base classes instead of implementing the interfaces above.
We will implement the properties simply with backing fields and mark them virtual so that a developer may override them in subclasses:
public class WinFormView<T> : Form, IView<T> where T : class, IController
{
...
protected T controller;
public virtual T Controller
{
get { return controller; }
set { controller = value; }
}
IController IView.Controller
{
get { return Controller; }
set { Controller = value as T; }
}
...
}
public class ControllerBase<TTask, TView> : IController<TTask, TView>
where TTask : class, ITask
where TView : class
{
protected TTask task;
protected TView view;
public virtual TTask Task
{
get { return task; }
set { task = value; }
}
public virtual TView View
{
get { return view; }
set { view = value; }
}
ITask IController.Task
{
get { return Task; }
set { Task = value as TTask; }
}
IView IController.View
{
get { return View as IView; }
set { View = value as TView; }
}
}
Note that the non-generic IView
and IController
interfaces are implemented as gateways to the strongly typed generic properties. This makes the access in the old non-generic manner (as done by the framework) equivalent to accessing the new strongly typed properties.
Below is an example of using the new generic features of the framework:
class MyController : ControllerBase<MyTask, IMyView>
{
public void MyOperation()
{
View.MyViewOperation();
}
public override MyTask Task
{
get { return base.Task; }
set
{
base.Task = value;
...
}
}
}
Summary
In the article we have developed new framework features which make it more usable and allow us to avoid typecasting errors.
Project Website
www.MVCSharp.org
Oleg Zhukov, born and living in Russia is Lead Engineer and Project Manager in a company which provides business software solutions. He has graduated from Moscow Institute of Physics and Technology (MIPT) (department of system programming) and has got a M.S. degree in applied physics and mathematics. His research and development work concerns architectural patterns, domain-driven development and systems analysis. Being the adherent of agile methods he applies them extensively in the projects managed by him.