Как перехватить все вызовы методов действия контроллера ASP.NET WebApi с перехватом Ninject для ведения журнала?

Наша компания должна регистрировать определенные вещи каждый раз, когда вызывается один из наших методов действия наших контроллеров ASP.NET WebApi. Поскольку мы сейчас используем Ninject для DI, мы хотели бы использовать его и для этой цели. Это то, что я пробовал до сих пор.

У меня есть Ninject, Ninject.Extensions.Interception и Ninject.Extensions.Interception.DynamicProxy, установленные через NuGet, и у меня есть следующий модуль

public class InterceptAllModule : InterceptionModule
{
    public override void Load()
    {
        Kernel.Intercept(p => p.Request.Service.Name.EndsWith("Controller")).With(new TimingInterceptor());
    }
}

Где TimingInterceptor

public class TimingInterceptor : SimpleInterceptor
{
    readonly Stopwatch _stopwatch = new Stopwatch();
    protected override void BeforeInvoke(IInvocation invocation)
    {
        _stopwatch.Start();
    }

    protected override void AfterInvoke(IInvocation invocation)
    {
        _stopwatch.Stop();
        string message = string.Format("[Execution of {0} took {1}.]",invocation.Request.Method,_stopwatch.Elapsed);
        Log.Info(message + "\n");
        _stopwatch.Reset();
    }
}

Теперь, когда я пытаюсь подключить модуль к ядру ninject и запустить свой сайт

var kernel = new StandardKernel(new InterceptAllModule());

Однако всякий раз, когда к одному из методов действия поступает вызов, он выдает ошибку

Cannot instantiate proxy of class: MyApiController.

Может ли кто-то с опытом указать, что я делаю не так, пожалуйста? Благодарю.

2 ответа

Решение

Обновить

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

Я бы сказал, что в нынешнем виде ваш код будет перехватывать потенциально нежелательные методы на ApiController, поэтому вам, вероятно, также понадобится поместить некоторый код на место для их фильтрации, например, ExecuteAsync и Dispose.

Мой единственный другой момент - производительность. Огромный отказ от ответственности - это просто очень простые тесты (каждый раз, используя журнал фильтра действий для записи статистики), я приглашаю вас сделать свой собственный (!)... но, используя перехватчик DynamicProxy, я получал время около 4 миллисекунд на получить запрос

[Execution of Get took 00:00:00.0046615.]
[Execution of Get took 00:00:00.0041988.]
[Execution of Get took 00:00:00.0039383.]

Комментируя код перехвата и используя фильтр действий, я получал производительность менее миллисекунды:

[Execution of Get took 00:00:00.0001146.]
[Execution of Get took 00:00:00.0001116.]
[Execution of Get took 00:00:00.0001364.]

Это зависит от вас, является ли это проблемой или проблемой, но я подумал, что укажу на это.

Предыдущий ответ

Вы отменили использование ActionFilters? Это естественная точка расширения AOP для действия MVC.

Если бы вы были заинтересованы в других методах, кроме фактического действия на контроллере, я бы понял, но я все равно решил написать предложение.

Вдохновлены ли ActionFilterAttributes повторно используются в потоках? Как это работает? и измерение времени вызова действий контроллера ASP.NET MVC.

Обновлен, чтобы показать исключение таймера, когда метод помечен. Вдохновение от основной инфраструктуры WebApi, в частности AllowAnonymousAttribute и AuthorizeAttribute

Зарегистрируйте это глобально, чтобы все действия контролировались этим:

GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());

Затем:

public class TimingActionFilter : ActionFilterAttribute
{
    private const string Key = "__action_duration__";

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (SkipLogging(actionContext))
        {
            return;
        }

        var stopWatch = new Stopwatch();
        actionContext.Request.Properties[Key] = stopWatch;
        stopWatch.Start();
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        if (!actionExecutedContext.Request.Properties.ContainsKey(Key))
        {
            return;
        }

        var stopWatch = actionExecutedContext.Request.Properties[Key] as Stopwatch;
        if(stopWatch != null)
        {
            stopWatch.Stop();
            var actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
            Debug.Print(string.Format("[Execution of {0} took {1}.]", actionName, stopWatch.Elapsed));
        }

    }

    private static bool SkipLogging(HttpActionContext actionContext)
    {
        return actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>().Any() ||
                actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<NoLogAttribute>().Any();
    }
}

А также

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoLogAttribute : Attribute
{

}

Теперь вы можете исключить глобальный фильтр, используя:

public class ExampleController : ApiController
{
    // GET api/example
    [NoLog]
    public Example Get()
    {
       //
    }
}

Для тех, кто все еще скрывался, я хотел использовать Ninject, потому что я мог вставить регистратор (или что-то еще) в перехватчик, но я хотел перехватить все действия.

Ответ Марка идеален, но вместо глобальной регистрации используется

GlobalConfiguration.Configuration.Filters.Add(new TimingActionFilter());

свяжите свой фильтр с помощью Ninject

Kernal.BindHttpFilter<TimingActionFilter>(FilterScope.Action).

Вам нужно будет создать соответствующий конструктор в классе TimingActionFilter.

Другие вопросы по тегам