2

Can I use or "cast" the type of a class, for instance type of the class CustomClass, therefor typeof(CustomClass), to execute a method which relies on providing its corresponding class CustomClass as an "argument"?

Method<CustomClass>();

How could I achieve this by only knowing typeof(CustomClass)?

Detailed question:

I'm developing a Web API in ASP.NET 4.5. I have access to an extension method, that I cannot change, for the Startup's IAppBuilder app:

public static class AppExtensions
{
    public static void AddExceptionTranslation<TException>(
        this IAppBuilder app,
        HttpStatusCode statusCode,
        Func<TException, ApiErrors> messageGenerator))
        where TException : Exception;
}

This extension method is used to convert custom exceptions, which extend Exception, to its appropriate status codes. I'm using it as follows:

public static void SetUpExceptionHandling(IAppBuilder app)
{
    app.AddExceptionTranslation<CustomException>(HttpStatusCode.BadRequest, e => e.ToApiErrors());
    app.AddExceptionTranslation<CustomUnauthException>(HttpStatusCode.Forbidden, e => e.ToApiErrors());
    // ...
}

Right now I have to manually add an entry for every single custom exception, providing the TException in each case. I'm already maintaining a data structure which contains the typeof for each custom exception class, alongside with its appropriate status codes, unique ids and other properties that I only want to maintain in one place.

I'd like to use this existing data structure to iterate through it and execute AddExceptionTranslation<TException> for each custom exception, where I know their Type.

Is this possible? If not, would it be possible if my data structured contained something else? Sure, I could just bite the bitter apple and add each new custom exception to the data structure and manually add an entry for the method execution but I'd really like to automate the latter.

Please let me know if I can clarify. Thanks!

Edit

AddExceptionTranslation actually accepts a second argument, a lambda expression, that's applied to the custom exception:

app.AddExceptionTranslation<CustomException>(HttpStatusCode.Forbidden, e => e.ToApiErrors());

I didn't think it would be relevant to the question but since it's interfering with the suggested solution/duplicate I'm in quite the pickle. Can I still invoke the method in some way?

Jonast92
  • 4,964
  • 1
  • 18
  • 32
  • I have a crazy idea, allow the custom exception types to register themselves. Say you give them an interface to inherit, `ICanRegister`, which has a method that takes the `IAppBuilder` and calls `SetUpExceptionHandling` with the appropriate `HttpStatusCode`. Then on startup in your Global.asax, you find all classes which implement `ICanRegister` and through reflection call the method to register. Now one tricky thing might be the instance of the custom exception. – Yuriy Faktorovich Mar 25 '19 at 17:30
  • My problem with what you're trying to do, is it means the `HttpStatusCode` that's equivalent to the custom exception now lives outside of it. At a minimum I'd have another extension set up where it checks whether your custom exception can provide the status code. And then you can call the generic method via reflection. – Yuriy Faktorovich Mar 25 '19 at 17:35
  • Personally I'd do this where the data structure is created. Currently you started with a concrete `T`, but lose that when you do `typeof(T)`. If you can keep the `T` around, you can use it with `AddExceptionTranslation`. I don't know what your data structure looks like, but if it's a `List` where `ExceptionInfo` has a `Type`, `StatusCode`, etc, then I'd have `ExceptionInfo : ExceptionInfo`, then `abstract void Register(IAppBuilder app)` on `ExceptionInfo`, and `override void Register(IAppBuilder app) => app.AddExceptionTranslation(...)` in `ExceptionInfo`. – canton7 Mar 25 '19 at 17:36
  • @YuriyFaktorovich I'm hiding a more complex implementation that AddExceptionTranslation takes care of. Each exception and data annotation has a unique subject with a unique key which will be used to identify each error that can occur in the API. This is then converted to an error object, consisting of the status code, message and unique key for the error, that that returned from the API when it throws an error. – Jonast92 Mar 25 '19 at 17:40
  • The answer in the duplicate question is unlikely to help me because one of the arguments is actually a lambda expression which I can't invoke because the invoked arguments must be objects. I'll try to find an answer to that or dig deeper in the recommendations in the comments. Thanks. – Jonast92 Mar 25 '19 at 18:07
  • @Jonast92 if there are considerations which affect the answer which weren't in your question, maybe you should open a new question but this time put those considerations in? – canton7 Mar 26 '19 at 10:00
  • @canton7 Of course. I did not know that this would affect the question until after I got pointed to the possible duplicate and I attempted to follow its implementation. It's true that the exact question itself is a duplicate, as its phrased. – Jonast92 Mar 26 '19 at 13:00
  • The problem is, *consumers* of generic types/methods are meant to know the types they want to use at their *compile time*. What you've got is a *runtime* type which you didn't "know" during your compilation. Which leaves either direct reflection-based code or some tricky `Expression` antics to run some *new* compilation. The answers shown on the previously linked question do outline these options. – Damien_The_Unbeliever Mar 26 '19 at 13:56

1 Answers1

1

What you need to do is make a generic method for each of the types in your collection. Here's how to do it for one type. The code below is highly contrived but shows how to deal with the lambda.

var exceptionType = typeof(CustomException);

Func<CustomException, ApiErrors> func = e => e.ToApiErrors();

typeof(Extensions)
    .GetMethod(nameof(Extensions.AddException))
    .MakeGenericMethod(exceptionType)
    .Invoke(null, new object[] { builder, HttpStatusCode.Accepted, func });

What this is doing:

  • From the Extensions class containing the AddExceptionTranslation method
  • we get that method, which is generic and then
  • make a version of that that is callable for the specified exception type
  • and then call that method, which, because it is static, we pass null for the object type

This solves your problem, except for one problem. You still have to "hard code" the exception type for every exception for the Func that is abstracted before the call to Invoke. To fix this, replace

Func<CustomException, ApiErrors> func = e => e.ToApiErrors();

with

var funcType = typeof(Func<,>).MakeGenericType(exceptionType, typeof(ApiErrors));
var errorsMethod = typeof(ApiErrorsExtension).GetMethod(nameof(ApiErrorsExtension.ToApiErrors));
var func = Delegate.CreateDelegate(funcType, errorsMethod);

which

  • creates a usable version of the Func<,>
  • gets the ToApiErrors() method you really are trying to call and
  • creates a delegate that can be passed to the AddExceptionTranslation method

Because this uses reflection it is slow. Because you are doing this at startup it shouldn't matter. If it does, there are libraries that can cache the reflection and emit faster IL code to speed it up, but, again, I wouldn't worry about it.

What you can do is package up the above code into its own method and then call that for each of the types in your collection.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • Thanks. How would I approach it when I have a lambda expression as the last argument in AddExceptionTranslation? That's surely problematic since it's not an object. The methods actually look like this: app.AddExceptionTranslation(HttpStatusCode.Forbidden, e => e.ToApiErrors()); – Jonast92 Mar 26 '19 at 14:46
  • @Jonast92 see my updated answer to deal with the lambda. – Kit Mar 26 '19 at 15:32
  • Thanks I'll take a look at this soon! – Jonast92 Mar 26 '19 at 15:34