Невозможно вызвать метод действия «System.Web.Mvc.PartialViewResult Foo [T] (T)» на контроллере «Контроллер», поскольку метод действия является универсальным методом - PullRequest
6 голосов
/ 20 мая 2010

Невозможно вызвать метод действия 'System.Web.Mvc.PartialViewResult FooT' на контроллере 'Контроллер', поскольку метод действия является универсальным методом

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

Есть ли обходной путь для этого ограничения в ASP MVC 2? Я действительно предпочел бы использовать универсальный. Обходной путь, который я предложил, состоит в том, чтобы изменить тип модели на объект. Это работает, но не является предпочтительным:

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

Ответы [ 2 ]

7 голосов
/ 20 мая 2010

Код, который выдает внутри 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;
    }
}
1 голос
/ 07 июля 2016

Шесть лет спустя, с MVC5 (и MVC6) в городе, я столкнулся с той же проблемой. Я строю свой сайт с помощью MVC5, поэтому я могу с уверенностью предположить, что он еще не поддерживается "из коробки". Я приземлился здесь в поисках решения. Ну, в конце концов я нашел способ исправить это без взлома контроллера или его фабрики, особенно потому, что мне нужна была эта функция только в нескольких местах.

Подход: слегка изменить шаблон команды (который я уже использовал в своем коде в любом случае).

Для этой проблемы вы начинаете с определения интерфейса

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

Обработчик (userd в коде выше) будет выглядеть так:

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: Этот пост дает более полное представление об ответе, приведенном здесь , особенно о том, как можно обобщить решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...