【问题标题】:Can you overload controller methods in ASP.NET MVC?你能在 ASP.NET MVC 中重载控制器方法吗?
【发布时间】:2010-10-01 00:20:24
【问题描述】:

我很想知道您是否可以在 ASP.NET MVC 中重载控制器方法。每当我尝试时,我都会收到以下错误。这两种方法接受不同的参数。这是不能做的事吗?

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

【问题讨论】:

  • @andy 对于 mvc 4 也是一样的 :)
  • 和 mvc 5 一样
  • 和 mvc 6 一样
  • 和 MVC Core 1.1 一样
  • 和 MVC Core 2.0 一样

标签: c# asp.net-mvc overloading


【解决方案1】:

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

[ActionName("MyOverloadedName")]

但是,您必须为同一个 http 方法使用不同的操作名称(正如其他人所说)。所以这只是当时的语义。您希望在代码中使用名称还是在属性中使用名称?

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

【讨论】:

  • 使用这个和重载你的动作的主要缺点是它不能再被同一个视图文件渲染。
  • 其实还是可以渲染同一个视图文件的。您只需要指定视图的名称,而不是盲目地调用return View();。例如:return View("MyOverloadedName");.
  • @JD 但是微软说..用作控制器动作的方法不能重载..你可以在这里看到..asp.net/mvc/tutorials/controllers-and-routing/…
【解决方案2】:

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

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

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

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

【讨论】:

  • 使用 MVC2 及以上也可以使用 HttpPost/HttpGet 属性
  • @yohal 是的,如果您不需要支持多个动词,那将是现在处理它的规范方法。
  • 请注意不要滥用它来违反 REST 的原则。
  • 很确定这只是因为您的 Show() 方法具有不同的签名。如果并且当您需要将信息发送到 Get 版本时,您的 Get 和 Post 版本最终会具有相同的签名,并且您需要 ActionName 属性或本文中提到的其他修复之一。
  • @ScottK.Fraley 这是真的。如果他们需要相同的签名,您必须以不同的方式命名它们并应用ActionNameAttribute。在实践中,我很少发现这种情况。
【解决方案3】:

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

为什么不试试这个...

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

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


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

【讨论】:

  • string 不能为空。)
  • 字符串可以为空。其实它已经可以为空了,只是不需要'?'
  • @ProfK - 不,字符串是可以为空的引用类型。它不是“可空的”。 Nullable 意味着您正在使用 Nullable (即 T?)。乔希的观点是你不能把 ?在 string 之后,因为它不是值类型,并且 Nullable 只接受值类型。
  • 我随机找到了回到这个问题的方法,然后意识到我发布了上面的评论。不记得这个了……很奇怪! string 不能是 nullable 仍然是真的;但它可以是null!无论哪种方式,我都没有诚意地发布了最初的评论。
【解决方案4】:

不,不,不。去试试下面我们重载“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 overloading

【讨论】:

  • 谢谢。我想你也可以从一开始就使用不同的动作名称而不是使用属性。
  • @Dan 但是我们在 C# 端没有多态性。
  • 你是对的,没有控制器方法重载,但它与 HTTP 无关。
  • 感谢您的澄清。 +1。应该考虑更多的 HTTP 而不是 C#。没有理由使用 OO 策略来处理行动。
【解决方案5】:

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

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

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

【讨论】:

  • @Cerbrus 因为这是一个可怕的 hack,下一个查看您的控制器代码的人会被一种非常非标准的方法弄糊涂。
  • 嘿,很公平。
【解决方案6】:

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

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

}

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

}

【讨论】:

  • 装饰与过载无关。它是允许重载的参数列表。
  • @SkySanders 我不同意,基于参数的重载在 MVC 控制器方法中不起作用 - 你有它的工作示例吗?干杯。
  • 使用[HttpPost] 属性而不是[AcceptVerbs("POST")]
【解决方案7】:

我在 MVC5 中的 Attribute Routing 的帮助下实现了这一点。诚然,我是使用 WebForms 进行 Web 开发十年的新手,但以下内容对我有用。与公认的答案不同,这允许所有重载的动作由同一个视图文件呈现。

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

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

【讨论】:

  • 干得好!我刚遇到这个问题,你救了我!我也有“x”年的 WebForms - 所以仍然是一个学习曲线。现在没有 MVC 就找不到工作了哈哈
