Можете ли вы перегружать методы контроллера в ASP.NET MVC?

327

Мне интересно посмотреть, можно ли перегрузить методы контроллера в ASP.NET MVC. Всякий раз, когда я пытаюсь, я получаю ошибку ниже. Два метода принимают разные аргументы. Это то, что нельзя сделать?

Текущий запрос на действие «MyMethod» для контроллера типа «MyController» неоднозначен между следующими методами действия:

Папа бордовый
источник
10
@ и это тоже самое для mvc 4 :)
Басарат
10
И то же самое для MVC 5
DhruvJoshi
10
И то же самое для mvc 6
Imad
7
И то же самое для MVC Core 1.1
kall2sollies
7
И то же самое для MVC Core 2.0
Гильерме

Ответы:

201

Вы можете использовать атрибут, если хотите, чтобы ваш код перегружался.

[ActionName("MyOverloadedName")]

Но вам придется использовать другое имя действия для того же метода http (как уже говорили другие). Так что это просто семантика на тот момент. Вы бы предпочли, чтобы имя было в вашем коде или атрибуте?

У Фила есть статья, связанная с этим: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx

JD Конли
источник
5
Основным недостатком использования этого и перегрузки вашего действия является то, что оно больше не может быть обработано одним и тем же файлом представления.
Джефф Мартин
66
На самом деле, он все еще может отображать тот же файл представления. Вам просто нужно указать имя представления вместо слепого вызова return View();. Например: return View("MyOverloadedName");.
EAMann
1
@JD, но Microsoft говорит ... Метод, используемый в качестве действия контроллера, не может быть перегружен .. Вы можете увидеть это здесь .. asp.net/mvc/tutorials/controllers-and-routing/…
himanshupareek66
@EAMann Хорошо, я всегда определял весь путь для представления до сих пор
Александр Дерк
69

Да. Я смог сделать это, установив HttpGet/ HttpPost(или эквивалентный AcceptVerbsатрибут) для каждого метода контроллера на что-то отдельное, то есть, HttpGetили HttpPost, но не оба. Таким образом, он может сказать, основываясь на типе запроса, какой метод использовать.

[HttpGet]
public ActionResult Show()
{
   ...
}

[HttpPost]
public ActionResult Show( string userName )
{
   ...
}

Одно из предложений, которое у меня есть, заключается в том, что для подобного случая будет иметь частную реализацию, на которую полагаются оба ваших открытых метода Action, чтобы избежать дублирования кода.

tvanfosson
источник
1
С MVC2 и выше можно также использовать атрибут HttpPost / HttpGet
yoel halb 13.12.12
@yohal Да, это был бы канонический способ справиться с этим сейчас, если вам не нужно поддерживать несколько глаголов.
tvanfosson
3
Просто будьте осторожны, чтобы не злоупотреблять этим, чтобы нарушать принципы ОТДЫХА.
Фред
1
Уверен, это работает только потому, что ваши Show()методы имеют разные подписи. Если и когда вам нужно будет отправить информацию в версию Get, ваши версии Get и Post в итоге будут иметь одинаковую подпись, и вам понадобится ActionNameатрибут или одно из других исправлений, упомянутых в этом посте.
Скотт Фрэйли
1
@ ScottK. Фрейли, это правда. Если бы им нужна была одна и та же подпись, вы бы назвали их по-разному и применили ActionNameAttribute. На практике я редко обнаруживал, что это так.
tvanfosson
42

Вот еще что вы могли бы сделать ... вы хотите метод, который может иметь параметр, а не.

Почему бы не попробовать это ...

public ActionResult Show( string username = null )
{
   ...
}

Это сработало для меня ... и в этом одном методе вы можете на самом деле проверить, есть ли у вас входящий параметр.


Обновлен, чтобы удалить недопустимый синтаксис, допускающий использование значения NULL, и использовать значение параметра по умолчанию.

Фаррел
источник
6
( stringне может быть обнуляемым.)
Джош М.
23
Строка может быть обнуляемой. На самом деле, он уже обнуляем, просто не нуждается в '?'
ProfK
9
@ProfK - Нет, строка является ссылочным типом, который может быть нулевым. Это не "обнуляемый". Nullable означает, что вы используете Nullable <T> (то есть T?). Дело в том, что Джош не может поставить? после строки, потому что это не тип значения, а Nullable <T> принимает только типы значений.
Эрик Фанкенбуш
4
Я случайно нашел свой путь назад к этому вопросу и затем понял, что разместил комментарий выше. Никаких воспоминаний об этом ... странно! Это все еще правда, что stringне может быть nullable; но это может быть null! Так или иначе я отправил начальный комментарий без искренности.
Джош М.
20

