I want to:
-
return a generic type, can be for example ICollection OR string, needs to be flexible. this will be returned by the restapi client that consumes the api.(different endpoints have different return types)
-
pass in some kind of delegate containing the method to be called from the actual rest client.
rest client is an Interface IClient that is resolved like this, it is just and instance of the api client for the application.
public static IClient GetClient()
{
return App.Current.Handler.MauiContext.Services.GetService<IClient>();
}
you can then access api like this (InjectedApiClient is a static class)
var client = InjectedApiClient.GetClient();
var languages = client.GetAllLanguagesAsync()) // get all languages from the api ICollection<Language> return type
In the past I remember using delegates like this:
public async Task DoSomething(Func<Task> methodToExecute)
{
//some generic logic here that can be applied to everything
await methodToExecute();
//some generic logic here that can be applied once this is done or failed
}
public async Task FlagOrder(Guid orderId)
{
await DoSomething(async () => await SetOrderStatusToFlagged(orderId));
}
I was trying to use the same approach when designing the following logic:
//this method should accept any api call that does not need retrying and be flexible to return any type
public static async Task<T> ExecuteCall<T>(Func<Task<T>> methodToExecute)
{
try
{
return await methodToExecute();
}
catch (HttpRequestException)
{
return default(T);
//log this to queue to do
}
}
//this method should accept any api call that needs retrying and be flexible to return any type
public static async Task<T> ExecuteWithRetry<T>(Func<Task<T>> methodToExecute)
{
try
{
return await _retryPolicy.ExecuteAsync(async () =>
{
return await methodToExecute();
});
}
catch (HttpRequestException)
{
return default(T);
//log this to queue to do
}
}
the following code works but I dont like the fact I need to call GetClient and pass it like this for every call in the app:
var client = InjectedApiClient.GetClient();
var machines = await InjectedApiClient.ExecuteWithRetry(async () => await
client.GetAllMachinesAsync());
var languages = await InjectedApiClient.ExecuteWithRetry(async () => await
client.GetAllLanguagesAsync());
Is there any way I could do the following:
public static async Task<T> ExecuteCall<T>(Func<Task<T>> methodToExecute)
{
try
{
//I want to get the client here so dont need to instantiate that everywhere I am
//making api calls, also dont want to pass it as an argument
// but execute the method passed down as a methodToExecute here with this client
var client = InjectedApiClient.GetClient();
return await //hey client take the passed delegate(methodToExecute) and execute that and return whatever it returns.
}
catch (HttpRequestException)
{
return default(T);
//log this to queue to do
}
}
When I pass the client as an argument this refactoring is loosing its purpose I dont want to
have execute method for each individual api call just one and reuse it for everything.
Also calling get client inside this method will only allow me to manually choose which method to call from the Iclient so again will need multiple methods so the generic would be useless. I want to have only 2 methods for any possible outcome.
>Solution :
It seems that you need to accept Func<IClient, Task<T>> (see the Func<T,TResult> delegate), i.e. something along these lines:
public static async Task<T> ExecuteCall<T>(Func<IClient, Task<T>> methodToExecute)
{
try
{
var client = InjectedApiClient.GetClient();
return await methodToExecute(client);
}
catch (HttpRequestException)
{
return default(T);
//log this to queue to do
}
}
And usage:
var machines = await InjectedApiClient.ExecuteCall(c => c.GetAllMachinesAsync());
You also can go full generic with:
public async Task<TResult> ExecuteCall<T, TResult>(Func<T, Task<TResult>> method)
{
T t = ...;
return await method(t);
}
With a bit more convoluted usage (another option – specify both generic parameters):
await InjectedApiClient.ExecuteCall((IClient c) => c.GetAllMachinesAsync());