ASP.NET MVC Robust HyperLinks

Sunday, September 05, 2010 / Posted by Luke Puplett /

Motivated by ActionLink failing to produce proper MVC-style / / / links and, although RouteLink does work, I think route names and possibly all route logic should be kept out of the view, I decided to bake my own link maker, and make the links in the controller and put them in the model. Here’s how I did that.

I have a BaseSiteNameController in which helper methods go. To this, I added the following method:

public static string BuildLink(
    RequestContext requestContext,
    string routeName,
    string action,
    string controller,
    object routeValues)
{
    UrlHelper u = new UrlHelper(requestContext);

    var rvd = new RouteValueDictionary(routeValues);
    rvd.Add("controller", controller);
    rvd.Add("action", action);

    var httpContext = System.Web.HttpContext.Current;

    return UrlHelper.GenerateUrl(
        routeName, null, null,
        httpContext.Request.Url.Scheme, 
        httpContext.Request.Url.DnsSafeHost, 
        null, rvd, u.RouteCollection, 
        requestContext, true);
}

Note that I specify controller and action in the RouteValueDictionary even though GenerateUrl has parameters for controller and action. When these are used, it kicks out old-skool URLs – I think these params were designed to be used when a route name is not supplied. The GenerateUrl method smells like it wasn’t designed for general direct use.

GenerateUrl is the thing that does the magic; it looks up the route and works out how the URL should be structured.

It’s a static method because I also want to be able to call it from my models. Some of my models have sub-models and some of those have links. I figured that a View is for layout, and links kind of straddle both sides but the logic required to make them, tips them into the non-view side, in my opinion.

My models aren’t always created and prepared by my controllers. Some of my models contain a small amount of logic to populate themselves, so I want them to be able to call this method* when they populate their links (e.g. FavouriteBooksModel.AddBookLink)

Note also that the method takes a RequestContext. I now have to have this context available in my models, so I pass it down using a CustomerWebProfile class that I already use to flow important data into my models, such as TimeZone data. Each model has a CustomerWebProfile property (inherited via a base model).

*Except that my models don’t directly call BuildLink because I wrap these calls helper methods.

Under each action method, I add a method to build a link to the method. I do this because I can prevent the caller from having to know the action name, and so I can refactor it easily.

public static string BuildLinkToCustomerAccount(
    System.Web.Routing.RequestContext requestContext, string customerId)
{
    return BuildLink(requestContext, "Default", "CustomerAccount", "Account",
        new { data = customerId });
}

Now my models use these helper methods to make their links (code in model).

public string CustomerAccountLink
{
    get
    {
        return Controllers.PlannerController.BuildLinkToRecordOneTime(
            this.WebProfile.RequestContext, this.TransmissionKeys);
    }
}

If I change my controllers or actions, I can change the helper method, and renames can be done without worrying about strings in aspx pages!

My views now make links in the normal way, like this:

<a href="<%: Model.CustomerAccountLink %>" ><%: Html.GetLocalString("Your Account") %></a>


And if I change the model, my page won’t compile and I can sort them out before runtime.

Labels: , , ,

0 comments:

Post a Comment