【解决方案8】:

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

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

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

【讨论】:

  • 嗯,有点像重新发明轮子,但这次是方形。为什么不简单地使用 [HttpPost/Get] 属性?
  • 已经有一段时间了,但我想我这样做是因为 MVC 没有区分两个具有匹配信号的单独方法。我使用的是 HttpPost 属性,尽管我没有将 HttpGet 放在其他方法上..
  • @DevDave 以及归因于这两种方法,确保您使用的是来自 system.web.mvc 的属性 - 而不是来自 system.web.http 的那些!
【解决方案9】:

我刚刚遇到这个问题,尽管它现在已经很老了,但它仍然非常相关。具有讽刺意味的是,该线程中的一个正确评论是由一位自认是 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 这样的网址,它们将分别映射到第一个和第二个索引操作。

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

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

重要说明 一个缺点是,使用路由为重载操作生成 url 在基于操作名称时不起作用,例如,在使用 UrlHelper.Action 时。但如果使用命名路由,例如 UrlHelper.RouteUrl,它确实有效。并且,根据备受尊敬的消息来源,使用命名路由是无论如何都要走的路 (http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/)。

祝你好运!

【讨论】:

    【解决方案10】:

    您可以使用 [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");
        }
    }
    

    【讨论】:

      【解决方案11】:

      我需要一个重载:

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

      这不是一个完美的解决方案,尤其是当你有很多争论时,但它对我很有效。

      【讨论】:

        【解决方案12】:

        我的申请中也遇到了同样的问题。在没有修改任何方法信息的情况下,我在动作头上提供了 [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);
        
                }
        

        【讨论】:

          【解决方案13】:

          将基本方法创建为虚拟

          public virtual ActionResult Index()
          

          将被覆盖的方法创建为 override

          public override ActionResult Index()
          

          编辑:这显然只适用于重写方法在派生类中,而派生类似乎不是 OP 的意图。

          【讨论】:

          • 您可能误解了这个问题。 OP 要求在同一个控制器中重载该方法,而不是在派生类中覆盖它。
          • @Andiih : 如果两种方法都在同一个控制器中会发生什么?
          【解决方案14】:

          我喜欢在另一个帖子中发布的这个答案

          这主要用于从另一个控制器继承并希望覆盖来自基本控制器的操作

          ASP.NET MVC - Overriding an action with differing parameters

          【讨论】:

            【解决方案15】:

            每个控制器方法只允许一个公共签名。如果您尝试重载它,它会编译,但您会遇到您遇到的运行时错误。

            如果您不愿意使用不同的动词(例如 [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 只需要一个。

            【讨论】:

              【解决方案16】:

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

              BinaryIntellect 网站和作者的所有学分

              基本上有四种情况:使用不同的动词使用路由用[NoAction]属性重载标记改变带有 [ActionName] 的动作属性名称

              所以,这取决于您的要求和您的情况。

              不过,请点击链接:

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

              【讨论】:

                【解决方案17】:
                1. 这个答案适用于那些在同一问题上苦苦挣扎的人。你可以 实现您自己的自定义过滤器 动作方法选择器属性。 Here我找到了最好的解决方案 为解决您的问题。在 .net 5 项目上运行良好。

                2. 如果您尝试实现与 Web api 控制器中相同的逻辑,请使用 Microsoft.AspNetCore.Mvc.WebApiCompatShim。此 nuget 包提供 ASP.NET Core MVC 与 ASP.NET Web API 2 的兼容性,以简化现有 Web API 实现的迁移。请检查this answer 但请考虑 从 ASP.NET Core 3.0 开始,Microsoft.AspNetCore.Mvc.WebApiCompatShim 包不再可用。

                【讨论】:

                  【解决方案18】:

                  如果这是尝试对多个视图使用一个 GET 操作,这些视图 POST 到具有不同模型的多个操作,则尝试为每个重定向到第一个 GET 的 POST 操作添加一个 GET 操作,以防止刷新时出现 404。

                  远景但常见的场景。

                  【讨论】:

                    猜你喜欢
                    • 2013-09-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-12-26
                    • 2010-11-04
                    • 2015-12-06
                    相关资源
                    最近更新 更多