Routing legacy requests with and without querystri

2020-07-11 08:40发布

问题:

(Before starting: I am aware of this and this. I'd like to find a more concise solution -if possible- for a slightly more specific problem)

I'm rewriting an old Webforms app in MVC. As usual, no permalinks should be broken.

I'm using standard {controller}/{action}/{id} routes. Legacy paths are usually SomePage.aspx?ID=xxx, and I have one particular case where Foo.aspx is a list of Bar (new URL: /Bar or /Bar/Index) and Foo.aspx?ID=xxx is the Bar detail (new URL: /Bar/View/xxx)

One possible workaround is adding the following before the Default MapRoute:

routes.MapRoute("Bar View", "Foo.aspx",
                new { controller = "Bar", action = "View" });

And then defining the corresponding action in BarController:

public ActionResult View(int? id)
{
    if (id == null)
        return RedirectToAction("Index");
    return View();
}

There are two problems with this:

  • Now, if I create an ActionLink, it uses the legacy format
  • I'd like to handle this in the routes; making the id nullable and redirecting in the controller is just wrong

I'm fine with mapping the legacy URLs by hand (I don't need a generic solution and there are only about 8 pages)

This is a new project, so I'm not tied to anything.

回答1:

I was able to solve this based on Dangerous' idea plus a constraint based on this answer.

My new route table is:

routes.MapRoute("Bar", "Bar/{action}/{id}",
                new
                {
                    controller = "Bar",
                    action = "Index",
                    id = UrlParameter.Optional
                });
routes.MapRoute("Bar View", "Foo.aspx",
                new {controller = "Bar", action = "View"},
                new {id = new QueryStringConstraint()});
routes.MapRoute("Bar Index", "Foo.aspx",
                new { controller = "Bar", action = "Index" });
routes.MapRoute("Default", /*...*/);

And the QueryStringConstraint couldn't be simpler:

public class QueryStringConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route,
                      string parameterName, RouteValueDictionary values,
                      RouteDirection routeDirection)
    {
        return httpContext.Request.QueryString.AllKeys
            .Contains(parameterName, StringComparer.InvariantCultureIgnoreCase);
    }
}


回答2:

I believe if you specify the following routes:

routes.MapRoute(
    null,
    "Bar/{action}/{id}",
     new { controller = "Bar", action = "View", id = UrlParameter.Optional },
     new { action = "Index|Next" } //contrain route from being used by other action (if required)
);

routes.MapRoute(
    null,
    "Foo.aspx/{id}",
    new { controller = "Bar", action = "View", id = UrlParameter.Optional }
);

//specify other routes here for the other legacy routes you have.

Then this should solve your first problem. If the user specifies Foo.aspx in the url then they will be taken to the View action.

If the action link:

@Html.ActionLink("Click me", "Index", "Bar")

is specified then the first route will be used (as the order matters).

However, I could not figure out how to specify if Foo.aspx?id=... then to go to one route else if Foo.aspx is specified then go to the other route. Therefore, I would check whether id is null in the action. However, if you do find this out I would very much like to know.

Hope this helps.