Контроллер AOP контроллера ASP.NET MVC3 не перехватывает все методы, только IController.Execute - PullRequest
4 голосов
/ 20 января 2012

У меня есть проект с несколькими уровнями - среди них веб-интерфейс (ASP.NET MVC3) и сервисный интерфейс (в основном бизнес-логика).Проекту несколько месяцев, поэтому все работает как положено.Теперь я пытаюсь добавить аспект ведения журнала в некоторые методы контроллера MVC3, используя пользовательские атрибуты [Log].

Я использую Castle Windsor для внедрения зависимостей.Чтобы получить аспект ведения журнала, я использую Castle DynamicProxy - SNAP .Разрешение контроллеров осуществляется с помощью WindsorControllerFactory из полезного руководства Кшиштофа Кочича, но я изменил его, чтобы найти интерфейс контроллера по умолчанию (см. Ниже).

На моем уровне обслуживания:

[Log(LoggingLevel.Info)]
public void Save(MyBusinessDto dto)
{
    // business logic and other checks

    this.repository.Save(mbo);
}

В моем веб-интерфейсе IWindsorInstaller для контроллеров:

private static BasedOnDescriptor FindControllers()
{
    return AllTypes
            .FromThisAssembly()
            .BasedOn<IController>()
            .WithService.DefaultInterface();
}

В моем (слегка настроенном) WindsorControllerFactory, который ищет интерфейс по умолчанию дляконтроллер:

protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(404, string.Format(Error404, requestContext.HttpContext.Request.Path));
    }

    string controllerName = controllerType.Name;
    string defaultInterfaceName = 'I' + controllerName;
    Type defaultInterface = controllerType.GetInterface(defaultInterfaceName);

    object controller = this.kernel.Resolve(defaultInterface);

    return (IController)controller;
}

В моих контроллерах:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

Все это прекрасно работает в сервисном проекте, но в контроллерах методы не перехватываются.

  • Я подтвердил, что WindsorControllerFactory возвращает прокси-контроллеры.
  • Я подтвердил, что контроллеры зарегистрировали перехватчик.
  • Я подтвердил, что MasterProxy в SNAP перехватываетконтроллер - но он только перехватывает IController.Execute(RequestContext requestContext).

Как я могу перехватить все методы контроллера, которые имеют мой атрибут [Log]?

Обновление 1:Я рассмотрел использование DynamicProxy напрямую вместо SNAP, но это вторично по отношению к тому, чтобы заставить его работать и для контроллеров.

Обновление 2 + 4: кажется, что SNAP отсутствует в github обратноgithub .

Обновление 3: это то, что я вижу в отладчике Visual Studio при взломе WindsorControllerFactory (см. выше).Проверенная переменная controller - это то, что возвращается в MVC, и она действительно проксируется.

  • controller {Castle.Proxies.IMyBusinessControllerProxy}
    • __interceptors {Castle.DynamicProxy.IInterceptor [1]}
      • [0] {Snap.MasterProxy}
    • __target {My.Business.Web.Controllers.MyBusinessController}
      • service {Castle.Proxies.IMyBusinessServiceProxy}
      • (другие инъекции в конструктор)
    • MyInjectedProperty {My.Business.Useful.MyOtherType}

Ответы [ 2 ]

4 голосов
/ 23 января 2012

В IController GetControllerInstance(...) не обслуживать прокси интерфейса, обслуживать прокси класса с virtual методами.

Методы, реализованные пользователем в контроллере, возвращенном из IController GetControllerInstance(...), не будут доступны через прокси-интерфейс IMyBusinessController, но будут преобразованы из IController в фактический класс контроллера; например MyBusinessController. Вместо этого используйте прокси класса, чтобы приведение MVC3 вернуло прокси. Также пометьте методы как virtual, иначе перехватывающий прокси не сможет перехватывать вызовы метода и проверять наличие пользовательских атрибутов.

В контроллерах добавьте virtual к вашим методам с атрибутами:

public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
    [Log(LoggingLevel.Debug)]
    public virtual ActionResult CreateOrUpdate(MyBusinessFormModel fm)
    {
        // Convert form model to data transfer object,
        // perform validation and other checks

        this.service.Save(dto);

        return View(fm);
    }
}

Почему перехватывается только Execute(...)? Интерфейс IController содержит только Execute(...). Execute вызывается на возвращенном прокси интерфейса контроллера, поэтому его можно перехватить. Но как только внутренний ControllerBase.Execute(...) MVC3 получает вызов, он выполняет приведение к классу, который он ожидал от ControllerFactory.

Проблема похожа на this утечка , в которой оба обходят прокси интерфейса. Я думаю, что это может быть решено несколькими способами; возможно, путем создания пользовательского конвертера типов, создания прокси-класса класса из целевого объекта прокси-интерфейса на фабрике, умной конфигурации Windsor и так далее.

Кшиштоф Коймич Установщик IController и WindsorControllerFactory должны работать из коробки. Интерфейсные прокси могут быть рекомендованы для более широкой картины (и они хорошо работают до использования перехватчиков в контроллерах), но в этом случае может быть причина не заходить так далеко, чтобы избежать дальнейших побочных эффектов.

Спасибо Мариусу за то, что он указал мне правильное направление!

2 голосов
/ 21 января 2012

Поскольку DynamicProxy (SNAP использует dynamicproxy) не может перехватывать не виртуальные методы, я предполагаю, что возвращаемый прокси является производным классом вашего контроллера и, следовательно, не виртуальные методы игнорируются. Вам нужно либо заставить SNAP (не знаю, как это работает) вернуть интерфейсный прокси с target (ваша реализация), либо просто попытаться сделать ваши методы контроллера виртуальными.

...