By default, the MVC UrlHelper
will include all of the route values for the current route in it’s calculations.
This means that unless you explicitly override them you can get situations like this:
<!-- on page /Person/View/1 -->
<a href="@Url.Action("View", "Pet")">View Animal</a>
<!-- URL resolves to /Pet/View/1 -->
Disaster - the ID from the current request has been included in the new URL!
In some cases this can be very useful - this is the reason that you don’t need to specify a controller if you are already within a view on the same controller - but can be very annoying when you want to create a URL in isolation (see here and here).
Using the Isolate
Extension
To get around this problem I have written an Isolate
extension method that can be used as below:
<!-- on page /Person/View/1 -->
<a href="@Url.Isolate(u => u.Action("View", "Pet"))">View Animal</a>
<!-- URL resolves to /Pet/View -->
The extension works by temporarily removing all of the existing route values from the specified instance of UrlHelper
, executing the action, and then re-inserting the original route values before returning the result.
public static TResult Isolate<TResult>(this UrlHelper urlHelper, Func<UrlHelper, TResult> action)
{
var currentData = urlHelper.RequestContext.RouteData.Values.ToDictionary(kvp => kvp.Key);
urlHelper.RequestContext.RouteData.Values.Clear();
try
{
return action(urlHelper);
}
finally
{
foreach (var kvp in currentData)
urlHelper.RequestContext.RouteData.Values.Add(kvp.Key, kvp.Value.Value)
}
}
It’s a basic solution and there are some (predictable) scenarios where it will fall down, but it solved my immediate problem without adding to much bloat to the code.