1

I have the following scenario:

  1. I have several derived exceptions classes that implements a base exception
    //Base exception type
    public class SaberpsicologiaException : Exception
    {
    }

    //One of the derived exception class
    public class MovedPermanentlyException : SaberpsicologiaException
    {
        public string CannonicalUri { get; private set; }

        public MovedPermanentlyException(string cannonicalUri) 
            : base($"Moved permanently to {cannonicalUri}")
        {
            this.CannonicalUri = cannonicalUri;
        }
    } 

  1. For each exception class I want to implement an exceptionHandler that will return an ActionResult, which will implement a common interface:
    interface ISaberpsicologiaExceptionHandler<T>
        where T : SaberpsicologiaException
    {
        ActionResult Result(T exception);
    }

    public class MovedPermanentlyExceptionHandler 
        : ISaberpsicologiaExceptionHandler<MovedPermanentlyException>
    {
        public ActionResult Result(MovedPermanentlyException exception)
        {
            var redirectResult = new RedirectResult(exception.CannonicalUri);
            redirectResult.Permanent = true;

            return redirectResult;
        }
    }

  1. When I catch an exception derived from SaberpsicologiaException I want the appropiate handler to run:
    public class ExceptionHandlerFilter : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            base.OnException(context);

            HandleResponseCodeByExceptionType(context);
        }

        private void HandleResponseCodeByExceptionType(ExceptionContext context)
        {
            var exception = context.Exception;

            if (!CanHandle(exception))
            {
                return;
            }

            var mapping = new Dictionary<Type, Type>
            {
                { typeof(MovedPermanentlyException),  typeof(MovedPermanentlyExceptionHandler) }
            };

            var handlerType = mapping[exception.GetType()];
            var handler = Activator.CreateInstance(handlerType);

            handler.Result(exception); //<- compilation error 
            //handler is type "object" and not MovedPermanentlyExceptionHandler
        }
    }

I tried to resolve it with the Activator (Reflection), but I get to the problem of not really having and object of type ISaberpsicologiaExceptionHandler< [runtime exceptiontype] > so I can't have use the type properly.

In summary the problem is that I have an exception type and I want to get the ISaberpsicologiaExceptionHandler for that exception type, I guess I could use more reflection to just execute the 'Result' method, but I would like to do it a little bit more ellegant.

Manjar
  • 3,159
  • 32
  • 44
  • 1
    `Activator.CreateInstance` returns `object`.You are missing a cast to e.g. `MovedPermanentlyExceptionHandler`: `var handler = Activator.CreateInstance(handlerType) as MovedPermanentlyExceptionHandler;` – BionicCode Sep 21 '19 at 10:23
  • I cant use the generic version because I dont know which type it is, in compilation time (id I knew it I didnt even need to use activator) – Manjar Sep 21 '19 at 11:00

3 Answers3

2

You didn't show the full context of your class that implements ISaberpsicologiaExceptionHandler<T>. But just from the definition of this interface I would say it doesn't need to be a generic interface. A few possible solutions:

Solution 1

Make the method generic:

interface ISaberpsicologiaExceptionHandler        
{
  ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
}

public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
  public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
  {
    if (exception is MovedPermanentlyException movedPermanentlyException)
    {
      var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
      redirectResult.Permanent = true;

      return redirectResult;
     }

     throw new InvalidArgumentException("Exception type not supported", nameof(exception));
   }
}

Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type

catch (MovedPermanentlyException exception)
{
  var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
  handler.Result(exception);
}

Solution 2

Use specialized interfaces:

// General interface
interface ISaberpsicologiaExceptionHandler
{
  ActionResult Result(Exception exception);
}

// Specialized interface
interface IMovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
  ActionResult Result(MovedPermanentlyException exception);
}    

public class MovedPermanentlyExceptionHandler : IMovedPermanentlyExceptionHandler
{
  public ActionResult Result(MovedPermanentlyException exception)
  {
    var redirectResult = new RedirectResult(exception.CannonicalUri);
    redirectResult.Permanent = true;

    return redirectResult;
  }

