need help canonicalizing an HTTP GET form in asp.n

2019-01-29 11:47发布

问题:

I have a form in an asp.net mvc site that serves 3 purposes: paging, sorting, and searching. These items should all be rendered in the same form, since returning the correct search results depends on variables from all 3 aspects. What I'm trying to do is move the parameters out of the querystring and put them in a canonical URL.

I'm almost there, here are my 3 route configurations so far (using T4MVC for area, controller, and action names):

context.MapRoute(null,
    "my-area/my-widgets/search/{size}-results-max/page-{page}/order-by-{sort}",
    new
    {
        area = MVC.MyArea.Name,
        controller = MVC.MyArea.MyWidgets.Name,
        action = MVC.MyArea.MyWidgets.ActionNames.Search,
        page = UrlParameter.Optional,
        size = UrlParameter.Optional,
        sort = UrlParameter.Optional,
    }
);

context.MapRoute(null,
    "my-area/my-widgets/canonicalize-search",
    new
    {
        area = MVC.MyArea.Name,
        controller = MVC.MyArea.MyWidgets.Name,
        action = MVC.MyArea.MyWidgets.ActionNames.CanonicalizeSearch,
    }
);

context.MapRoute(null,
    "my-area/my-widgets",
    new
    {
        area = MVC.MyArea.Name,
        controller = MVC.MyArea.MyWidgets.Name,
        action = MVC.MyArea.MyWidgets.ActionNames.CanonicalizeSearch,
    }
);

The form in the view submits to the CanonicalizeSearch route, using this syntax:

@using (Html.BeginForm(MVC.MyArea.MyWidgets.CanonicalizeSearch(), 
    FormMethod.Get))

In the MyWidgetsController, there are 2 action methods:

[ActionName("canonicalize-search")]
public virtual RedirectToRouteResult CanonicalizeSearch(string keyword, 
    int page = 1, int size = 10, string sort = "Title-Ascending")
{
    var result = RedirectToRoutePermanent(new
    {
        area = MVC.MyArea.Name,
        controller = MVC.MyArea.MyWidgets.Name,
        action = MVC.MyArea.MyWidgets.ActionNames.Search,
        page = page,
        size = size,
        sort = sort,
        keyword = keyword,
    });
    return result;
}

[ActionName("search")]
public virtual ViewResult Search(string keyword, 
    int page = 1, int size = 10, string sort = "Title-Ascending")
{
    // code to perform query
    return View(model);
}

This works for moving all querystring variables into a canonicalized route except for the keyword. If I add a keyword parameter to the first route, the CanonicalizeSearch action only redirects to the Search action when keyword is not null, empty, or whitespace. This is no good as it makes browsing page results impossible when there is no keyword entered.

I think I've tried everything -- giving the keyword a default value in the controller, adding a 4th route that adds keyword to the other 3 parameters, etc. However the only way I can seem get this to work is by keeping keyword as a querystring parameter. (Actually I can get it to work by prepending an underscore to the keyword in CanonicalizeSearch and stripping it off in Search, but that's pretty hacky).

Any help?

回答1:

Did you try setting UrlParameter.Optional on the keyword parameter in your first route? Sounds obvious and dumb, but you never ruled it out.



回答2:

I think I stumbled on a better solution to this by trying to solve another problem.

Say someone types in "my search terms" in the keyword box. Submitting that causes the CanonicalizeSearch method to route to the path:

/my-area/my-widgets/search/10-results-per-page/page-1/
    order-by-Title-Ascending/my%20search%20terms

Those %20 symbols are annoying. I would rather the URL look like this:

/my-area/my-widgets/search/10-results-per-page/page-1/
    order-by-Title-Ascending/my-search-terms

I can accomplish this with the following (note the change from a permanent to a temporary redirect):

[ActionName("canonicalize-search")]
public virtual RedirectToRouteResult CanonicalizeSearch(string keyword, 
    int page = 1, int size = 10, string sort = "Title-Ascending")
{
    var result = RedirectToRoute(new
    {
        area = MVC.MyArea.Name,
        controller = MVC.MyArea.MyWidgets.Name,
        action = MVC.MyArea.MyWidgets.ActionNames.Search,
        page = page,
        size = size,
        sort = sort,
        keyword = (string.IsNullOrWhiteSpace(keyword)) 
            ? "no-keywords" : keyword.Replace(' ', '-'),
    });
    TempData["keyword"] = keyword;
    return result;
}

[ActionName("search")]
public virtual ViewResult Search(string keyword, 
    int page = 1, int size = 10, string sort = "Title-Ascending")
{
    keyword = TempData["keyword"] as string ?? keyword;
    // code to perform query
    return View(model);
}

This solves both the question I posted here and the removal of the %20 symbols. Whenever the keyword is null empty or whitespace, it will render the URL

/my-area/my-widgets/search/10-results-per-page/page-1/
    order-by-Title-Ascending/no-keywords

... and the route will always match.