2010-05-19 807 views
5

No se puede llamar al método de acción 'System.Web.Mvc.PartialViewResult FooT' en el controlador 'Controller' debido a que el método de acción es un método genéricoNo se puede llamar al método de acción 'System.Web.Mvc.PartialViewResult Foo [T] (T)' en el controlador 'Controller' porque el método de acción es un método genérico

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

¿hay una solución para esta limitación en ASP MVC 2? Realmente preferiría usar un genérico. La solución alternativa que he propuesto es cambiar el tipo de modelo para que sea un objeto. Funciona, pero no se prefiere:

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

Respuesta

7

El código que lanza que está dentro del ActionDescriptor defecto:

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

Debido a que el código se llama "methodInfo.ContainsGenericParameters" no creo que hay una forma de anular este comportamiento sin crear su propio ActionDescriptor. Al echar un vistazo al código fuente esto parece no trivial.

Otra opción es hacer que su clase de controlador sea genérica y crear una fábrica de controladores genéricos personalizada. Tengo un código experimental que crea un controlador genérico. Es hacky pero es solo un experimento personal.

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

Seis años más tarde, con MVC5 (y MVC6) en la ciudad, me encontré con este mismo problema. Estoy construyendo mi sitio con MVC5, por lo que puedo suponer con seguridad que todavía no se admite de manera inmediata. Aterricé aquí en busca de solución. Bueno, finalmente encontré la manera de arreglarlo sin hackear el controlador o su fábrica, especialmente porque solo necesitaba esta característica en solo unos pocos lugares.

El enfoque: modificando ligeramente el Command Pattern (que ya estaba usando en mi código).

Para este problema, se empieza por definir la interfaz

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

y la aplicación correspondiente:

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

El manejador (userd en el código anterior) se verá así:

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

Con lo anterior en su lugar, todo lo que tiene que hacer es proporcionar implementaciones para la h andler basado en los detalles de su clase T. Algo como esto:

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

Con todo lo anterior en su lugar, ahora se puede simplemente hacer esto en su controlador:

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

var partialViewResult = processor.Process(model); 

sé que es un nivel adicional de indirección pero funcionó de forma excelente. En mi caso, amplié el controlador y el procesador para que puedan devolver todo lo que quiero, no solo PartialViewResult.

Me interesaría ver una solución más simple para esto, si existe. Supongo que no es común tener este tipo de problema al usar MVC.

PD: Cualquier buen contenedor de IoC debería poder registrar genéricos abiertos escaneando a través de ensamblajes.Por lo tanto, una vez configurado correctamente, no es necesario registrar explícitamente las implementaciones para la interfaz del controlador.

+0

Esta publicación ([link] (http://codedrum.blogspot.com/2016/07/cannot-call-action-method-on-controller.html)) proporciona una mejor comprensión de la respuesta dada aquí, especialmente en cómo la solución puede ser generalizada. –

Cuestiones relacionadas