  #region Implementation of ISaberpsicologiaExceptionHandler

  // Explicit interface implementation
  ActionResult ISaberpsicologiaExceptionHandler.Result(Exception exception)
  {
    if (exception is MovedPermanentlyException movedPermanentlyException)
    {
      return Result(movedPermanentlyException);
    }

    throw new InvalidArgumentException("Exception type not supported", nameof(exception));
  }    
  #endregion
}

Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic less specialized base interface ISaberpsicologiaExceptionHandler no matter the implementing type.

catch (MovedPermanentlyException exception)
{
  var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
  handler.Result(exception);
}

Solution 3

Use reflection:

interface ISaberpsicologiaExceptionHandler        
{
  ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
}    

public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
{
  public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
  {
    if (exception is MovedPermanentlyException movedPermanentlyException)
    {
      var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
      redirectResult.Permanent = true;

      return redirectResult;
     }

     throw new InvalidArgumentException("Exception type not supported", nameof(exception));
   }
}

Usage:
To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type

catch (MovedPermanentlyException exception)
{
  var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
  MethodInfo reflectedMethod = handlerType.GetMethod("Result");
  MethodInfo genericMethod = reflectedMethod.MakeGenericMethod(exception.GetType());
  object[] args = {exception};
  genericMethod.Invoke(this, args);
}

Solution 4

Recommended solution.

Use the proper concrete implementation on invocation:

I don't know the concept of your exception handler. But since you always know which specific exception you want to catch you can create the proper instance (using a factory at this point is also an option):

try      
{
  // Do something that can throw a MovedPermanentlyException
}
catch (MovedPermanentlyException e)
{
  var movedPermanentlyExceptionHandler = new MovedPermanentlyExceptionHandler();
  movedPermanentlyExceptionHandler.Result(e);
}
catch (SomeOtherException e)
{
  var someOtherExceptionHandler = new SomeOtherExceptionHandler();
  someOtherExceptionHandler.Result(e);
}

There are more solutions so I just take a break. It just boils down to avoid code that uses unknown generic types where members of this unknown type are referenced. I argue that this is always possible and just a question of good design.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • I think i will try to implement your Solution 2, since I don't really want to rely too much on Reflection for this issue. I don't really like Solution 4 because I don't want to add much noise in the controllers. Thank you very much for the answer :) – Manjar Sep 21 '19 at 13:34
1

I took a more generic approach using System.Linq.Expressions

Given an exception type, a delegate is built to invoke the desired function

 LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
    var type = typeof(ISaberpsicologiaExceptionHandler<>);
    var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
    var handle = genericType.GetMethod("Result", new[] { exceptionType });
    var func = typeof(Func<,>);
    var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));

    //Intension is to create the following expression:
    // Func<Exception, ActionResult> function = 
    // (exception) => (new handler()).Result((MyException)exception);

    // exception =>
    var exception = Expression.Parameter(typeof(Exception), "exception");
    // new handler()
    var newHandler = Expression.New(handlerType);
    // (MyException)exception
    var cast = Expression.Convert(exception, exceptionType);
    // (new handler()).Result((MyException)exception)
    var body = Expression.Call(newHandler, handle, cast);
    //Func<TException, ActionResult> (exception) => 
    //  (new handler()).Result((MyException)exception)
    var expression = Expression.Lambda(delegateType, body, exception);
    return expression;
}

and can be used like the following with the filter

//...

var exceptionType = exception.GetType();
var handlerType = mapping[exceptionType]; 

var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();

var result = handler.DynamicInvoke(exception);

context.Result = (IActionResult)result;

//...

Here is the full implementation

public class ExceptionHandlerFilter : ExceptionFilterAttribute {
    public override void OnException(ExceptionContext context) {
        base.OnException(context);
        HandleResponseCodeByExceptionType(context);
    }
    static readonly Dictionary<Type, Type> mapping = new Dictionary<Type, Type>
    {
        { typeof(MovedPermanentlyException), typeof(MovedPermanentlyExceptionHandler) }
    };

