lucamilan
4/7/2012 - 1:49 PM

Strongly typed and well-controlled links within ASP.NET MVC areas

Strongly typed and well-controlled links within ASP.NET MVC areas

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class LinkWithinAreaAttribute : Attribute
{
    public string AreaName { get; private set; }
    public string OrSwitchTo { get; set; }

    public LinkWithinAreaAttribute(string areaName)
    {
        AreaName = areaName;
    }
}

public static class LinkWithinArea
{
    public static RouteValueDictionary FixArea<TController>(this RouteValueDictionary routeValues)
    {
        var attr = (LinkWithinAreaAttribute) typeof(TController)
            .GetCustomAttributes(typeof(LinkWithinAreaAttribute), inherit: true)
            .SingleOrDefault();

        if (attr != null && !IsInAlternativeArea(routeValues, attr))
            routeValues["area"] = attr.AreaName;

        return routeValues;
    }

    private static bool IsInAlternativeArea(RouteValueDictionary routeValues, LinkWithinAreaAttribute attr)
    {
        return attr.OrSwitchTo != null
            && routeValues.ContainsKey("area")
            && attr.OrSwitchTo.ToLowerInvariant() == routeValues["area"].ToString().ToLowerInvariant();
    }
}

public static class AreaAwareLinkingExtensions
{
    public static string Action<TController>(this UrlHelper helper, Expression<Action<TController>> action)
        where TController : Controller
    {
        var vpd = helper.RouteCollection
            .GetVirtualPathForArea(helper.RequestContext, GetFixedRouteValues(action));
        return (vpd == null) ? null : vpd.VirtualPath;
    }

    public static void RenderAction<TController>(this HtmlHelper helper, Expression<Action<TController>> action)
        where TController : Controller
    {
        var routeValues = GetFixedRouteValues(action);
            
        foreach (var keyValuePair in helper.ViewContext.RouteData.Values)
        {
            if (!routeValues.ContainsKey(keyValuePair.Key))
                routeValues.Add(keyValuePair.Key, keyValuePair.Value);
        }
            
        helper.RenderRoute(routeValues);
    }

    public static MvcHtmlString ActionLink<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object htmlAttributes = null)
        where TController : Controller
    {
        return System.Web.Mvc.Html.LinkExtensions.RouteLink(helper, linkText, GetFixedRouteValues(action), new RouteValueDictionary(htmlAttributes));
    }

    private static RouteValueDictionary GetFixedRouteValues<TController>(Expression<Action<TController>> action)
        where TController : Controller
    {
        return Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression(action).FixArea<TController>();
    }
}