web-dev-qa-db-fra.com

ASP.NET MVC Url.Action ajoute les valeurs de route actuelles à l'URL générée

J'ai vu cette question plusieurs fois ici en SO mais aucun d'eux avec une réponse acceptable:

ASP.NET MVC @ Url.Action inclut les données de route actuelles
ASP.NET MVC ajoute implicitement des valeurs de route

Fondamentalement, j'ai Controller avec une méthode d'action appelée Group, il a une surcharge qui ne reçoit aucun paramètre et affiche une liste d'éléments et un autre qui reçoit un identifiant et affiche les détails de ce groupe.

Si je fais quelque chose comme ça:

Url.Action("Group", "Groups");

Depuis la page principale du site (/) il retourne une URL comme celle-ci:

"mysite.com/Groups/Group"

ce qui est bien maintenant, si l'adresse actuelle du site est/Groups/Group/1 Et j'appelle la même méthode

Url.Action("Group", "Groups");

l'URL retournée est la suivante:

"mysite.com/Groups/Group/1"

Il ajoute automatiquement la valeur de l'itinéraire pour la page actuelle lors de la génération de l'URL. Même si je génère l'URL de cette façon:

Url.Action("Group", "Groups", null);

Spécifiant ainsi explicitement que je ne veux aucune valeur de route, l'URL générée est la même. Pour obtenir l'adresse que je veux, je dois définir explicitement la valeur de l'itinéraire sur une chaîne vide, comme ceci:

Url.Action("Group", "Groups", new {id=""});

Cela générera l'url suivante:

"mysite.com/Groups/Group"

Ma question est, pourquoi cela se produit-il? Si je ne définis aucune valeur d'itinéraire, il ne doit pas les ajouter à l'URL générée.

53
willvv

Url.Action réutilisera les paramètres de requête actuels, si vous ne les définissez pas explicitement. C'est par conception dans l'algorithme de correspondance d'url sortant. Lorsque vous recherchez les paramètres de données d'itinéraire dans un processus de génération d'URL, les paramètres sont extraits de:

1) valeurs explicitement fournies

2) valeurs de la requête en cours

3) par défaut

Dans l'ordre que j'ai spécifié ci-dessus.

L'algorithme de correspondance sortante pour les itinéraires est compliqué, il est donc recommandé de définir explicitement tous les paramètres pour la demande, comme vous l'avez fait dans votre exemple

50
objectbox

Mon application définit explicitement les valeurs de route et ne veut pas de valeur magique de la demande actuelle. Je veux avoir le plein contrôle.

J'ai créé une extension qui coexiste avec ma collection de bibliothèques de routes. D'où le paramètre unique RouteValueDictionary. (Voir le commentaire de ma bibliothèque Route en bas)

Ici, je supprime toutes les valeurs de routage de la demande avant de générer une URL.

(Remarque: pour la partie array.contains ignorecase, voir: Comment puis-je rendre Array.Contains insensible à la casse sur un tableau de chaînes? )

public static string Action(this UrlHelper helper, 
                            RouteValueDictionary routeValues)
{
    RemoveRoutes(helper.RequestContext.RouteData.Values);

    string url = helper.Action(routeValues["Action"].ToString(), routeValues);
    return url;
}

public static void RemoveRoutes(RouteValueDictionary currentRouteData)
{
    List<string> keyList = new List<string>(currentRouteData.Keys);

    string[] ignore = new[] { "Area", "Controller", "Action" };
    foreach (string key in keyList)
    {
        if (!ignore.Contains(key, StringComparer.CurrentCultureIgnoreCase))
            currentRouteData.Remove(key);
    }
}

J'ai des méthodes d'extension Form et ActionLink qui utilisent la méthode RemoveRoutes. Aucun assistant dans ma bibliothèque mvc n'utilise une méthode qui n'est pas une méthode d'extension que j'ai créée. Ainsi, toutes les données routées sont nettoyées avant de générer des URL.

Pour référence, j'utilise AttributeRouting. Voici un exemple d'une route de ma bibliothèque de routes.

public static RouteValueDictionary DisplayNews(int newsId)
{
    RouteValueDictionary route = new RouteValueDictionary();
    route["Area"] = _area;
    route["Controller"] = _controller;
    route["Action"] = "DisplayNews";
    route["newsId"] = newsId;
    return route;
}
2
Valamas

