0

So I have an interceptor set up. But I don't want the debugger to bother with it - I want it to go straight from MyController to MyService when using 'step in' on _service.FooBar();

public class MyController : Controller
{
    private readonly IMyService _service;
    public MyController(IMyService srv) => _service = srv;
    public void FooBar() => _service.FooBar();//I want the debugger to 'step in' from here...
}

public class MyService : IMyService
{
    public void FooBar()
    {
        throw new NotImplementedException();//...to here
    }
}

public static class NinjectWebCommon
{
    ...
    private static void RegisterServices(IBindingRoot kernel)
    {
        var serviceAssembly = Assembly.GetAssembly(typeof(MyService));
        kernel.Bind(x =>
        {
            x.From(serviceAssembly)
                .SelectAllClasses().BindDefaultInterface()
                .Configure(p => p.InRequestScope().Intercept().With<DatabaseTransactionInterceptor>());
        });
}

public class DatabaseTransactionInterceptor : IInterceptor
{
    private readonly IDBEngine _dbEngine;
    public DatabaseTransactionInterceptor(IDBEngine dbEngine)
    {
        _dbEngine = dbEngine;
    }

    public void Intercept(IInvocation invocation)
    {
        var shouldTerminate = !invocation.ContainsAttribute<DoNotTerminateAttribute>();

        try
        {
            invocation.Proceed();

            if (shouldTerminate)
            {
                _dbEngine.Terminate();
            }
        }
        catch
        {
            _dbEngine.Terminate(true);

            throw;
        }
    }
}

internal static class NinjectInvocationExtensions
{
    internal static bool ContainsAttribute<T>(this IInvocation invocation) where T : Attribute
    {
        var request = invocation.Request;
        var wasCalledFromInterface = request.Method.DeclaringType != null;

        return wasCalledFromInterface
            ? InterfaceRequestContainsAttribute<T>(request)
            : ConcreteRequestContainsAttribute<T>(request);
    }
    ...
    private static MethodInfo GetDerivedMethod(Type derivedType, MethodInfo interfaceMethod) =>
        derivedType.GetMethods()
            .Single(derivedMethod => derivedMethod.IsEqualForOverloadResolution(interfaceMethod));
}

I managed partial success by adding a DebuggerStepThrough attribute to the DatabaseTransactionInterceptor class, but it still goes into NinjectInvocationExtensions.ContainsAttribute(). I tried adding the attribute to NinjectInvocationExtensions, but the debugger still goes into GetDerivedMethod().

Is there any way to accomplish what I want?

Sarov
  • 545
  • 6
  • 17
  • I'm assuming adding `[DebuggerStepThrough]` to `GetDerivedMethod` is not the answer you're looking for? Are you saying that you want to not only prevent the debugger from stepping into `Intercept`, but you want `Intercept` to make the debugger skip everything that it calls, even if it's code that the debugger would otherwise step into, if it were called elsewhere? The only thing I can think of would be that you could replicate all the code it calls into internal code marked with DebuggerStepThrough. – StriplingWarrior Dec 19 '19 at 20:35
  • @StriplingWarrior "I'm assuming adding [DebuggerStepThrough] to GetDerivedMethod is not the answer you're looking for?" Yeah, that didn't work either - for some reason it didn't stop in there. Not sure why. Further, while not a deal-breaker, I'd prefer to avoid changing NinjectInvocationExtensions at all. "Are you saying that [...] if it were called elsewhere?" Yes, exactly. – Sarov Dec 19 '19 at 20:50
  • What do you mean by "for some reason it didn't stop in there"? – StriplingWarrior Dec 19 '19 at 20:53
  • 1
    See whether this fits: [DebuggerStepThrough Attribute - How to also skip child Methods](https://stackoverflow.com/a/53990683/7444103) – Jimi Dec 19 '19 at 20:53
  • @StriplingWarrior It's stopping inside inside the lambda - on "derivedMethod.IsEqualForOverloadResolution(interfaceMethod)". – Sarov Dec 19 '19 at 20:56
  • @Jimi When I add that to both Intercept() and GetDerivedMethod(), then it does skip, but it also skips the invocation itself. – Sarov Dec 19 '19 at 21:02
  • What do you mean by "it skips the invocation itself"? – StriplingWarrior Dec 19 '19 at 21:32
  • Hmm, I thought that's what you wanted it to do. Move it around a bit :) – Jimi Dec 19 '19 at 21:34
  • Regarding the lambda, [that does seem to be a limitation of the compiler](https://stackoverflow.com/a/19785714/120955). But since [Closures are just poor man's objects](https://stackoverflow.com/q/2497801/120955), if you're devoted, you could overcome this by creating a class that takes `interfaceMethod` as a constructor argument and exposes a method that you can pass into `.Single()` directly, avoiding the need for the lambda. Decorate that method with `DebuggerStepThrough`, along with `IsEqualForOverloadResolution` and anything in the call chain. But Jimi's approach is easier if it works. – StriplingWarrior Dec 19 '19 at 21:41
  • @StriplingWarrior "What do you mean by "it skips the invocation itself"?" It skips over MyService.FooBar(). – Sarov Dec 19 '19 at 21:46
  • I wondered if it might do that, based on [the docs](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debuggerstepperboundaryattribute?view=netframework-4.7.2): "encountering a DebuggerStepperBoundaryAttribute while stepping through code using the F10 key (or Step Over command) has the same effect as pressing the F5 key". :-( – StriplingWarrior Dec 19 '19 at 21:57
  • @StriplingWarrior That's not exactly what it does (maybe in VS 2005, as described the Docs). If you test the sample code I linked, you see that it steps through the methods called by the method decorated with that attribute. You have to place it *wisely*, though and, IIRC, in combination with other attributes, as shown in the sample (I cannot test the OP's code, so...). – Jimi Dec 19 '19 at 22:03
  • I think the problem may be that the call to `invocation.Proceed()` is what causes `MyService.FooBar()` to get invoked. What if you put the logic for ShouldTerminate into a method with DebuggerStepperBoundaryAttribute on it, but only decorate Intercept with the DebuggerStepThroughAttribute? – StriplingWarrior Dec 19 '19 at 22:09
  • 1
    @StriplingWarrior So remove all [DebuggerStepThrough]s, add one to Intercept, and add a [DebuggerStepThrough, DebuggerStepperBoundary] to a new private bool ShouldTerminate(IInvocation invocation) method? I'll give it a shot... Didn't work, it stopped in the ctor. Adding [DebuggerStepThrough] to the ctor... An improvement. It takes me to a Proceed() method I made. Adding [DebuggerStepThrough] to that... I think that's got it! I'll do more thorough testing tomorrow. Thanks for both of your help. – Sarov Dec 19 '19 at 22:19
  • No problem. It's been a great learning experience for me. Thanks for sharing your knowledge, Jimi! – StriplingWarrior Dec 20 '19 at 00:03

1 Answers1

0

Thanks to Jimi and StriplingWarrior for helping me reach this solution.

I just had to refactor a bit so that the logic to determine 'shouldTerminate' is in its own method, mark that method with [DebuggerStepperBoundary], ensure it's both the first thing called after the invocation, ensure that there's nothing I want stepped through after ShouldTerminate, and ensure the invocation is the first thing called.

The below code works exactly as intended:

[DebuggerStepThrough]
public class DatabaseTransactionInterceptor : IInterceptor
{
    private readonly IDBEngine _dbEngine;

    public DatabaseTransactionInterceptor(IDBEngine dbEngine) => _dbEngine = dbEngine;

    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();

            if (ShouldTerminate(invocation))
                _dbEngine.Terminate();
        }
        catch
        {
            _dbEngine.Terminate(true);

            throw;
        }
    }

    [DebuggerStepperBoundary]
    private static bool ShouldTerminate(IInvocation invocation) =>
        !invocation.ContainsAttribute<DoNotTerminateAttribute>();
}
Sarov
  • 545
  • 6
  • 17