Неоднозначные методы действия 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 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) { ... }
Ответы
Ответ 1
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; }
}
В приведенном выше примере атрибут просто говорит: "Этот метод совпадает, если ключ xxx присутствовал в запросе". Вы также можете фильтровать информацию, содержащуюся в маршруте (controllerContext.RequestContext), если это лучше подходит для ваших целей.
Ответ 2
Параметры в ваших маршрутах {roleId}
, {applicationName}
и {roleName}
не соответствуют именам параметров в ваших методах действий. Я не знаю, имеет ли это значение, но это затрудняет определение ваших намерений.
Соответствует ли ваш itemId шаблону, который можно сопоставить с помощью регулярного выражения? Если это так, то вы можете добавить ограничение на свой маршрут, чтобы только URL-адрес, соответствующий шаблону, был идентифицирован как содержащий itemId.
Если ваш itemId содержит только цифры, это будет работать:
routes.MapRoute("AssignRemove",
"Items/{action}/{itemId}",
new { controller = "Items" },
new { itemId = "\d+" }
);
Изменить: вы также можете добавить ограничение на маршрут AssignRemovePretty
, чтобы потребовались как {parentName}
, так и {itemName}
.
Edit 2: Кроме того, поскольку ваше первое действие просто перенаправляется на ваше второе действие, вы можете удалить некоторую двусмысленность, переименовав первый.
// Method #1
public ActionResult AssignRemovePretty(string parentName, string itemName) {
// Logic to retrieve item 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+" }
);
Ответ 3
Другой подход - переименовать один из методов, чтобы не было конфликта. Например
// GET: /Movies/Delete/5
public ActionResult Delete(int id = 0)
// POST: /Movies/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
См. http://www.asp.net/mvc/tutorials/getting-started-with-mvc3-part9-cs
Ответ 4
routes.MapRoute("AssignRemove",
"Items/{parentName}/{itemName}",
new { controller = "Items", action = "Assign" }
);
рассмотрите возможность использования библиотеки маршрутов испытаний MVC Contribs для проверки маршрутов.
"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));