    private void HandleResponseCodeByExceptionType(ExceptionContext context) {
        var exception = context.Exception;

        if (!CanHandle(exception)) {
            return;
        }

        var exceptionType = exception.GetType();
        var handlerType = mapping[exceptionType];

        var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();

        var result = handler.DynamicInvoke(exception);

        context.Result = (IActionResult)result;
    }

    LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
        var type = typeof(ISaberpsicologiaExceptionHandler<>);
        var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
        var handle = genericType.GetMethod("Result", new[] { exceptionType });
        var func = typeof(Func<,>);
        var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));

        //Intension is to create the following expression:
        // Func<Exception, ActionResult> function = 
        // (exception) => (new handler()).Result((MyException)exception);

        // exception =>
        var exception = Expression.Parameter(typeof(Exception), "exception");
        // new handler()
        var newHandler = Expression.New(handlerType);
        // (MyException)exception
        var cast = Expression.Convert(exception, exceptionType);
        // (new handler()).Result((MyException)exception)
        var body = Expression.Call(newHandler, handle, cast);
        //Func<TException, ActionResult> (exception) => 
        //  (new handler()).Result((MyException)exception)
        var expression = Expression.Lambda(delegateType, body, exception);
        return expression;
    }
}

Used the following unit test to verify expected behavior

[TestClass]
public class ExceptionHandlerFilterTests {
    [TestMethod]
    public void Should_Handle_Custom_Exception() {
        //Arrange
        var subject = new ExceptionHandlerFilter();
        var url = "http://example.com";
        var context = new ExceptionContext(Mock.Of<ActionContext>(), new List<IFilterMetadata>()) {
            Exception = new MovedPermanentlyException(url)
        };

        //Act
        subject.OnException(context);

        //Assert
        context.Result.Should()
            .NotBeNull()
            .And.BeOfType<RedirectResult>();
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thank you very much for the answer, I really appretiate it, awesome work, however I kept the solution of @BionicCode, because I don't want to rely too much on Reflection for this scenario. However your solution it is amazing too :) – Manjar Sep 21 '19 at 13:37
0

You may be better off utilizing an if...else or switch statement. Your code could look something like this

private void HandleResponseCodeByExceptionType(ExceptionContext context)
        {
            var exception = context.Exception;

            if (!CanHandle(exception)) return;

            var exceptionType = exception.GetType();

            if (exceptionType == typeof(MovedPermanantelyException)) {
                 var handler = new MovePermanentlyExceptionHandler();
                 handler.Result(exception);
            }
            else {
                // chain the rest of your handlers in else if statements with a default else
            }
        }

This has the distinct advantage of allowing you to explicitly use the constructors for these handlers instead of attempting to create them with reflection. With reflection you will be unable to add additional parameters to your constructors without a lot of extra work on and modifications to your code.

MichaelM
  • 964
  • 7
  • 20
  • Had an earlier segment about using casting but realized it wasn't fixing the issue so I have removed it. You could potentially utilize the `dynamic` class, which would potentially allow you to call the `.Result()` method, but that is dangerous and can easily introduce errors into your code. I'd highly suggest following this method if at all possible – MichaelM Sep 21 '19 at 11:53
  • I had a running version using this method, however I feel like I'm not really using the potencial of the generics, that is why I wanted to improve that code. What I was hopping was to be able to cast the object into the interface, but it is not possible since the interface implements a concrete type. – Manjar Sep 21 '19 at 12:09
  • As a side note: It is possible to use parameters with reflection, one of the overloads of .CreateInstance allow you to pass a list of parameters for the "instantiation". – Manjar Sep 21 '19 at 12:12
  • 1
    @Manjar yes that is true, but unless all the constructors share the same parameters, it will be difficult to generate that list of parameters; it's a dangerous road to start to head down and is better off being avoided. – MichaelM Sep 21 '19 at 12:23