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:
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.
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).
{
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: aspnet, html, programming, web
0 comments:
Post a Comment