Introduction
Many applications need a Toolbar kind of functionality including application created using ASP.net MVC . I thought of epxerimenting this with ASP.net MVC.
Background
Think about a application (ex CRM) with different models (ex Customer, Order, Products) but similiar actions ( ex add, save, delete). From user experience point of view , it is desirable that performing an action on those models is same throughout a application hence "Toolbar"
I am not going to discuss much about in general MVC pattern or specific to ASP.net MVC rather I would focus on design of Toolbar control and decision i made.
Design consideration
- Control should encapsulate all required HTML , Java script , Style sheet
- Rendering View should only be concerned with providing controller information and actions require binding with ToolBar
- It is not neccessary that all actions in controller required binding with Toolbar
Implementation
Control implementation is inspired by Seth juarez post.
One of design consideration is that not all controller's action required binding with Toolbar, how do we achieve this.
I had two options:
- While creating control in a view I pass list of actions to bind with ToolBar
- Create a custom attribute for action methods, apply this attirbut in controller. While redering a control, control check for this attribute and create required Tool bar Item.
I went for 2 option because i can reuse my Tool bar item defination across many views and i dont have to pass manually actions i want to use while creating a Toolbar.
so lets create custom Attribute ToolBarItemAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false,Inherited=false)]
public sealed class TooBarItemAttribute:Attribute
{
public string ToolTip { get; set; }
}
I created custom attribute with single property tooltip which later would be used to show user friendly Text for controller's action. Attribut usage is set to method because we want to make sure that this attribute can only be applied on Methods.
Creeating ToolBar Control
Tool Bar control is nothing more than series of buttons encapsulated in HTML <form> tag. In order to change action of <form> element at Client side we need a Java script which modify action peoperty of form element depending on button pressed. We need to inject this Javascript when control is being rendered so let us create RenderJavaScript
private string RenderJavaScript()
{
StringBuilder JavaScript = new StringBuilder();
JavaScript.AppendFormat(@"<script language=""javascript"" type=""text/javascript"">{0}",Environment.NewLine);
JavaScript.AppendFormat(@" function OnToolBarClick(action){0}",Environment.NewLine);
JavaScript.AppendFormat(@" {{{0}", Environment.NewLine);
JavaScript.AppendFormat(@" var form = document.getElementById(""{1}_Form"");{0}", Environment.NewLine, _formname);
JavaScript.AppendFormat(@" form.action = action;{0}",Environment.NewLine);
JavaScript.AppendFormat(@" form.submit();{0}",Environment.NewLine);
JavaScript.AppendFormat(@" }}{0}", Environment.NewLine);
JavaScript.AppendFormat(@"</Script>{0}", Environment.NewLine);
return JavaScript.ToString();
}
second we need a function to create Action URL
private string GetActionUrl(string actionName)
{
string controllerName = _controllerType.Name;
if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
controllerName = controllerName.Remove(controllerName.Length - 10, 10);
}
RouteValueDictionary values = new RouteValueDictionary();
values = values ?? new RouteValueDictionary();
values.Add("controller", controllerName);
values.Add("action", actionName);
VirtualPathData vpd = RouteTable.Routes.GetVirtualPath(_helper.ViewContext, values);
return (vpd == null) ? null : vpd.VirtualPath;
}
Third we need a function to render ToolBar Items. This function iterate through all the method of controllers which has ToolBarItemAttirbut Set and zero paramenter length.In button, we set our javascript function on onclick event to change main form action property. This function call GetActionURL
private void WriteToolBarItems()
{
var methods = from method in _controllerType.GetMethods()
where method.GetCustomAttributes(typeof(TooBarItemAttribute),false).Length > 0 & method.GetParameters().Length == 0
select method;
StringBuilder sb = new StringBuilder();
sb.AppendFormat(@"<div>{0}", Environment.NewLine);
if (methods.Count() == 0)
sb.AppendFormat(@"<span><strong>{0} doesn't have any action with Attribute ToolBarItem<Strong></span>",_controllerType.Name);
foreach (MethodInfo method in methods)
{
sb.AppendFormat(@"<span>{0}", Environment.NewLine);
sb.AppendFormat(@"<button onclick=""OnToolBarClick('{1}')"" value=""{2}"">{2}</button>{0}",Environment.NewLine,GetActionUrl(method.Name),GetActionToolTip(method));
sb.AppendFormat(@"</span>{0}", Environment.NewLine);
}
sb.AppendFormat(@"</div>{0}", Environment.NewLine);
_context.Response.Write(sb.ToString());
}
There are some more function in ToolBar control to finish markup and not worth mentioning, you can check those in code.
Finally we have constructor which play very important role to create control. Htmlhelper give us access to response context, controller type to retieve and setup actions, formname of auto created form element, and alignment (Top, Bottom) of ToolBar with respect to other form child control you may create
public ToolBar(HtmlHelper helper, Type controllerType, string formName, ToolBarAlignment alignment):
this(helper,controllerType,formName,FormMethod.Post,alignment)
{
}
private ToolBar(HtmlHelper helper,Type controllerType,string formName,FormMethod formMethod, ToolBarAlignment alignment)
{
_helper = helper;
_context = helper.ViewContext.HttpContext;
_controllerType = controllerType;
_formname = formName;
_formMethod = formMethod;
_toolBarAlignment = alignment;
WriteStartTag();
if (_toolBarAlignment == ToolBarAlignment.Top)
WriteToolBarItems();
}
Using the code
This code require ASP.NET Prview 4 and visiual sudio 2008
using this ToolBar is two step process
1 Apply ToolBarItemAttriibute to methods in controller whose action you want to bind with toolbar
public class CustomerController:Controller
{
public ActionResult Index()
{
return View();
}
[TooBarItem(ToolTip="Add Customer")]
public ActionResult Add()
{
string CustomerName = this.ReadFromRequest("CustomerName");
ViewData["CustomerName"] = CustomerName;
return View();
}
}
2 Using ToolBarHelper create Toolbar in your view
<%using(Html.ToolBarControl(typeof(MVCToolBarExp.Controllers.CustomerController),"formWithToolBar",ToolBarAlignment.Top)) %>
<%{%>
<div><span>Customer Name:</span><%=Html.TextBox("CustomerName") %></div>
<%} %>
Attached source code contain ToolBar control source as well as dumy test project. Let me know your views and any alternative way of implementation
Download MVCToolBarExp.zip - 375.26 KB