Reputation: 7362
I have a method in my xamarin app that fetches some data from the server, and I need the method to either return the data if HTTP request was successful, or an "system error" string if the server returned an error, or "Internet Connectivity error" string if the was no connectivity.
I would like to know how can i set return type to return either one of them. I know Tuple can return multiple. But I believe its both, rather than either.
My Code
public async Task<List<AllReportVM>> GetAllReports(string token, string username)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
return JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
} else {
return "SystemError";
}
} else {
return "Internet Connectivity Error";
}
}
Upvotes: 2
Views: 507
Reputation: 125
My first choice would be to throw an exception in the error cases.
public async Task<List<AllReportVM>> GetAllReports(string token, string username)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
return JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
} else {
throw new Exception("SystemError");
}
} else {
throw new Exception("Internet Connectivity Error");
}
}
It would require the caller to catch the exception but then you only need a single return path.
My second choice would be to add an out argument to take the exception reason. NOTE: This approach won't work with async methods.
public Task<List<AllReportVM>> GetAllReports(string token, string username, out string error)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
error = null;
return JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
} else {
error = "SystemError";
return null;
}
} else {
error = "Internet Connectivity Error";
return null;
}
}
If that is not acceptable you could look into returning the Tuple with half of the tuple as null.
Upvotes: 1
Reputation: 149020
What you're asking for is effectively a discriminated union. C# doesn't really have something like this natively. There are a few ways to get around this. First, consider what I call the try/out pattern:
public bool GetAllResults(string token, string username, out List<AllReportVM> results);
This is okay for relatively simple methods, but it doesn't actually give you the error message (at least given the way it's usually implemented) and out
parameters don't work with async
methods. So we can rule this out.
The second option is to throw an exception on an error, and this what I'd recommend in 90% of situations:
public async Task<List<AllReportVM>> GetAllReports(string token, string username)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
return JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
} else {
throw new RequestException("SystemError");
}
} else {
throw new RequestException("Internet Connectivity Error");
}
}
And to call this you'd have to use:
try
{
var list = await obj.GetAllReports(token, username);
...
}
catch (RequestException ex)
{
Console.WriteLine(ex.Message);
}
This is a pretty solid, well established pattern. In fact, You probably should already be using exception handling for all sorts of other exceptions that could potentially occur within your application. However, it does have some performance impact and you should avoid using exceptions for simple control flow. This may not be an option for applications that need to make lots of requests and are expected to handle failures efficiently (e.g. batch-processing). When I've encountered situations like this in the past, I've found it useful to implement a custom class, for example:
public class RequestResult<T>
{
public bool Success { get; }
public T Result { get; }
public string ErrorMessage { get; }
private RequestResult(bool success, T result, string errorMessage)
{
this.Success = success;
this.Result = result;
this.ErrorMessage = errorMessage;
}
public static RequestResult<T> Success(T result)
{
return new RequestResult<T>(true, result, null);
}
public static RequestResult<T> Failure(string errorMessage)
{
return new RequestResult<T>(false, default(T), errorMessage);
}
}
public async Task<RequestResult<List<AllReportVM>>> GetAllReports(string token, string username)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
var result = JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
return RequestResult.Success(result);
} else {
return RequestResult.Failure("SystemError");
}
} else {
return RequestResult.Failure("Internet Connectivity Error");
}
}
Upvotes: 5
Reputation: 36
Looking at your code you probably want to throw exceptions instead, mainly because both strings you are returning seem to be indicating something exceptional occurred.
Also ask yourself "when this method returns, how will I handle the output?" If you don't know the type you can certainly do things like if(result is string), however do you want to litter your code with that? Isn't it easier to just handle an exception instead?
If you really need to do it this way you can return Task<object>, you'll just need to check the type of the result from the caller when you do that. For instance:
var result = await GetAllReports(token, username)
if (result is string)
{
//Do Something
}
if(result is List<AllReportVM>)
{
//Do Something
}
public async Task<object> GetAllReports(string token, string username)
{
var httpClient = GetHttpClient();
if (CrossConnectivity.Current.IsConnected) {
var response = await httpClient.GetAsync ("getdashboardreports.ashx").ConfigureAwait (false);
if (response.IsSuccessStatusCode) {
var content = response.Content;
string jsonString = await content.ReadAsStringAsync ().ConfigureAwait (false);
return JsonConvert.DeserializeObject<List<AllReportVM>> (jsonString);
} else {
return "SystemError";
}
} else {
return "Internet Connectivity Error";
}
}
Upvotes: 0
Reputation: 9704
If the only return types other than successful returns are errors than you should throw an exception. If you have a specific exception story in mind you should create a class that inherits from the Exception class and throw that. If an exception that describes your expected issue exists, then that's the one you should prefer. The calling method can then catch and handle it as required.
Upvotes: 0