Generate a URL From Route Data ONLY

2019-02-10 20:21发布

问题:

I'm trying to do something simple in ASP.NET MVC:

RouteValuesDictionary routeValues = GetMyRouteData();
var url = new UrlHelper(Html.ViewContext.RequestContext);
return url.RouteUrl(routeValues);

The problem is that no matter what I do, the url includes route data from the current request context. I want to generate a URL based on ONLY the route values from GetMyRouteData().

Thanks

回答1:

The problem is that no matter what I do, the url includes route data from the current request context

That's by design. You have to explicitly set the route values that were present in the original request and that you don't want in the resulting url to null:

    var routeValues = GetMyRouteData();
    // remove values that you want to exclude from the resulting url
    // by setting their values to null
    routeValues["id"] = null;
    var url = new UrlHelper(Html.ViewContext.RequestContext);
    return url.RouteUrl(routeValues);


回答2:

This is not specific to ASP.NET MVC, but due to ASP.NET Routing's route resolution. The entry point to this is RouteCollection.GetVirtualPath, which has two signatures.

The first takes a RequestContext and a RouteValueDictionary. This is used for default route resolution, which relies on pattern matching to find a route. The route search incorporates all tokens from RequestContext as well as RouteValueDictionary; in other words, the two sets of route tokens are combined to form the basis for the route search. A special case exists whereby null parameters in the RouteValueDictionary remove that parameter from search. However, if such a null-valued parameter has a value in the RequestContext, that value will still appear in the generated URL as a query string value.

The other signature additionally accepts a route name. It is a little strange because it alters both the route resolution as well as the query string creation. Routes are found, obviously, using name resolution. Given the named route is found, only tokens for those parameters specified in route's URL pattern will appear in the generated URL.

Why is it this way? It's ASP.NET MVC's interpretation of Ruby on Rails' parameter-handling convention.

So default route resolution and "fallback" token resolution are comingled. If you don't want tokens to fallback to the RequestContext, (and you still want to use ASP.NET Routing) you have to use named route resolution.



回答3:

This might help clarify. Use the source, Luke!

The RouteUrl helper calls into this static method to generate the URL:

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;
}

Note the line:

RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);

This is merging in the current requestcontext values under the covers. So, you could make your own helper that just calls into this static method and passes empty collections to avoid getting the current route context values being merged in. Experiment and debug into the MVC code and you should be able to see which values you need to nuke.