Нет, Нет и Нет. Идите и попробуйте код контроллера ниже, где у нас перегружен «LoadCustomer».

public class CustomerController : Controller
    {
        //
        // GET: /Customer/

        public ActionResult LoadCustomer()
        {
            return Content("LoadCustomer");
        }
        public ActionResult LoadCustomer(string str)
        {
            return Content("LoadCustomer with a string");
        }
    }

Если вы попытаетесь вызвать действие «LoadCustomer», вы получите ошибку, как показано на рисунке ниже.

введите описание изображения здесь

Полиморфизм является частью программирования на C #, а HTTP является протоколом. HTTP не понимает полиморфизм. HTTP работает над концепцией или URL, а URL может иметь только уникальные имена. Так что HTTP не реализует полиморфизм.

Чтобы исправить это, нам нужно использовать атрибут «ActionName».

public class CustomerController : Controller
    {
        //
        // GET: /Customer/

        public ActionResult LoadCustomer()
        {
            return Content("LoadCustomer");
        }

        [ActionName("LoadCustomerbyName")]
        public ActionResult LoadCustomer(string str)
        {
            return Content("LoadCustomer with a string");
        }
    }

Так что теперь, если вы сделаете вызов URL «Customer / LoadCustomer», будет вызвано действие «LoadCustomer», а со структурой URL «Customer / LoadCustomerByName» будет вызвано «LoadCustomer (string str)».

введите описание изображения здесь

введите описание изображения здесь

Ответ выше я взял из этой статьи codeproject -> Перегрузка действия MVC

Шивпрасад Койрала
источник
Спасибо за это. Я полагаю, вы также можете просто использовать другое имя действия с самого начала, а не использовать атрибут.
Дан
1
@ Дан, но у нас нет полиморфизма на стороне C #.
Shivprasad Koirala
Вы правы, нет перегрузки метода контроллера, но это никак не связано с HTTP.
Мел
Спасибо за разъяснение. +1. Должен думать больше HTTP, а не C #. Нет причин подходить к действиям с помощью ОО-стратегии.
15

Чтобы преодолеть эту проблему, вы можете написать a, ActionMethodSelectorAttributeкоторый проверяет MethodInfoдля каждого действия и сравнивает его с опубликованными значениями формы, а затем отклоняет любой метод, для которого значения формы не совпадают (конечно, за исключением имени кнопки).

Вот пример: - http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

НО, это не очень хорошая идея.

Ян Мерсер
источник
@Cerbrus, потому что это ужасный взлом, и следующий человек, который смотрит на код вашего контроллера, будет смущен очень нестандартным подходом.
Ян Мерсер
Хех, достаточно справедливо.
Цербр
14

Насколько я знаю, вы можете использовать один и тот же метод только при использовании различных методов http.

т.е.

[AcceptVerbs("GET")]
public ActionResult MyAction()
{

}

[AcceptVerbs("POST")]
public ActionResult MyAction(FormResult fm)
{

}
Кини
источник
2
украшения не имеют ничего общего с перегрузкой. это список параметров, который позволяет перегрузить.
Sky Sanders
@SkySanders Я не согласен, перегрузка на основе параметров не работает в методах контроллера MVC - есть ли у вас рабочий пример этого? Приветствия.
Мел
Используйте [HttpPost]атрибут вместо [AcceptVerbs("POST")].
Фред
9

Я добился этого с помощью Attribute Routing в MVC5. По общему признанию я новичок в MVC, пришедшем из десятилетия веб-разработки с использованием WebForms, но мне помогло следующее. В отличие от принятого ответа, это позволяет отображать все перегруженные действия одним и тем же файлом представления.

Сначала включите маршрутизацию атрибутов в App_Start / RouteConfig.cs.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapMvcAttributeRoutes();

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );            
    }
}

При желании украсьте свой класс контроллера префиксом маршрута по умолчанию.

[RoutePrefix("Returns")]
public class ReturnsController : BaseController
{
    //.......

Затем украсьте действия вашего контроллера, которые перегружают друг друга, общим маршрутом и параметрами, которые вам подходят. Используя ограниченные по типу параметры, вы можете использовать один и тот же формат URI с идентификаторами разных типов.

[HttpGet]
// Returns
public ActionResult Index()
{
    //.....
}

[HttpGet]
[Route("View")]
// Returns/View
public ActionResult View()
{
    // I wouldn't really do this but it proves the concept.
    int id = 7026;
    return View(id);
}

[HttpGet]
[Route("View/{id:int}")]
// Returns/View/7003
public ActionResult View(int id)
{
    //.....
}

[HttpGet]
[Route("View/{id:Guid}")]
// Returns/View/99300046-0ba4-47db-81bf-ba6e3ac3cf01
public ActionResult View(Guid id)
{
    //.....
}

Надеюсь, что это помогает и не ведет кого-то по ложному пути. :-)

