Problem
I have a model that uses the State pattern. Some States allow read/write access to the Model's properties and some are read-only. I wanted a good way to separate the logic for this from my Views; the View shouldn't have to decide whether properties are displayed as TextBoxes or inside Divs.
First Thought
At first I was going to create a custom ViewEngine. That ViewEngine would be responsible for choosing the right View – that is, if the View was called “Edit” but the model was in a readonly state, it would change the View to “ReadOnly”. However, that just didn't sound right – why was that responsibility falling on the ViewEngine? Plus, it's kind of inflexible – what if some editable Views are called “Edit” but some are called “Details”.
Solution
I ended up with a mixture of a custom ActionFilter and a base Controller class. Before I show you the code, let me show you the usage:
[AcceptVerbs(HttpVerbs.Get)]
[StateItemEditView("ReadOnly")]
public ActionResult Edit(int id)
{
var module = _Repository.GetByID(id);
return View(module);
}
Notice Line 2 – this specifies that:
- The current action has editable content
- The ReadOnly View name is called "ReadOnly"
The code for the controller, which I use as a base class for my controllers that act upon StateItems:
public class StateItemController : Controller
{
protected override ViewResult View(string viewName, string masterName, object model)
{
object readOnlyViewName;
if (ControllerContext.Controller.TempData
.TryGetValue(StateItemEditViewAttribute.key_ReadOnlyView, out readOnlyViewName))
{
var stateItem = model as IStateItem;
if (workflowItem != null && stateItem.CurrentState.IsReadOnly)
viewName = readOnlyViewName as string;
}
return base.View(viewName, masterName, model);
}
}
Before returning the View, which has a ViewName string, we want to make sure the ViewName is correct. We check the Controller's TempData for a predefined key. If that key exists, the value will be the name of the View to display for ReadOnly states. We then check if the Model exists and is a StateItem, and if it's ReadOnly we change the ViewName before returning the View.
The StateItemEditViewAttribute is just responsible for setting the TempData key:
public class StateItemEditViewAttribute : ActionFilterAttribute
{
public StateItemEditViewAttribute(string readOnlyView)
{
_ReadOnlyView = readOnlyView;
}
private string _ReadOnlyView;
public const string key_ReadOnlyView = "ReadOnlyView";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.TempData.Add(key_ReadOnlyView, _ReadOnlyView);
}
}
I'm using this for a binary decision – Edit/ReadOnly – but this could be used for a lot of other purposes too.