Based on my previous posting regarding Generic Repositories, I decided to take this approach a step further and create Generic Controllers. The idea is simple: create a Generic Controller which can handle all of the CRUD operations by default. If we need to override or extend these methods, we can create a controller which inherits from this Generic Controller. We will also need to create a Controller Factory which will first look for a derived class, if it cannot find one, it will automatically instantiate an instance of the Generic Controller and use this instance as the controller.
First we must create a Generic Controller, capable of handling all of the CRUD requests. Below is my implementation.
public class ApplicationController<T> : Controller<T> where T: class
{
protected ApplicationRepository<T> Repository { get; set; }
private string[] _strLockedProperties = new string[] {
"Id",
"CreatedOn",
"CreatedBy",
"ModifiedOn",
"ModifiedBy"
};
protected virtual string[] LockedProperties
{
get
{
return _strLockedProperties;
}
}
public ApplicationController()
{
Repository = new ApplicationRepository<T>();
}
public virtual ActionResult Index()
{
return View(Repository.GetAll());
}
public virtual ActionResult Create()
{
return View();
}
[HttpPost]
public virtual ActionResult Create(T record)
{
if (ModelState.IsValid)
{
Repository.Insert(record);
Repository.Save();
}
return RedirectToAction("Index");
}
public virtual ActionResult Details(Guid id)
{
return View(Repository.Get(id));
}
public virtual ActionResult Edit(Guid id)
{
return View(Repository.Get(id));
}
[HttpPost]
public virtual ActionResult Edit(Guid id, T record)
{
var properties = typeof(T).GetProperties();
var fields = new List<string>();
foreach (var prop in properties)
{
if (!LockedProperties.Contains(prop.Name))
{
fields.Add(prop.Name);
}
}
var allowed = fields.ToArray();
var existing = Repository.Get(id);
if (TryUpdateModel(existing, allowed))
{
Repository.Save();
return RedirectToAction("Index");
}
return View(record);
}
public virtual ActionResult Delete(Guid id)
{
return View(Repository.Get(id));
}
[HttpPost]
public virtual ActionResult Delete(Guid id, string confirm)
{
Repository.Delete(Repository.Get(id));
Repository.Save();
return RedirectToAction("Index");
}
This class will be able to handle all of the default CRUD operations without worrying about the actual data types. Based on the previous posting, even the repositories will be automatically generated.
If you need any special functionality for a controller, simply create a derived class.
public class UserController : ApplicationController<UserAccount>
{
public override ActionResult Edit(Guid id)
{
//Special code here.
}
}
Now lets wire up our Controller Factory. We need to create a factory which first looks for a strongly typed controller. If it cannot find that strongly typed controller it will automatically instantiate a new Generic Controller and return this instance.
public class ApplicationControllerFactory : IControllerFactory
{
private string Namespace
{
get
{
return this.GetType().Namespace;
}
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
if (string.IsNullOrEmpty(controllerName))
throw new ArgumentNullException("controllerName");
Type cType = Type.GetType(Namespace + ".Controllers." + controllerName + "Controller");
if (cType == null)
{
cType = Type.GetType(Namespace + ".Library.ApplicationController`1[" + Namespace + ".Models." + controllerName + "]");
}
return Activator.CreateInstance(cType) as Controller;
}
public void ReleaseController(IController controller)
{
if (controller is IDisposable)
(controller as IDisposable).Dispose();
else
controller = null;
}
public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return System.Web.SessionState.SessionStateBehavior.Default;
}
}
Now register the Controller Factory in your Global.asax.cs file.
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new ApplicationControllerFactory());
}
The effects on development speed of this approach is pretty profound. Essentially you have a Controller and Repository layer which is completely dynamic, powered only by the meta data of the data layer. All that must be done is creating the views, and overriding your controllers and repositories where necessary.
