I have a Sitecore site and on 2 of my CD servers, Url.Action
returns an empty string. This works locally and on 9 other servers ranging from dev to prod, CD and CM.
Deployment automation ensures that the exact same web.config
is deployed to all environments; ditto for all other configs.
My controller inherits from SitecoreController
properly. This is not isolated to a certain controller or action, this happens with all controllers and actions.
What would make Url.Action
return an empty string in one environment and not others, with identical code?
What would make Url.Action return an empty string sometimes?
Specifically, route values that are derived from the current request.
The Url.Action
method is driven by the UrlHelper
, which in turn is driven by routes. It uses route values to determine which route to use to build the URL. The routing framework attempts to match each route against the route values in the order they are registered until a match is found. If the routing framework reaches the end of the routing table and there is still no match, it returns an empty string (because there is no other reasonable default behavior).
On the other hand, if you call Url.Action
and pass a route name, this narrows the possible matches to only 1 specific route (the named one). But the route values still need to match that route or you get the default empty string.
In general, all route values must match, but there are a couple of things that may make the behavior quirky:
Url.Action
, it may be supplied automatically if it exists in the current request.This second quirk means that if Url.Action
is put on a shared view and one request contains a route value to make it match a route, and another request doesn't contain that route value, in the latter case the URL may match another route or it may be an empty string.
Say the routing configuration is setup like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "AboutWith4RouteValues",
url: "test/home/about/{foo}/{bar}",
defaults: new { controller = "Home", action = "About" });
routes.MapRoute(
name: "ContactWith4RouteValues",
url: "test/home/contact/{foo}/{bar}",
defaults: new { controller = "Home", action = "Contact", bar = UrlParameter.Optional });
routes.MapRoute(
name: "Home",
url: "",
defaults: new { controller = "Home", action = "Index" }
);
}
}
Also, let's say there is a link on the _Layout.cshtml
page:
<a href='@Url.Action("About", "Home")'>About</a>
If you go to the home page in the browser (/
), the resulting link URL will be:
<a>About</a>
This is because foo
and bar
are not present in the route values of the request so it doesn't match any of the registered routes.
Actually,
Url.Action
returns an empty string, but Razor optimizes away the emptyhref=''
attribute.
On the other hand, if you put this URL in the browser:
/test/home/contact/arg1/arg2
The link URL is generated as:
<a href='/test/home/about/arg1/arg2'>About</a>
This is because both {foo}
(with value arg1
) and {bar}
(with value arg2
) are available in the current request. Note that the incoming request matches the ContactWith4RouteValues
route, but when the link URL is generated it uses the AboutWith4RouteValues
route. Since both foo
and bar
are present in the request, they are carried over to the generation of the URL.
Now, if the URL in the browser is changed to:
/test/home/contact/arg1
It still matches the ContactWith4RouteValues
route because the last value is optional. However, the URL that is generated is now:
<a>About</a>
This is because foo
has a value in the request, but bar
has no value, the Url.Action
generation request does not match AboutWith4RouteValues
because bar
is a required value in order to make it match. And since it also doesn't match the Home
route, we have reached the end of the route table and the only logical thing to return is empty string.
The simplest workaround to avoid these quirks of the current request is to manually specify the route values when calling Url.Action
or other UrlHelper
based methods (such as ActionLink
, RedirectToRoute
, etc).
<a href='@Url.Action("About", "Home", new { foo = Model.Foo, bar = Model.Bar })'>About</a>
This ensures those values are always present when building the URL even if they don't happen to be part of the current request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With