Generic Extension Method for Dynamic Member Invocation

275 views Asked by At

I am trying to build a generic extension method that can call a member function of a class dynamically and return a specified type. For some background, this is the general problem:

I am using Autorest to generate some client libraries for a swagger API. Because some GET routes within the API return different objects depending on the HTTP status code of the response, the method invocation returns object and the developer is responsible for casting the object themselves. I am trying to create a convenient wrapper for doing this cast in a generic way.

Here is an example of a typical function signature that would be wrapped up:

object IPet GetPets(string petName)

Note that this method might return a number of object types, depending on the HTTP status code. For instance, 200 might return a Dog object but 404 might return a Cat object.

This would be invoked through an Autorest generated client library like this:

AnimalApi animalClient = new AnimalApi(new Uri("http://myanimals.com"));
object pet = animalClient.Pet.GetPets("dog");
if(pet is Dog) {
    Console.WriteLine("Dog!");
} else {
    Console.WriteLine("Not Dog");
}

I would like to scoop up this manual casting functionality into something a bit more intuitive, here is what I am thinking:

AnimalApi animalClient = new AnimalApi(new Uri("http://myanimals.com"));
string petType = "Dog";
Dog pet = animalClient.Pet.CallMethod<IPet, Dog, string>( (api,type) => api.GetPets(type), petType);

In this scenario, any return other than objects of type 'Dog' would cause an exception to be thrown. Here is my attempt at implementation:

public static Tout CallMethod<Tin, Tout>(this Tin client, Expression<Action<Tin, Targ>> apiCall, params object[] args)
    where Tout : class {

    MethodCallExpression providedMethod = apiCall.Body as MethodCallExpression;
    if(providedMethod == null) {
        throw new ApplicationException("Invalid method call provded");
    }
    var method = providedMethod.Method;

    object responseData;
    try {
        // Call object-returning function
        responseData = method.Invoke(client, args);
    } catch(Exception error) {
        if(error.InnerException is HttpOperationException) {
            // Unknown error occurred
            var ex = error.InnerException as HttpOperationException;
            object content = JsonConvert.DeserializeObject(ex.Response.Content);
            throw new ServiceException(ex.Response.StatusCode + ": "
                + JsonConvert.SerializeObject(content));
        } else {
            throw error.InnerException;
        }
    }
    // Return formatted object if successful
    if(responseData is Tout) {
        return responseData as Tout;
        // Otherwise throw
    } else {
        // Deal with known error object responses
        if(responseData is ErrorResponse) {
            var error = responseData as ErrorResponse;
            throw new ServiceException(error);
        } else {
            // Unknown error occurred
            throw new ServiceException("Unknown response was received: "
                + JsonConvert.SerializeObject(responseData));
        }
    }
}

The problem I have here is of passing the function and arguments into the generic extension method. Without knowing the various possible numbers of arguments that might be required by the various API calls, how can I define Expression<Action<Tin, Targ>> generically? It seems to me like I would have to replicate this function with Expression<Action<T1, T2, T3>> and so on to accommodate varying length argumen lists.

I want an expressive way for people to interact with the API, such that it is easy to see what is happening. However, this mechanism should be robust to various API changes down the road. My current goal is to provide a way to encapsulate common object casting and error checking operations. Is there a better way to do this? For the moment I am working under the assumption that the server side swagger doc cannot change.

0

There are 0 answers