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


327

Мені цікаво дізнатися, чи можете ви перевантажувати методи контролера в ASP.NET MVC. Кожен раз, коли я намагаюся, я отримую помилку нижче. Два способи приймають різні аргументи. Це щось таке, чого неможливо зробити?

Поточний запит на дію "MyMethod" для контролера типу "MyController" неоднозначний між такими методами дій:


10
@andy це те ж саме для mvc 4 також :)
basarat

10
І те саме для mvc 5
DhruvJoshi

10
І те саме для mvc 6
Імад

7
І те саме для MVC Core 1.1
kall2sollies

7
І те саме для MVC Core 2.0
Guilherme

Відповіді:


201

Ви можете використовувати атрибут, якщо хочете, щоб ваш код перевантажував.

[ActionName("MyOverloadedName")]

Але вам доведеться використовувати інше ім’я дії для того ж методу http (як уже говорили інші). Тож це просто семантика на той момент. Ви б хотіли мати ім’я у своєму коді чи атрибуті?

Філ має статтю, пов’язану з цим: http://haacked.com/archive/2008/08/29/how-a-method-beasures-an-action.aspx


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 )
{
   ...
}

Я маю одну пропозицію, що для подібного випадку буде приватна реалізація, на яку обидва ваші публічні методи дії покладаються, щоб уникнути дублювання коду.


1
З MVC2 і вище можна також використовувати атрибут HttpPost / HttpGet
yoel halb

@yohal Так, це був би канонічний спосіб вирішити це зараз, якщо вам не потрібно підтримувати кілька дієслів.
tvanfosson

3
Тільки остерігайтеся не зловживати цим, щоб порушити принципи REST.
Фред

1
Досить впевнений, що це працює лише тому, що ваші Show()методи мають різні підписи. Якщо і коли вам потрібно надіслати інформацію у версію Get, версії Get і Post надходять з тим же підписом, і вам буде потрібен ActionNameатрибут або один з інших виправлень, згаданих у цій публікації.
Скотт Фрейлі

1
@ ScottK.Fraley це правда. Якщо їм потрібен той самий підпис, вам доведеться по-іншому назвати їх і застосовувати ActionNameAttribute. На практиці я рідко зустрічав це так.
tvanfosson

42

Ось ще щось, що ви могли зробити ... ви хочете, щоб метод, який здатний мати параметр, а ні.

Чому б не спробувати це ...

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

Це працювало для мене ... і в цьому одному методі ви можете фактично перевірити, чи є у вас вхідний параметр.


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


6
( stringне може бути зведеним нанівець.)
Джош М.

23
рядок може бути мінливою. Насправді це вже зводиться до ладу, просто не потрібне "?"
ПрофК

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 Action


Дякую за це Я думаю, ви можете просто використовувати іншу назву дії з самого початку, а не використовувати атрибут.
Дан

1
@Dan, але тоді у нас немає поліморфізму на стороні C #.
Шивпрасад Койрала

Ви маєте рацію, немає перевантаженого методу контролера, але це нічого спільного з HTTP.
Крейда

Дякуємо за роз’яснення +1. Слід думати більше HTTP, а не C #. Немає підстав підходити до дій із стратегією ОО.

15

Щоб подолати цю проблему, ви можете написати документ, 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

Я домігся цього за допомогою маршрутизації атрибутів у 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)
{
    //.....
}

Сподіваємось, це допомагає і не веде когось неправильним шляхом. :-)


Хороша робота! Я щойно зіткнувся з цим питанням, ти врятував мене! У мене також "x" роки з WebForms - тому все ще дуже сильно крива навчання. Не можу знайти роботу без MVC зараз-а-ха-ха
Тез Вінгфілд

4

Ви можете використовувати сингл, ActionResultщоб мати справу з обома Postта Get:

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

Корисно, якщо у ваших Getі Postметодах є відповідні підписи.


1
Хм, начебто знову вигадуючи колесо, але цього разу у квадратній формі. Чому б просто не використовувати атрибути [HttpPost / Get]?
SOReader

минуло деякий час, але я думаю, що я це зробив, оскільки MVC не розрізняв два окремі методи з відповідними знаками. Я використовував атрибут HttpPost, хоча я не ставлю HttpGet іншим методом ..
DevDave

@DevDave, а також привласнюючи обидва методи, переконайтеся, що ви використовуєте атрибути від system.web.mvc - а не ті, що з system.web.http!
Крейда

4

Я щойно стикався з цим питанням, і, хоча воно вже досить давнє, воно все ще дуже актуальне. За іронією долі, єдиний правильний коментар у цій темі опублікував самозваний новачок у MVC, коли написав повідомлення. Навіть документи ASP.NET не зовсім коректні. У мене великий проект, і я успішно перевантажую методи дій.

Якщо хтось розуміє маршрутизацію, що перевищує просту схему маршруту за замовчуванням {kontroler} / {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 , які відображатимуть відповідно до першої та другої дій Index.

Розглядаючи цей контролер прикладу, очевидно, що він виходить за рамки згаданого вище схеми маршруту за замовчуванням. За замовчуванням добре працює, якщо ваша URL-адреса точно відповідає умовам іменування коду, але це не завжди так. Код повинен бути описовим домену, але для URL-адрес часто потрібно йти далі, оскільки їхній вміст має базуватися на інших критеріях, таких як вимоги до SEO.

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

Важлива примітка . Одним недоліком є ​​те, що використання маршрутизації для створення URL-адрес для перевантажених дій не працює, якщо ґрунтуватися на імені дії, наприклад, при використанні UrlHelper.Action. Але це працює, якщо використовуються названі маршрути, наприклад, UrlHelper.RouteUrl. І з використанням названих маршрутів, згідно з шанованими джерелами, це шлях у будь-який спосіб ( http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/ ).

Удачі!


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

Я зіткнувся з тим же питанням і в своїй заяві. Без Modifiyig жодної інформації про метод, я надав [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()

Редагувати: Це, очевидно, застосовується лише в тому випадку, якщо метод переопределення є похідним класом, який, здається, не був наміром ОП.


2
Ви, мабуть, нерозумієте питання. ОП запитує про перевантаження методу в одному контролері, не переосмислюючи його у похідному класі.
Туз

@Andiih: що буде, якщо обидва способи знаходяться в одному контролері?
Дхармік Бхандарі


0

Для кожного методу контролера дозволена лише одна публічна підпис. Якщо ви спробуєте перевантажити її, вона скомпілюється, але ви отримуєте помилку під час виконання роботи.

Якщо ви не бажаєте використовувати різні дієслова (наприклад, [HttpGet]і[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 потрібні два параметри, а DeleteAction - лише один.


0

Вибачте за затримку. Я був з тією ж проблемою, і я знайшов посилання з хорошими відповідями, чи може це допомогти новим хлопцям

Усі кредити для веб-сайту BinaryIntellect та авторів

В основному, є чотири ситуації: використання дієслів різниць , використання маршрутизації , маркування перевантаження за допомогою атрибута [NoAction] та зміна назви атрибута дії на [ActionName]

Отже, залежить, це ваші вимоги та ваша ситуація.

Як би там не було, перейдіть за посиланням:

Посилання: http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx


-1

Якщо це спроба використання однієї дії GET для декількох переглядів, що POST для декількох дій з різними моделями, то спробуйте додати дію GET для кожної дії POST, яка переспрямовує на першу GET, щоб запобігти оновленню 404.

Далекий, але загальний сценарій.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.