I have an application here with a mix of webform and mvc. I specify the routing as below
routes.Add("AspxRoute", new Route("Upload/New", new WebFormRouteHandler<Page>("~/Uploads.aspx")));
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
So that virtual path to "Upload/New" actually maps to an aspx webform page.
But my problem is that Html.ActionLink("Test", "Controller", "Action") now renders
/Upload/New?Controller=Controller&Action=Action
Having looked at the MVC source code, I understand that it is because ActionLink calls to RouteCollection.GetVirtualPath(requestContext, routeName, mergedRouteValues), where routeName is left to null. And somehow this defaults to use the AspxRoute route to construct the url. I tried to added another route before "AspxRoute", but it seems it always defaults to the non-mvc routehandler one.
How does RouteCollection.GetVirtualPath behave when routeName is null? And why is it behaving this way for my case?
How do I construct a correct url? Do I need to write a new Htmlhelper extension?
Cheers
An alternative option would be to add a custom constraint to your WebFormRoute(s). For example, you could create an implementation of IRouteConstraint to match RouteDirection.IncomingRequest, then use it to ensure the route is ignored by Server-Generated routes (such as ActionLink) but still used by client-generated requests. Something like:
public class IncomingOnlyRouteConstraint: IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
return true;
}
return false;
}
}
And then add the constraint to your route:
routes.Add("AspxRoute", new Route("Upload/New", null,
new RouteValueDictionary() { {"WebFormsConstraint", new IncomingOnlyRouteConstraint()} },
new WebFormRouteHandler<Page>("~/Uploads.aspx")));
Of course you may prefer to add your own style of constraint, this one is quite limiting on the route that implements it, but it's just an example of one way you could resolve the issue.
Try:
<%=Html.RouteLink("Test", "Default", new {controller = "Controller", action = "Action"})%>
Using RouteLink instead of ActionLink allows you to specify the route you want to use, which in this case is the Default MVC route mapping as opposed to the custom one you have added.
Also: Make sure your Default route is the LAST entry in the routing table. That's another easy way to wind up with the sort of html action link you're getting.
Force the route defaults to have no controller:
var routeDefaults = new RouteValueDictionary() { { "controller", null } };
routes.Add("RouteName", new Route("some/path", routeDefaults, new SomeHandler()));
I experienced the same thing where the routes worked correctly "inbound", but Html.ActionLink()
was picking the wrong route. I worked around it by adding a route constraint so that the controller
must be empty:
var constraints = new RouteValueDictionary()
{
{ "controller", string.Empty }
};
routes.Add(new Route("sso/server", null, constraints, new OpenIDServerRouteHandler()));
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{identity}",
defaults: new { controller = "Pages", action = "Home", identity = UrlParameter.Optional }
);
Since the "controller" route value is restricted to nothing, a call to ActionLink() ends up ignoring the route. Hope this helps someone!