【问题标题】:Cannot call action method 'System.Web.Mvc.PartialViewResult Foo[T](T)' on controller 'Controller' because the action method is a generic method无法在控制器“Controller”上调用操作方法“System.Web.Mvc.PartialViewResult Foo[T](T)”,因为操作方法是通用方法
【发布时间】:2011-02-21 15:26:39
【问题描述】:

无法在控制器“Controller”上调用操作方法“System.Web.Mvc.PartialViewResult FooT”,因为操作方法是通用方法

<% Html.RenderAction("Foo", model = Model}); %>

对于 ASP MVC 2 的这种限制是否有解决方法?我真的更喜欢使用泛型。我想出的解决方法是将模型类型更改为对象。它有效,但不是首选:

    public PartialViewResult Foo<T>(T model) where T : class
    {
      // do stuff
    }

【问题讨论】:

    标签: .net asp.net-mvc generics asp.net-mvc-2


    【解决方案1】:

    在默认 ActionDescriptor 中抛出的代码:

        internal static string VerifyActionMethodIsCallable(MethodInfo methodInfo) {
            // we can't call instance methods where the 'this' parameter is a type other than ControllerBase
            if (!methodInfo.IsStatic && !typeof(ControllerBase).IsAssignableFrom(methodInfo.ReflectedType)) {
                return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType,
                    methodInfo, methodInfo.ReflectedType.FullName);
            }
    
            // we can't call methods with open generic type parameters
            if (methodInfo.ContainsGenericParameters) {
                return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallOpenGenericMethods,
                    methodInfo, methodInfo.ReflectedType.FullName);
            }
    
            // we can't call methods with ref/out parameters
            ParameterInfo[] parameterInfos = methodInfo.GetParameters();
            foreach (ParameterInfo parameterInfo in parameterInfos) {
                if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef) {
                    return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters,
                        methodInfo, methodInfo.ReflectedType.FullName, parameterInfo);
                }
            }
    
            // we can call this method
            return null;
        }
    

    因为代码正在调用“methodInfo.ContainsGenericParameters”,所以我认为没有办法在不创建自己的 ActionDescriptor 的情况下覆盖此行为。看一眼源代码,这似乎并不简单。

    另一种选择是使您的控制器类通用并创建自定义通用控制器工厂。我有一些创建通用控制器的实验代码。它很老套,但只是个人实验。

    public class GenericControllerFactory : DefaultControllerFactory
    {
        protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            //the generic type parameter doesn't matter here
            if (controllerName.EndsWith("Co"))//assuming we don't have any other generic controllers here
                return typeof(GenericController<>);
    
            return base.GetControllerType(requestContext, controllerName);
    
            throw new InvalidOperationException("Generic Factory wasn't able to resolve the controller type");
        }
    
        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            //are we asking for the generic controller?
            if (requestContext.RouteData.Values.ContainsKey("modelType"))
            {
                string typeName = requestContext.RouteData.Values["modelType"].ToString();
                //magic time
                return GetGenericControllerInstance(typeName, requestContext);
            }
    
            if (!typeof(IController).IsAssignableFrom(controllerType))
                throw new ArgumentException(string.Format("Type requested is not a controller: {0}",controllerType.Name),"controllerType");
    
            return base.GetControllerInstance(requestContext, controllerType);
        } 
    
        /// <summary>
        /// Returns the a generic IController tied to the typeName requested.  
        /// Since we only have a single generic controller the type is hardcoded for now
        /// </summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        private IController GetGenericControllerInstance(string typeName, RequestContext requestContext)
        {
            var actionName = requestContext.RouteData.Values["action"];
    
            //try and resolve a custom view model
    
            Type actionModelType = Type.GetType("Brainnom.Web.Models." + typeName + actionName + "ViewModel, Brainnom.Web", false, true) ?? 
                Type.GetType("Brainnom.Web.Models." + typeName + ",Brainnom.Web", false, true);
    
            Type controllerType = typeof(GenericController<>).MakeGenericType(actionModelType);
    
            var controllerBase = Activator.CreateInstance(controllerType, new object[0] {}) as IController;
    
            return controllerBase;
        }
    }
    

    【讨论】:

      【解决方案2】:

      六年后,在城里使用 MVC5(和 MVC6)时,我遇到了同样的问题。我正在使用 MVC5 构建我的网站,因此我可以放心地假设它还没有开箱即用的支持。我来到这里寻找解决方案。好吧,我最终找到了一种方法来修复它,而无需侵入控制器或其工厂,特别是因为我只需要在几个地方使用此功能。

      方法:稍微修改Command Pattern(反正我已经在我的代码中使用了)。

      对于这个问题,你从定义接口开始

      public interface IMyActionProcessor
      {
          PartialViewResult Process<T>(T theModel);
      }
      

      以及相应的实现:

      public sealed class MyActionProcessor : IMyActionProcessor
      {
          // Your IoC container. I had it explicitly mapped just like any other 
          // registration to cause constructor injection. Using SimpleInjector, it will be 
          // <code>Container.Register(typeof(IServiceProvider), () => Container);</code>
          private IServiceProvider _serviceProvider;
          public MyActionProcessor(IServiceProvider serviceProvider) 
          {
              _serviceProvider = serviceProvider;
          }
          public PartialViewResult Process<T>(T theModel)
          {
              var handlerType =
                  typeof(IMyActionHandler<>).MakeGenericType(theModel.GetType());
      
              dynamic handler = _serviceProvider.GetService(handlerType);
      
              return handler.Handle((dynamic)theModel);
          }
      }
      

      处理程序(上面代码中的用户)将如下所示:

      public interface IMyActionHandler<T> where T : class
      {
          PartialViewResult Execute(T theModel);
      }
      

      有了上述内容,您现在所要做的就是根据您的类T 的具体情况为处理程序提供实现。像这样的:

      public class ModelClassHandler : IMyActionHandler<ModelClass>
      {
          public PartialViewResult Execute(ModelClass theModel);
          {
              // Do Stuff here and return a PartialViewResult instance
          }
      }
      

      以上所有内容就绪后,您现在可以在控制器中简单地执行此操作:

      var processor = YourServiceLocator.GetService<IMyActionProcessor>();
      var model = new ModelClass { /* Supply parameters */ };
      
      var partialViewResult = processor.Process(model);
      

      我知道这是一个额外的间接级别,但效果非常好。就我而言,我继续扩展处理程序和处理器,以便它们可以返回我想要的任何内容,而不仅仅是 PartialViewResult

      如果有的话,我有兴趣看到一个更简单的解决方案。我想在使用 MVC 时遇到这种问题并不常见。

      PS:任何好的 IoC 容器都应该能够通过扫描程序集来注册开放的泛型。因此,一旦配置得​​当,您无需显式注册处理程序接口的实现。

      PPS:This post provides better insight to the answer given here,特别是关于如何推广解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-27
        • 1970-01-01
        • 2020-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多