cookdn
источник
Хорошая работа! Я только столкнулся с этой проблемой, ты спас меня! У меня также есть "х" лет с WebForms - так что еще очень много обучения. Не могу устроиться на работу без MVC, ха-ха-ха
Tez Wingfield
4

Вы можете использовать один , ActionResultчтобы иметь дело с обоими Postи Get:

public ActionResult Example() {
   if (Request.HttpMethod.ToUpperInvariant() == "GET") {
    // GET
   }
   else if (Request.HttpMethod.ToUpperInvariant() == "POST") {
     // Post  
   }
}

Полезно, если у ваших методов Getи Postметодов совпадают подписи.

DevDave
источник
1
Хм, добрый - заново изобретаем колесо, но уже в форме квадрата. Почему бы просто не использовать атрибуты [HttpPost / Get]?
SOReader
это было некоторое время, но я думаю, что сделал это, потому что MVC не различал два отдельных метода с совпадающими сигмами. Я использовал атрибут HttpPost, хотя не использовал HttpGet для другого метода ..
DevDave
@DevDave, а также приписывая оба метода, убедитесь, что вы используете атрибуты из system.web.mvc, а не атрибуты из system.web.http!
Мел
4

Я только что натолкнулся на этот вопрос и, хотя сейчас он довольно старый, он все еще очень актуален. По иронии судьбы, один правильный комментарий в этой теме был опубликован новичком в MVC, который сам признался, когда писал эту статью. Даже документы ASP.NET не совсем корректны. У меня большой проект, и я успешно перегружаю методы действий.

Если кто-то понимает маршрутизацию, помимо простого шаблона маршрута по умолчанию {controller} / {action} / {id}, может быть очевидно, что действия контроллера могут быть отображены с использованием любого уникального шаблона. Кто-то здесь говорил о полиморфизме и сказал: «HTTP не понимает полиморфизм», но маршрутизация не имеет ничего общего с HTTP. Проще говоря, это механизм для сопоставления строковых шаблонов.

Лучший способ сделать это - использовать атрибуты маршрутизации, например:

[RoutePrefix("cars/{country:length(3)}")]
public class CarHireController
{
    [Route("{location}/{page:int=1}", Name = "CarHireLocation")]
    public ActionResult Index(string country, string location, int page)
    {
        return Index(country, location, null, page);
    }

    [Route("{location}/{subLocation}/{page:int=1}", Name = "CarHireSubLocation")]
    public ActionResult Index(string country, string location, string subLocation, int page)
    {
        //The main work goes here
    }
}

Эти действия будут относиться к URL-адресам, таким как /cars/usa/new-yorkи /cars/usa/texas/dallas, которые будут отображаться в первом и втором индексных действиях соответственно.

Изучив этот пример контроллера, вы увидите, что он выходит за рамки шаблона маршрута по умолчанию, упомянутого выше. Значение по умолчанию хорошо работает, если ваша структура URL точно соответствует соглашениям об именах кода, но это не всегда так. Код должен описывать домен, но URL-адреса часто должны идти дальше, потому что их содержание должно основываться на других критериях, таких как требования SEO.

Преимущество шаблона маршрутизации по умолчанию заключается в том, что он автоматически создает уникальные маршруты. Это обеспечивается компилятором, поскольку URL-адреса будут соответствовать уникальным типам и членам контроллера. Прокрутка собственных шаблонов маршрутов потребует тщательного обдумывания, чтобы гарантировать уникальность и то, что они работают.

Важное замечание Единственный недостаток заключается в том, что использование маршрутизации для генерации URL-адресов для перегруженных действий не работает, если основано на имени действия, например, при использовании UrlHelper.Action. Но это работает, если использовать именованные маршруты, например, UrlHelper.RouteUrl. И использование именованных маршрутов - это, согласно хорошо уважаемым источникам, путь в любом случае ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Удачи!

DVS
источник
3

Вы можете использовать [ActionName ("NewActionName")], чтобы использовать тот же метод с другим именем:

public class HomeController : Controller
{
    public ActionResult GetEmpName()
    {
        return Content("This is the test Message");
    }

