Search code examples
postsharp

PostSharp MethodExecutionTag lost over aspect


I written 2 interception, 1 is on validate passing in arguement, the other is error handling.

Interception #1 throw an error (as intended), with custom object attached to the MethodExecutionArgs.MethodExecutionTag

    public class ValidateArguementAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
            ...
            var state = new ExecutionState
            {
                ReturnExactErrorMessage = true,
            };
            args.MethodExecutionTag = state;

            throw new ArgumentException(errMsgs.ToString());

However, this MethodExecutionTag is lost in error handling.

    public class ExceptionHandlingWithResponseAttribute : ExceptionHandlingAttribute
{
    public override void OnException(MethodExecutionArgs args)
    {
        base.OnException(args);

        ExecutionState state = args.MethodExecutionTag as ExecutionState;
        if (state != null)
        {
          // as debugging, expected to be here, but it's not
        }

I do a simple check, over the MethodExecutionArgs.GetHashCode(), getting different values.

I have this similar designed for a web Api application, it is working as expected, though I can't debug it, as I'm testing from unit testing.

Is this somekind of bug, or how can I resolve this?


Solution

  • This is by design. Execution tags are isolated between aspects.

    Imagine you have a transaction and an caching aspect. Both set execution tags in OnEntry and try to retrieve it in OnExit. The tag from the transaction aspect is overwritten by the caching aspect which creates awful mess.

    The isolation is needed to avoid nasty things like this.

    The solution could be to merge both aspect into one:

    public class ValidateArguementAttribute : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
                ...
                var state = new ExecutionState
                {
                    ReturnExactErrorMessage = true,
                };
                args.MethodExecutionTag = state;
    
                throw new ArgumentException(errMsgs.ToString());
        }
    
        public override void OnException(MethodExecutionArgs args)
        {
            base.OnException(args);
    
            ExecutionState state = args.MethodExecutionTag as ExecutionState;
            if (state != null)
            {
              // as debugging, expected to be here, but it's not
            }
        }
    }
    

    Alternativelly the validation outcome could be handled directly instead of throwing the exception.

    UPDATE (based on Kelmen's idea of custom exception):

    public class ValidationException : Exception
    {
        public ExecutionState State { get; private set; }
    
        public ValidationException(ExecutionState state)
        {
            State = state;
        }
    }
    
    [Serializable]
    public class ValidateArguementAttribute : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            var state = new ExecutionState
            {
                ReturnExactErrorMessage = true,
            };
    
            throw new ValidationException(state);
        }
    }
    
    [Serializable]
    public class ExceptionHandlingWithResponseAttribute : OnExceptionAspect
    {
        public override void OnException(MethodExecutionArgs args)
        {
            base.OnException(args);
    
            ValidationException validationException = args.Exception as ValidationException;
            if (validationException != null)
            {
                // handle the exception
            }
            else
            {
                // just rethrow
                args.FlowBehavior = FlowBehavior.RethrowException;
            }
        }
    }