My Technical Notes

Friday, 18 July 2014

Strongly-typed Html.ActionLink

Html.ActionLink is fraught with the possibility that either the controller or action or parameter name(s) have been changed. As a way of making sure that these are always valid, we can use a strongly typed lambda expression.

We first define an ActionLink method which takes in the linkText (a string) and a lambda expression representing the controller, action and parameter(s):


public static class HtmlActionExtensions
{
    public static MvcHtmlString ActionLink<T, U>(this HtmlHelper html, string linkName, Expression<Func<T, U>> action)
    {
        var controllerName = action.Parameters.First().Type.Name.Replace("Controller", "");

        var methodExpr = (action.Body as System.Linq.Expressions.MethodCallExpression);

        var actionName = methodExpr.Method.Name;

        var parameters = new Dictionary<string, object>();

        if (methodExpr.Arguments.Any())
        {
            var methodParams = methodExpr.Method.GetParameters();

            int counter = 0;
            foreach (var arg in methodExpr.Arguments)
            {
                // see second answer of Eric Lippert here:
                // http://stackoverflow.com/questions/2162757/how-to-get-parameter-names-from-an-expression-tree

                var paramName = methodParams.ElementAt(counter).Name;
                var paramValue = GetValue(arg);

                parameters.Add(paramName, paramValue);
                counter++;
            }
        }

        var routeDict = new System.Web.Routing.RouteValueDictionary(parameters);
        return html.ActionLink(linkName, actionName, controllerName, routeDict, null);
    }

    public static object GetValue(Expression member)
    {
        return Expression.Lambda(member).Compile().DynamicInvoke();
    }
}

Given a test action TestAction defined in the HomeController class:


public ActionResult TestAction(int id)
{
    return Content("test action called with id: " + id);
}

We can add to our view:


@Html.ActionLink("Test Action", (HomeController h) => h.TestAction(76))

No comments: