无法在控制器'Controller'上调用动作方法'System.Web.Mvc.PartialViewResult Foo [T](T)'因为动作方法是通用方法

时间:2010-05-19 23:22:18

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

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

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

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

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

2 个答案:

答案 0 :(得分:7)

抛出的代码在默认的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的情况下覆盖此行为。从浏览源代码看,这似乎并不重要。

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

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

答案 1 :(得分:1)

六年后,随着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,特别是关于如何推广解决方案。