您可以在ASP.NET MVC中重载控制器方法吗?


327

我很好奇您是否可以在ASP.NET MVC中重载控制器方法。每当我尝试时,都会出现以下错误。这两种方法接受不同的参数。这是无法完成的事情吗?

在以下操作方法之间,当前对控制器类型“ MyController”的操作“ MyMethod”的请求不明确:


10
@andy对于mvc 4也是如此:)
basarat

10
而同为MVC 5
DhruvJoshi

10
与mvc 6相同
-Imad

7
与MVC Core 1.1相同
-kall2sollies

7
与MVC Core 2.0相同
Guilherme

Answers:


201

如果希望代码进行重载,则可以使用该属性。

[ActionName("MyOverloadedName")]

但是,对于相同的http方法,您必须使用不同的操作名称(就像其他人所说的那样)。所以这只是语义。您希望在代码或属性中使用名称吗?

菲尔(Phil)有与此相关的文章:http : //haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx


5
使用此方法并使操作超载的主要缺点是它不再可以由同一视图文件呈现。
杰夫·马丁

66
实际上,它仍然可以呈现相同的视图文件。您只需要指定视图的名称即可,而不是盲目调用return View();。例如:return View("MyOverloadedName");
EAMann 2011年

1
@JD但微软称..用作控制器动作的方法不能超载。你可以在这里看到它.. asp.net/mvc/tutorials/controllers-and-routing/...
himanshupareek66

@EAMann尼斯,到目前为止,我一直在定义视图的整个路径
Alexander Derck

69

是。通过将每个控制器方法的HttpGet/ HttpPost(或等效AcceptVerbs属性)设置为不同的值(即HttpGetor HttpPost,但不能同时设置两者),我已经能够做到这一点。这样,它可以根据请求的类型确定使用哪种方法。

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

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

我的一个建议是,对于这样的情况,将有一个私有实现,您的两个公共Action方法都依赖此私有实现来避免重复代码。


1
随着MVC2及更高版本,您还可以使用HttpPost / HttpGet属性
yoel halb

@yohal是的,如果您不需要支持多个动词,那将是现在处理它的规范方法。
tvanfosson

3
请注意不要滥用它违反REST的原理。
弗雷德(Fred)

1
可以肯定的是,这仅是有效的,因为您的Show()方法具有不同的签名。如果并且当您需要将信息发送到Get版本中时,那么您的Get和Post版本将以相同的签名结尾,并且您将需要该ActionName属性或本文中提到的其他修补程序之一。
斯科特·弗雷利

1
@ ScottK.Fraley是的。如果他们需要相同的签名,则必须使用不同的名称命名并应用ActionNameAttribute。实际上,我很少发现这种情况。
tvanfosson

42

这是您可以做的其他事情...您想要一个能够有参数而没有参数的方法。

为什么不试试这个...

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

这对我有用...在这种方法中,您实际上可以测试一下是否具有传入参数。


更新以删除字符串上无效的可为空的语法,并使用默认参数值。


6
string不能为空。)
Josh M.

23
字符串可以为空。实际上,它已经可以为空,只是不需要'?'
ProfK 2011年

9
@ProfK-否,字符串是引用类型,可以为null。它不是“空的”。Nullable表示您正在使用Nullable <T>(即T?)。Josh的观点是,您不能放?在字符串之后,因为它不是值类型,并且Nullable <T>仅接受值类型。
Erik Funkenbusch 2011年

4
我随机找到回到这个问题的方式,然后意识到我在上面发表了评论。对此没有任何回忆...很奇怪!仍然string不能做到nullable; 但是可以null!无论哪种方式,我都没有诚意发表最初的评论。
Josh M.

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)”。

在此处输入图片说明

在此处输入图片说明

我从此代码项目文章-> MVC Action重载中获取了以上答案


谢谢你 我想您也可以从一开始就使用其他动作名称,而不要使用属性。
2015年

1
@Dan,但是在C#端我们没有多态性。
Shivprasad Koirala 2015年

没错,没有控制器方法重载,但与HTTP无关。
Chalky 2015年

感谢您的澄清。+1。应该考虑更多的HTTP,而不是C#。没有理由采用面向对象策略来采取行动。

15