Donc, quand j'ai lu la réponse de objectbox, j'ai pensé que je devrais modifier plusieurs liens dans mon code. J'ai ensuite essayé d'ajouter une route par défaut en omettant les paramètres par défaut qui ont résolu le problème:

routes.MapRoute(
    "ArtistArtworkDefPage",
    "Artist/{username}/Artwork",
    new
    {
        controller = "Artist",
        action = "Artwork",
        page = 1
    }
);

routes.MapRoute(
    "ArtistArtwork",
    "Artist/{username}/Artwork/{page}",
    new
    {
        controller = "Artist",
        action = "Artwork",
        page = 1
    },
    new { page = @"\d+" }
);
2
tmorell

Exemple simple:

public class ProductController : Controller
{
  public ActionResult Edit(int id)
  {
    return View();
  }

  [Route("Product/Detail/{id:int}")]
  public ActionResult Detail(int id)
  {
    return View();
  }
}

La vue d'édition ne contient que ceci:

@{ Layout = null;}
@Url.Action("Detail", "Cmr")

Ainsi, lorsque vous exécutez votre site, par exemple localhost:randomPort/Product/Edit/123 Vous obtenez la réponse suivante: /Product/Detail/123

Pourquoi? Parce que l'attribut Route a requis le paramètre id. Le paramètre id est lu à partir de l'url, bien que nous n'ayons écrit que Url.Action(methodName, controller) - sans spécifier de paramètre. De plus, cela n'a pas de sens d'avoir un détail de méthode sans id.

Pour que les attributs fonctionnent, la ligne suivante doit être ajoutée à RouteConfig.cs:

public static void RegisterRoutes(RouteCollection routes)
{
  ...
  routes.MapMvcAttributeRoutes();
  ...
}
2
broadband

Voici une solution de contournement qui ne nécessite pas de routage d'attribut, ni ne modifie les valeurs de route actuelles dans votre demande. Cette idée vient de http://notherdev.blogspot.ca/2013/10/aspnet-mvc-current-values-used-by-url-action.html , que j'ai légèrement modifié pour travailler sans MvcFutures.

J'ai construit ma propre extension Action sur UrlHelper avec un booléen supplémentaire qui vous permet d'ignorer éventuellement les valeurs de route actuelles (pour ne pas entrer en conflit avec les méthodes d'action existantes sur UrlHelper.)

Ce qu'il fait, c'est construire un RequestContext complètement nouveau à partir de l'actuel, en utilisant l'itinéraire actuel mais pas les valeurs de l'itinéraire actuel. Ensuite, nous transmettons cela aux aides de base. De cette façon, lorsque les assistants de base examineront les valeurs de l'itinéraire dans le contexte de la demande, il n'en trouvera aucune et ne les utilisera donc pas lors de la génération de l'URL.

public static string Action(this UrlHelper urlHelper, string actionName, string controllerName, object routeValues, bool ignoreCurrentRouteValues=false) {
    var routeValueDictionary = new RouteValueDictionary(routeValues);
    var requestContext = urlHelper.RequestContext;
    if (ignoreCurrentRouteValues) {
        var currentRouteData = requestContext.RouteData;
        var newRouteData = new RouteData(currentRouteData.Route, currentRouteData.RouteHandler);
        requestContext = new RequestContext(requestContext.HttpContext, newRouteData);
    }

    return UrlHelper.GenerateUrl(null, actionName, controllerName, routeValueDictionary,
        urlHelper.RouteCollection, requestContext, includeImplicitMvcValues: false);
}
1
Ber'Zophus

Une solution stupide que j'ai trouvée qui fonctionne pour gérer les données de routage de la page actuelle, y compris la modification du paramètre de page selon vos préférences

@{
    var current_route_1 = new RouteValueDictionary(Url.RequestContext.RouteData.Values);
    var current_route_2 = new RouteValueDictionary(Url.RequestContext.RouteData.Values);

    //If you want to customize the routing values
    current_route_1["controller"] = "Controller1";
    current_route_2["controller"] = "Controller2";
}

@Url.RouteUrl(current_route_1);
@Url.RouteUrl(current_route_2);
0
Mahdi Fathalla

Une solution simple serait d'appeler en premier

Url.Action("dummy", new { ... }) 

puis renommez le mannequin dans la chaîne résultante au nom d'action correct.

0
Martin Staufcik