Reputation: 1791
A bit of background, I'm working with some code someone else wrote. It's a REST API that you can issue queries and commands to and they are handled in a generic fashion.
The method for resolving queries is like so:
[HttpPost]
[Route("query")]
public async Task<Task> ResolveQuery(ApiQuery message)
{
var type = Type.GetType(message.QueryType, true);
var query = this.serializer.Deserialize(message.Payload, type);
var interfaceType = type.GetGenericInterfaces(typeof(IQuery<>)).FirstOrDefault();
var responseType = interfaceType.GetGenericArguments()[0];
var handleMethod = typeof(IQueryBus).GetMethod("ResolveAsync").MakeGenericMethod(responseType);
var task = handleMethod.Invoke(this.bus, new object[] { query }) as Task;
await task;
return task;
}
I'm pretty sure it's very unusual to be using tasks in this way.
So when the handleMethod is invoked, even though it's result is being cast as a Task, and I can see a valid result when I mouse over it during debugging, there is no task.Result
member for me to use for eventual casting.
Things I've tried:
I've tried casting this handleMethod.Invoke as the 'resopnseType' I need, using Convert.ChangeType(result, responseType);
but I get an exception for not implementing IConvertible. Do I really have to implement IConvertible for every type that I need returned?
I've also tried having the return type set to Task, but I then need to specify TResponse in ResolveQuery and this gets an internal server error (I'm not sure how I specify the TResponse from my calling code).
I've also tried asking the programmer what he was doing here. He does not know. He was following another programmer's original advice for this implementation, but that programmer isn't here at present, otherwise I'd be asking him!
Can anyone tell me how to correctly get my result from the task object and return it, in a RESTful manner, to the calling client?
Update - in response to @Yeldar Kurmangaliyev
public interface IQueryBus
{
Task<TResponse> ResolveAsync<TResponse>(IQuery<TResponse> query);
}
Upvotes: 3
Views: 124
Reputation: 1791
I have encountered that very same problem few weeks ago. Unfortunately I can't find the page where I read that sacred sentence but here it is:
"Awaiting generic methods through reflection is only possible with cast to dynamic
which resolves in Task< target type>"
So to give you an example how it should look in your case:
dynamic taskResult = await (dynamic)handleMethod.Invoke(this.bus, new object[] { query });
Yes, dynamic is not the best thing but helps in such scenarios where the compiler has no idea of what type would the generic T parameter.
Upvotes: 1
Reputation: 34234
There is no Result
, because it is just a non-generic Task
.
In order to get result you need a Task<T>
. Since you don't know T
in design-time, you need to use reflection again:
public async Task<object> ResolveQuery(ApiQuery message)
{
var type = Type.GetType(message.QueryType, true);
var query = this.serializer.Deserialize(message.Payload, type);
var interfaceType = type.GetGenericInterfaces(typeof(IQuery<>)).Single();
var responseType = interfaceType.GetGenericArguments().Single();
var handleMethod = typeof(IQueryBus).GetMethod("ResolveAsync").MakeGenericMethod(responseType);
var task = (Task)handleMethod.Invoke(this.bus, new object[] { query });
await task;
return typeof(Task<>).MakeGenericType(responseType).GetProperty("Result").GetValue(task);
}
It will create a Result
property for your specific Task<responseType>
and return its value.
I have slightly updated the existing code too, just as a suggestion:
[0]
and .FirstOrDefault()
to .Single()
to avoid NullReferenceException
, because it is expected that there is exactly one valueas Task
to direct cast, because it is expected to be Task
Upvotes: 1