为了克服这个问题,你可以写一个ActionMethodSelectorAttribute是检查MethodInfo每个操作的,并将其与发布的Form值进行比较,然后拒绝任何表单值不匹配的方法(当然,不包括按钮名称)。

这是一个示例:-http : //blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/

但是,这不是一个好主意。


@Cerbrus,因为这是一个可怕的骇客,下一个查看您的控制器代码的人会被一种非常不标准的方法所迷惑。
伊恩·默瑟

fair,公平。
Cerbrus

14

据我所知,当使用不同的http方法时,只能使用相同的方法。

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

}

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

}

2
装饰与过载无关。它是允许重载的参数列表。
Sky Sanders

@SkySanders我不同意,基于参数的重载在MVC控制器方法中不起作用-您有一个有效的示例吗?干杯。
Chalky

使用[HttpPost]属性代替[AcceptVerbs("POST")]
弗雷德(Fred)

9

我已经借助MVC5中的属性路由实现了这一点。诚然,我是十年来使用WebForms进行Web开发的MVC的新手,但以下内容对我有用。与接受的答案不同,这允许所有重载的动作由同一视图文件呈现。

首先在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
{
    //.......

然后用适合的通用路径和参数来装饰使彼此过载的控制器动作。使用类型受限的参数,您可以使用具有不同类型ID的相同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)
{
    //.....
}

希望这会有所帮助,并且不会导致别人走错路。:-)


干得好!我刚遇到这个问题,您救了我!我在WebForms上也度过了“ x”年-所以仍然是一个学习曲线。如今,没有MVC就无法找到工作哈哈
Tez Wingfield

4

您可以使用一个ActionResult来处理PostGet

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

如果您的GetPost方法具有匹配的签名,则很有用。


1
嗯,这是在重新发明轮子,但这次是方形的。为什么不简单地使用[HttpPost / Get]属性?
SOReader

它已经有一段时间了,但我想我这样做是因为MVC不能区分具有匹配信号的两种独立方法。我正在使用HttpPost属性,尽管没有将HttpGet放在另一种方法上
。–

@DevDave以及归因于这两种方法,请确保您使用的是system.web.mvc中的属性-而不是system.web.http中的属性!
Chalky

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
    }
}

这些操作将处理类似/cars/usa/new-york/cars/usa/texas/dallas,它们将分别映射到第一个和第二个Index操作。

检查此示例控制器,很明显它超出了上面提到的默认路由模式。如果您的url结构与您的代码命名约定完全匹配,则默认设置很好用,但是并非总是如此。代码应该描述域,但是url通常需要走得更远,因为它们的内容应该基于其他标准,例如SEO要求。

默认路由模式的好处是它会自动创建唯一的路由。这是由编译器强制执行的,因为url将匹配唯一的控制器类型和成员。滚动自己的路线模式将需要仔细考虑,以确保唯一性并使其起作用。

重要说明:一个缺点是,基于路由名称(例如,使用UrlHelper.Action时),无法使用路由为重载的操作生成url。但是,如果使用命名路由,例如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”)]。问题解决了

[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()

编辑:显然,仅当override方法在派生类中,这似乎不是OP的意图时,才适用。


2
您可能误解了这个问题。OP要求在同一个控制器中重载该方法,而不是在派生类中重写它。
2012年

@Andiih:如果两种方法都在同一控制器中会发生什么?
Dharmik Bhandari '10


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);
    }

当然,这是一个hack,应该在以后进行重构。但是暂时,它对我有用。

您还可以创建一个调度程序,例如:

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仅需要1个参数。


0

抱歉耽搁了。我遇到了同样的问题,我发现了一个链接,上面有很好的答案,这对新手有帮助吗

BinaryIntellect网站和作者的所有学分

基本上,有四种情况:使用型动物动词使用路由选择以[无动作]属性过载标志更改与[ActionName] action属性名称

因此,这取决于您的要求和您的情况。

但是,请点击以下链接:

链接:http//www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx


-1

如果这是尝试对多个视图使用一个GET操作,然后将其发布到具有不同模型的多个操作,则尝试为每个POST操作添加一个GET操作,该操作重定向到第一个GET,以防止刷新404。

远射但常见的情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.