无法在控制器'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
}
答案 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
。
PS:任何好的IoC容器都应该能够通过扫描程序集注册开放的泛型。因此,一旦配置得当,您就不需要显式注册处理程序接口的实现。
PPS:This post provides better insight to the answer given here,特别是关于如何推广解决方案。