2009-06-25 3 views
117

У меня есть два метода действий, которые противоречат друг другу. В принципе, я хочу иметь возможность перейти к одному и тому же виду, используя два разных маршрута, либо по идентификатору элемента, либо по названию объекта и его родителям (элементы могут иметь одинаковое имя для разных родителей). Для фильтрации списка можно использовать термин поиска.ASP.NET MVC неоднозначные методы действия

Например ...

Items/{action}/ParentName/ItemName 
Items/{action}/1234-4321-1234-4321 

Вот мои методы действий (существуют также Remove методы действия) ...

// Method #1 
public ActionResult Assign(string parentName, string itemName) { 
    // Logic to retrieve item's ID here... 
    string itemId = ...; 
    return RedirectToAction("Assign", "Items", new { itemId }); 
} 

// Method #2 
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... } 

А вот маршруты ...

routes.MapRoute("AssignRemove", 
       "Items/{action}/{itemId}", 
       new { controller = "Items" } 
       ); 

routes.MapRoute("AssignRemovePretty", 
       "Items/{action}/{parentName}/{itemName}", 
       new { controller = "Items" } 
       ); 

Я понимаю, почему возникает ошибка, так как параметр page может быть нулевым, но я не могу найти оптимальный способ его решения. С чего начать? Я думал о расширении подписки Method #1, чтобы включить параметры поиска и переместить логику в Method #2 на частный метод, который они оба вызывали, но я не верю, что это действительно устранит двусмысленность.

Любая помощь была бы принята с благодарностью.


Фактическое решение (основано на ответ Леви)

Я добавил следующий класс ...

public class RequireRouteValuesAttribute : ActionMethodSelectorAttribute { 
    public RequireRouteValuesAttribute(string[] valueNames) { 
     ValueNames = valueNames; 
    } 

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { 
     bool contains = false; 
     foreach (var value in ValueNames) { 
      contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value); 
      if (!contains) break; 
     } 
     return contains; 
    } 

    public string[] ValueNames { get; private set; } 
} 

А потом украсили методы действий ...

[RequireRouteValues(new[] { "parentName", "itemName" })] 
public ActionResult Assign(string parentName, string itemName) { ... } 

[RequireRouteValues(new[] { "itemId" })] 
public ActionResult Assign(string itemId) { ... } 
+3

Спасибо за публикацию фактической реализации. Это помогает людям с подобными проблемами. Как и сегодня. :-P –

+4

Удивительно! Предложение незначительного изменения: (imo действительно полезно) 1) params string [] valueNames, чтобы сделать объявление атрибута более кратким и (предпочтение) 2) заменить тело метода IsValidForRequest на 'return ValueNames.All (v => controllerContext.RequestContext.RouteData. Values.ContainsKey (v)); ' –

+0

Привет, Джон, я думаю, что я ничего не беру, потому что где параметры запроса в RouteData? – fravelgue

ответ

161

MVC не поддерживает перегрузку метода, основанную исключительно на сигнатуре, поэтому это не будет выполнено:

public ActionResult MyMethod(int someInt) { /* ... */ } 
public ActionResult MyMethod(string someString) { /* ... */ } 

Однако делает метод поддержки перегружать на основе атрибута:

[RequireRequestValue("someInt")] 
public ActionResult MyMethod(int someInt) { /* ... */ } 

[RequireRequestValue("someString")] 
public ActionResult MyMethod(string someString) { /* ... */ } 

public class RequireRequestValueAttribute : ActionMethodSelectorAttribute { 
    public RequireRequestValueAttribute(string valueName) { 
     ValueName = valueName; 
    } 
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { 
     return (controllerContext.HttpContext.Request[ValueName] != null); 
    } 
    public string ValueName { get; private set; } 
} 

В приведенном выше примере, атрибут просто говорит «этот метод подходит, если ключ ххх присутствовал в запрос." Вы также можете фильтровать информацию, содержащуюся в маршруте (controllerContext.RequestContext), если это лучше подходит для ваших целей.

+0

Это оказалось тем, что мне было нужно. Как вы предположили, мне нужно было использовать controllerContext.RequestContext. –

+3

Ницца! Я еще не видел атрибут RequireRequestValue. Это хорошо знать. – CoderDennis

+1

мы можем использовать valueprovider для получения значений из нескольких источников, таких как: controllerContext.Controller.ValueProvider.GetValue (значение); –

7

Параметры на вашем маршруте {roleId}, {applicationName} и {roleName} не соответствуют именам параметров в ваших методах действий. Я не знаю, имеет ли это значение, но это делает сложнее выяснить, каково ваше намерение.

Соответствует ли ваш itemId шаблону, который может быть сопоставлен с помощью регулярных выражений? Если это так, то вы можете добавить ограничение на свой маршрут, чтобы только URL-адрес, соответствующий шаблону, был идентифицирован как содержащий itemId.

