When you look into the source code for the action link you find that
<%= Html.ActionLink("LinkText", "Action", "Controller"); %>
will match
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName) {
return ActionLink(htmlHelper, linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary());
}
Now so far this looks good since it is creating a new route value dictionary so it is not passing along the values in your current context to be added to the new link, which is what will happen.
However, further down in the code where the url is being generated:
public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) {
if (routeCollection == null) {
throw new ArgumentNullException("routeCollection");
}
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
if (vpd == null) {
return null;
}
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
return modifiedUrl;
}
you can see the requestContext being referenced which has access to the routeData and routeCollections, which will contain the id data. When creating the VirtualPathForArea, the following line is where the id value appears in your url:
internal static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values, out bool usingAreas) {
if (routes == null) {
throw new ArgumentNullException("routes");
}
if (!String.IsNullOrEmpty(name)) {
// the route name is a stronger qualifier than the area name, so just pipe it through
usingAreas = false;
return routes.GetVirtualPath(requestContext, name, values);
}
string targetArea = null;
if (values != null) {
object targetAreaRawValue;
if (values.TryGetValue("area", out targetAreaRawValue)) {
targetArea = targetAreaRawValue as string;
}
else {
// set target area to current area
if (requestContext != null) {
targetArea = AreaHelpers.GetAreaName(requestContext.RouteData);
}
}
}
// need to apply a correction to the RVD if areas are in use
RouteValueDictionary correctedValues = values;
RouteCollection filteredRoutes = FilterRouteCollectionByArea(routes, targetArea, out usingAreas);
if (usingAreas) {
correctedValues = new RouteValueDictionary(values);
correctedValues.Remove("area");
}
VirtualPathData vpd = filteredRoutes.GetVirtualPath(requestContext, correctedValues);
return vpd;
}
The line:
VirtualPathData vpd = filteredRoutes.GetVirtualPath(requestContext, correctedValues);
takes the virtual path (which is just the route) and returns it. So the virtual path would be /Home/About/Flib
Then when that virtual path is returned the following line uses it to set the client url for the action link:
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
So pretty much it looks like the actionlink is set using the virtual path for the area, which is just the route that was matched, which happens to be the default route in this case.