MVC strongly-typed action route helper
May 9th, 2009
A sample for the following post can be downloaded from here
Go and use it! it’s simple!
routes.MapRoute<HomeController>(
"Index",
"",
c => c.Index());
or maybe you have parameters on your action, and default values…
routes.MapRoute<HomeController>(
"Echo",
"Echo/{echo}",
c => c.Echo("I am the default value of the echo parameter!"));
You want to use it now? It’s ok, here it is:
You can copy and paste the following classes:
ControllerAction.cs
namespace System.Web.Mvc
{
using System;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;
///<summary>
/// Typed controller action that provides a <c ref="RouteValueDictionary">RouteValueDictionary</c> based on the parameters passed.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
public class ControllerAction<TController>
where TController : IController
{
/// <summary>
/// Initializes a new instance of the ControllerAction class.
/// </summary>
/// <param name="action">The controller action.</param>
public ControllerAction(Expression<Func<TController, ActionResult>> action)
{
this.DefaultValues = new RouteValueDictionary();
this.DefaultValues.Add("controller", typeof(TController).Name.Remove(typeof(TController).Name.LastIndexOf("Controller")));
var decorations = (action.Body as MethodCallExpression).Method.GetCustomAttributes(typeof(ActionNameAttribute), true);
var methodCall = action.Body as MethodCallExpression;
if (decorations != null && decorations.Length == 1)
{
this.DefaultValues.Add("action", (decorations[0] as ActionNameAttribute).Name);
}
else
{
this.DefaultValues.Add(”action”, methodCall.Method.Name);
}
var paremeters = methodCall.Method.GetParameters();
for (int parameterIndex = 0; parameterIndex < paremeters.Length; parameterIndex++)
{
object value = null;
var argumentExpression = methodCall.Arguments[parameterIndex];
if (argumentExpression is ConstantExpression)
{
value = (argumentExpression as ConstantExpression).Value;
this.DefaultValues.Add(
paremeters[parameterIndex].Name,
value);
}
}
}
/// <summary>
/// Gets the default route values.
/// </summary>
/// <value>The default route values.</value>
public RouteValueDictionary DefaultValues
{
get;
private set;
}
/// <summary>
/// Gets the controller name from the default values.
/// </summary>
/// <value>The controller name.</value>
public string Controller
{
get
{
return this.DefaultValues["controller"] as string;
}
}
/// <summary>
/// Gets the controller action from the default values.
/// </summary>
/// <value>The controller action.</value>
public string Action
{
get
{
return this.DefaultValues["action"] as string;
}
}
}
}
RouteCollectionExtensions.cs
namespace System.Web.Mvc
{
using System;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;
/// <summary>
/// Route collection extension methods class.
/// </summary>
public static class RouteCollectionExtensions
{
/// <summary>
/// Adds a typed route into a RouteCollection.
/// </summary>
/// <typeparam name="TController">The controller type.</typeparam>
/// <param name="routes">The route collection to fill in.</param>
/// <param name="routeName">Name of the route.</param>
/// <param name="url">The URL for the route.</param>
/// <param name="action">The controller action.</param>
public static void MapRoute<TController>(
this RouteCollection routes,
string routeName,
string url,
Expression<Func<TController, ActionResult>> action)
where TController : IController
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
var typedControllerAction = new ControllerAction<TController>(action);
routes.Add(routeName, new Route(url, typedControllerAction.DefaultValues, new MvcRouteHandler()));
}
}
}
Thanks,
Diego
HtmlHelper extension: HierarchicalRenderer
July 16th, 2008
I want to share with you an HtmlHelper extension method that I created to render a tree. It takes advantage of lamda expressions, so you can feel more comfortable when using it. It is not binded to any interface or class for the node type.
How using it looks like
For this example, I use as node class a type NodeViewData that holds some properties such as a string Caption, a Guid Id, and a List<NodeViewData>Children.
We will construct a simple tree, from a list of NodeViewData. This tree will be indented according to the depth, and with some simple Javascript we will show a message with its Id when clicking it.
<%= Html.HierarchicalRender<NodeViewData> ( // We pass an instance of IEnumerable<NodeViewData> // so it can iterate over the root nodes this.ViewData.Model.RootNodes, // We will use a <br /> as separator between nodes “<br />”, // I recieve an HierarchyInformation<NodeViewData> instance // it contains the Node, the parents of the node, // and the node order in its trunk // You specify a delegate (lamda) that returns // the HTML for the node being processed h => string.Format( // This is the basic node format I will use for the sample “<a href=\”#\” style=\”margin-left: {0}px\” onclick=\”alert (’{2}’); return false\”>{1}</a>”, // I will multiply the node parents count (depth) to indent the node h.ReversedParents.Length * 20, // We render the HTML for the caption Html.Encode (h.Node.Caption), // Let’s show the node Id when clicking h.Node.Id), // For a given node (n) let’s pass an expression // to return a IEnumerable of the same type // this expression will be used to let the // HierarchicalRendered iterate over its children n => n.Children) %>
Get it!
You can download a sample solution from http://cid-9e5d4c3be8afbb19.skydrive.live.com/self.aspx/Posts/SampleMvc.zip
In the HierarchicalRender.cs you can find the HtmlHelper extension method and classes.
Remember to have the namespace of the extension method in your markup to use it!
Glue code
Below I put the HierarchicalRender class (extension method) and the HierarchyInformation class (used for node the nodeFormat delegate).
public static string HierarchicalRender<T>( this HtmlHelper helper, IEnumerable<T> source, string nodeSeparator, Expression<Func<HierarchyInformation<T>, string>> nodeFormat, Expression<Func<T, IEnumerable<T>>> childEnumerator) { var builder = new StringBuilder(); HierarchicalRender<T>( helper, source, nodeSeparator, nodeFormat.Compile(), childEnumerator.Compile(), new T[0], builder); return builder.ToString(); } private static void HierarchicalRender<T>( this HtmlHelper helper, IEnumerable<T> source, string nodeSeparator, Func<HierarchyInformation<T>, string> nodeFormat, Func<T, IEnumerable<T>> childEnumerator, T[] reversedParents, StringBuilder builder) { int order = 0; foreach (T node in source) { if (order > 0) builder.Append(nodeSeparator); var info = new HierarchyInformation<T> { Node = node, Order = order, ReversedParents = reversedParents }; builder.Append(nodeFormat.Invoke(info)); var children = childEnumerator.Invoke(node).ToList(); if (children.Count > 0) { builder.Append(nodeSeparator); var reversedParentsWithSelf = reversedParents.ToList(); reversedParentsWithSelf.Insert(0, node); HierarchicalRender<T>( helper, children, nodeSeparator, nodeFormat, childEnumerator, reversedParentsWithSelf.ToArray(), builder); } order++; } } public class HierarchyInformation<T> { public T Node { get; set; } public T[] ReversedParents { get; set; } public int Order { get; set; } }