Если Itemid содержали только цифры, то это будет работать:

routes.MapRoute("AssignRemove", 
       "Items/{action}/{itemId}", 
       new { controller = "Items" }, 
       new { itemId = "\d+" } 
       ); 

Edit: Вы можете также добавить ограничение на AssignRemovePretty маршрут так, что оба {parentName} и {itemName} требуется.

Редактировать 2: Также, поскольку ваше первое действие просто перенаправляется на ваше второе действие, вы можете удалить некоторую двусмысленность, переименовав первый.

// Method #1 
public ActionResult AssignRemovePretty(string parentName, string itemName) { 
    // Logic to retrieve item's ID here... 
    string itemId = ...; 
    return RedirectToAction("Assign", itemId); 
} 

// Method #2 
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... } 

Затем укажите имена действий в ваших маршрутов, чтобы заставить правильный метод будет называться:

routes.MapRoute("AssignRemove", 
       "Items/Assign/{itemId}", 
       new { controller = "Items", action = "Assign" }, 
       new { itemId = "\d+" } 
       ); 

routes.MapRoute("AssignRemovePretty", 
       "Items/Assign/{parentName}/{itemName}", 
       new { controller = "Items", action = "AssignRemovePretty" }, 
       new { parentName = "\w+", itemName = "\w+" } 
       ); 
+1

Извините, Деннис, параметры действительно совпадают. Я задал вопрос. Я попробую ограничить регулярное выражение и вернусь к вам. Благодаря! –

+0

Ваше второе редактирование помогло мне, но в конечном итоге это предложение Леви опечатало сделку. Еще раз спасибо! –

0
routes.MapRoute("AssignRemove", 
       "Items/{parentName}/{itemName}", 
       new { controller = "Items", action = "Assign" } 
       ); 

рассмотреть возможность использования MVC вклад испытательные трассы библиотеку, чтобы проверить ваши маршруты

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName)); 
1

Недавно я воспользовался возможностью, чтобы улучшить @ ответ Леви для поддержки более широкого спектра сценариев я должен был иметь дело, например, как: поддержка нескольких параметров, соответствует ни одному из них (а не их всех) и даже не соответствуют ни одному из них.

Вот атрибут я использую сейчас:

/// <summary> 
/// Flags an Action Method valid for any incoming request only if all, any or none of the given HTTP parameter(s) are set, 
/// enabling the use of multiple Action Methods with the same name (and different signatures) within the same MVC Controller. 
/// </summary> 
public class RequireParameterAttribute : ActionMethodSelectorAttribute 
{ 
    public RequireParameterAttribute(string parameterName) : this(new[] { parameterName }) 
    { 
    } 

    public RequireParameterAttribute(params string[] parameterNames) 
    { 
     IncludeGET = true; 
     IncludePOST = true; 
     IncludeCookies = false; 
     Mode = MatchMode.All; 
    } 

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) 
    { 
     switch (Mode) 
     { 
      case MatchMode.All: 
      default: 
       return (
        (IncludeGET && ParameterNames.All(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        || (IncludePOST && ParameterNames.All(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        || (IncludeCookies && ParameterNames.All(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
      case MatchMode.Any: 
       return (
        (IncludeGET && ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        || (IncludePOST && ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        || (IncludeCookies && ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
      case MatchMode.None: 
       return (
        (!IncludeGET || !ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p))) 
        && (!IncludePOST || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p))) 
        && (!IncludeCookies || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p))) 
        ); 
     } 
    } 

    public string[] ParameterNames { get; private set; } 

    /// <summary> 
    /// Set it to TRUE to include GET (QueryStirng) parameters, FALSE to exclude them: 
    /// default is TRUE. 
    /// </summary> 
    public bool IncludeGET { get; set; } 

    /// <summary> 
    /// Set it to TRUE to include POST (Form) parameters, FALSE to exclude them: 
    /// default is TRUE. 
    /// </summary> 
    public bool IncludePOST { get; set; } 

    /// <summary> 
    /// Set it to TRUE to include parameters from Cookies, FALSE to exclude them: 
    /// default is FALSE. 
    /// </summary> 
    public bool IncludeCookies { get; set; } 

    /// <summary> 
    /// Use MatchMode.All to invalidate the method unless all the given parameters are set (default). 
    /// Use MatchMode.Any to invalidate the method unless any of the given parameters is set. 
    /// Use MatchMode.None to invalidate the method unless none of the given parameters is set. 
    /// </summary> 
    public MatchMode Mode { get; set; } 

    public enum MatchMode : int 
    { 
     All, 
     Any, 
     None 
    } 
} 

Для получения дополнительной информации и как к образцам реализации вы также можете прочитать this post.

Смежные вопросы