    [ActionName("GetEmpWithCode")]
    public ActionResult GetEmpName(string EmpCode)
    {
        return Content("This is the test Messagewith Overloaded");
    }
}
Алекс Бутенко
источник
2

Мне нужна была перегрузка для:

public ActionResult Index(string i);
public ActionResult Index(int groupId, int itemId);

Было несколько достаточно аргументов, где я в конечном итоге сделал это:

public ActionResult Index(string i, int? groupId, int? itemId)
{
    if (!string.IsNullOrWhitespace(i))
    {
        // parse i for the id
    }
    else if (groupId.HasValue && itemId.HasValue)
    {
        // use groupId and itemId for the id
    }
}

Это не идеальное решение, особенно если у вас много аргументов, но оно хорошо работает для меня.

Кейси Спикман
источник
1

Я столкнулся с той же проблемой в моем приложении тоже. Без изменения какой-либо информации о методе я предоставил [ActionName ("SomeMeaningfulName")] в заголовке Action. проблема решена

[ActionName("_EmployeeDetailsByModel")]
        public PartialViewResult _EmployeeDetails(Employee model)
        {
            // Some Operation                
                return PartialView(model);
            }
        }

[ActionName("_EmployeeDetailsByModelWithPagination")]
        public PartialViewResult _EmployeeDetails(Employee model,int Page,int PageSize)
        {

                // Some Operation
                return PartialView(model);

        }
ಅನಿಲ್
источник
0

Создайте базовый метод как виртуальный

public virtual ActionResult Index()

Создайте переопределенный метод как переопределение

public override ActionResult Index()

Редактировать: Очевидно, что это применимо, только если метод переопределения находится в производном классе, который, как представляется, не был намерением OP.

Andiih
источник
2
Вы, вероятно, неправильно поняли вопрос. OP запрашивает перегрузку метода в том же контроллере, а не переопределение его в производном классе.
Ace
@Andiih: что произойдет, если оба метода находятся в одном контроллере?
Дхармик Бхандари
0

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

Если вы не хотите использовать различные глаголы (например, атрибуты [HttpGet]and [HttpPost]) для дифференциации перегруженных методов (которые будут работать) или изменения маршрутизации, то остается только предоставить другой метод с другим именем или отправка внутри существующего метода. Вот как я это сделал:

Однажды я попал в ситуацию, когда мне пришлось поддерживать обратную совместимость. Первоначальный метод предполагал два параметра, а новый - только один. Перегрузка, как я ожидал, не сработала, потому что MVC больше не находил точку входа.

Чтобы решить эту проблему, я сделал следующее:

  1. Изменены 2 перегруженных метода действий с публичных на приватные
  2. Создан один новый публичный метод, который содержит «просто» 2 строковых параметра. Тот действовал как диспетчер, то есть:

    public ActionResult DoSomething(string param1, string param2)
    {
        if (string.IsNullOrEmpty(param2))
        {
            return DoSomething(ProductName: param1);
        }
        else
        {
            int oldId = int.Parse(param1);
            return DoSomething(OldParam: param1, OldId: oldId);
        }
    }
    
    
    private ActionResult DoSomething(string OldParam, int OldId)
    {
        // some code here
        return Json(result);
    }
    
    
    private ActionResult DoSomething(string ProductName)
    {
        // some code here
        return Json(result);
    }

Конечно, это взломать, и его следует изменить позже. Но пока это работает для меня.

Вы также можете создать диспетчер, как:

public ActionResult DoSomething(string action, string param1, string param2)
{
    switch (action)
    {
        case "update":
            return UpdateAction(param1, param2);
        case "remove":
            return DeleteAction(param1);
    }
}

Вы можете видеть, что для UpdateAction нужны 2 параметра, а для DeleteAction - один.

Matt
источник
0

Извините за задержку. У меня была такая же проблема, и я нашел ссылку с хорошими ответами, может это поможет новым парням

Все кредиты для сайта BinaryIntellect и авторов

По сути, существует четыре ситуации: использование разных глаголов , использование маршрутизации , маркировка перегрузки с помощью атрибута [NoAction] и изменение имени атрибута действия с помощью [ActionName]

Так что, это зависит от ваших требований и вашей ситуации.

Как бы то ни было, перейдите по ссылке:

Ссылка: http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx

Эрик Сабойя
источник
-1

Если это попытка использовать одно действие GET для нескольких представлений, выполняющих POST для нескольких действий с разными моделями, попробуйте добавить действие GET для каждого действия POST, которое перенаправляет на первый GET, чтобы предотвратить обновление 404 при обновлении.

Длинный выстрел, но общий сценарий.

Панос Родитакис
источник