WebAPI Client -- handle my own exception rather then AggregateException - c#

I have written a very simple WebApiClient extending HttpClient. The code is following. The main reason to do that was to throw MyOwnWebApiException when httpResponse.IsSuccessStatusCode is false.
public class WebApiClient : HttpClient
{
public WebApiClient(string apiBaseUrl)
{
this.BaseAddress = new Uri(apiBaseUrl);
this.DefaultRequestHeaders.Accept.Clear();
}
public void AddAcceptHeaders(MediaTypeWithQualityHeaderValue header)
{
this.DefaultRequestHeaders.Accept.Add(header);
}
public async Task<string> DoPost(string endPoint, Object dataToPost)
{
HttpResponseMessage httpResponse = await ((HttpClient)this).PostAsJsonAsync(endPoint, dataToPost);
if (httpResponse.IsSuccessStatusCode)
{
string rawResponse = await httpResponse.Content.ReadAsStringAsync();
return rawResponse;
}
else
{
string rawException = await httpResponse.Content.ReadAsStringAsync();
MyOwnWebApiErrorResponse exception =
JsonConvert.DeserializeObject<MyOwnApiErrorResponse>(rawException, GetJsonSerializerSettings());
throw new MyOwnWebApiException (exception.StatusCode,exception.Message,exception.DeveloperMessage,exception.HelpLink);
}
}
#region "Private Methods"
private static JsonSerializerSettings GetJsonSerializerSettings()
{
// Serializer Settings
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ObjectCreationHandling = ObjectCreationHandling.Auto
};
return settings;
}
#endregion
Following is the code of the class using WebApiClient.
class TestWebApiClient
{
private WebApiClient _client;
public ComputationProcessesWebApiClient()
{
_client = new WebApiClient("http://test.api/");
_client.AddAcceptHeaders(new MediaTypeWithQualityHeaderValue("application/json"));
}
public void GetData(string dataFor)
{
try
{
DataRequest request = new DataRequest();
request.dataFor = dataFor;
**// THIS LINE IS THROWING AGGREGATEEXCEPTION--- **I WANT MyOwnException ****
string response = _client.DoPost("GetData", request).Result; // Use the End Point here ....
}
catch (MyOwnWebApiException exception)
{
//Handle exception here
}
}
}
Question
In the TestWebApiClient class, i dont want to catch AggregateException, rather i want to keep it more elegent and catch MyOwnWebApiException, but the problem is the line ** _client.DoPost("GetData", request).Result** throws an AggregateException if something goes wrong from the WebApi. How to change the code so that from TestWebApiClient i only have to catch MyOwnException ??

This is as a result of synchronously waiting for your task. If you stay async and await your task instead, you'll find that your actual Exception is the one that is caught.
Compare the following below:
void Main()
{
TryCatch();
TryCatchAsync();
}
void TryCatch()
{
try
{
ThrowAnError().Wait();
}
catch(Exception ex)
{
//AggregateException
Console.WriteLine(ex);
}
}
async Task TryCatchAsync()
{
try
{
await ThrowAnError();
}
catch(Exception ex)
{
//MyException
Console.WriteLine(ex);
}
}
async Task ThrowAnError()
{
await Task.Yield();
throw new MyException();
}
public class MyException:Exception{};
Top hint for async/await? It's async/await all the way down. The moment you .Wait() or .Result on a Task, things start to get messy.

Related

I am calling a service as below and am getting compile error CS1983 The Return type of an async method must be Task

I have the following class:
public static class RequestGenerator
{
static string clientID;
static string clientSecret;
static string url = "https://servicestierdp1qa.workmgmt-bcbsm/v1.0/createWorkItem";
static HttpClient client = null;
static WorkResponse workResponse;
public static string generateRequest(WorkRequest workRequest)
{
if (client == null)
{
client = new HttpClient();
clientID = "myclientid";
clientSecret = "mysecret";
}
sendRequest(workRequest); //warning is moot because ensureSuccessStatusCode implicitly awaits
return workResponse.payloadArea.id;
}
static async Task sendRequest(WorkRequest workRequest)
{
try
{
string jsonRequest = JsonConvert.SerializeObject(workRequest);
HttpResponseMessage responseMessage = client.PostAsync(url,
new StringContent(jsonRequest,
Encoding.UTF8,
"application/json")).Result;
responseMessage.EnsureSuccessStatusCode();
string jsonResponse = await responseMessage.Content.ReadAsStringAsync();
workResponse = JsonConvert.DeserializeObject<WorkResponse>(jsonResponse);
}
catch (HttpRequestException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
}
This class is nearly identical to a class from another process that works. I don't know why I am getting this compile error.
The only real difference between this class and the one that works is the url, clientid and secret (in addition to names, etc)

How to get an Exception from another class c#

i have these class
//Class 1, ViewModel
public async System.Threading.Tasks.Task<JObject> ExecuteSystemObject(string parameters)
{
...
dynamic j = await ExternalProject.ExecuteSomething<MyModel>(parameters);
//How i can catch the error from the another class?
...
}
//Class2, Manager
public async Task<Object> ExecuteSomething<T>() where T : IModel, new()
{
...
WebResponse response = await ExternalProject.ExecuteRequestAsync(PostRequest);
...
}
//Class 3, from a binding Project
public static async Task<WebResponse> ExecuteRequestAsync(WebRequest request)
{
try
{
return await request.GetResponseAsync();
}
catch(WebException e)
{
var resp = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
dynamic obj = JsonConvert.DeserializeObject(resp);
//I have the message error here
var messageFromServer = obj.error.text;
throw e;
}
}
I can get the error only in the last class, if i try to get the WebException in the other, it will return null for me. Then, how can i pass that error to the main class(1º one, ViewModel)?
Always use throw; whenever you want to rethrow an exception for you to be able to retain the stacktrace.
public async System.Threading.Tasks.Task<JObject> ExecuteSystemObject(string parameters)
{
try
{
dynamic j = await ExternalProject.ExecuteSomething<MyModel>(parameters);
//How i can catch the error from the another class?
...
}
catch(Exception e)
{
//WebException will be caught here
}
}
public async Task<Object> ExecuteSomething<T>() where T : IModel, new()
{
try
{
WebResponse response = await ExternalProject.ExecuteRequestAsync(PostRequest);
}
catch(Exception)
{
throw;
}
}
public static async Task<WebResponse> ExecuteRequestAsync(WebRequest request)
{
try
{
//return await request.GetResponseAsync();
throw new WebException("Test error message");
}
catch(WebException e)
{
throw;
}
}
EDIT: Just a rule of thumb, only catch exceptions when you have something to do with it. If you are catching exceptions just to log it. Don't do it.

Exception thrown: 'System.TypeLoadException' in Unknown Module in UWP Background Task

This code gives me the exception
Exception thrown: 'System.TypeLoadException' in Unknown Module
public sealed class SampleBackgroundTask2 : IBackgroundTask
{
EasClientDeviceInformation currentDeviceInfo;
BackgroundTaskCancellationReason _cancelReason = BackgroundTaskCancellationReason.Abort;
BackgroundTaskDeferral _deferral = null;
IBackgroundTaskInstance _taskInstance = null;
ThreadPoolTimer _periodicTimer = null;
//
// The Run method is the entry point of a background task.
//
public void Run(IBackgroundTaskInstance taskInstance)
{
currentDeviceInfo = new EasClientDeviceInformation();
var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;
var settings = ApplicationData.Current.LocalSettings;
settings.Values["BackgroundWorkCost2"] = cost.ToString();
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
_deferral = taskInstance.GetDeferral();
_taskInstance = taskInstance;
_periodicTimer = ThreadPoolTimer.CreateTimer(new TimerElapsedHandler(PeriodicTimerCallbackAsync), TimeSpan.FromSeconds(1));
}
private async void PeriodicTimerCallbackAsync(ThreadPoolTimer timer)
{
try
{
var httpClient = new HttpClient(new HttpClientHandler());
string urlPath = (string)ApplicationData.Current.LocalSettings.Values["ServerIPAddress"] + "/Api/Version1/IsUpdatePersonal";
HttpResponseMessage response = await httpClient.PostAsync(urlPath,
new StringContent(JsonConvert.SerializeObject(currentDeviceInfo.Id.ToString()), Encoding.UTF8, "application/json")); // new FormUrlEncodedContent(values)
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
string jsonText = await response.Content.ReadAsStringAsync();
var customObj = JsonConvert.DeserializeObject<bool>(jsonText, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
if (customObj) // Если TRUE то да надо сообщить пользователю о необходимости обновления
{
ShowToastNotification("Ttitle", "Message");
}
}
}
catch (HttpRequestException ex)
{
}
catch (Exception ex)
{
}
finally
{
_periodicTimer.Cancel();
_deferral.Complete();
}
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
_cancelReason = reason;
}
}
If I comment async/await and HttpClient places then there is no exception.
So what's wrong with my code?
Or Is it done well to use UWP Background Task to make async GET/POST?
I have tried some classic solution like
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral _deferral = taskInstance.GetDeferral();
//
// Start one (or more) async
// Use the await keyword
//
// await SomeMethodAsync();
var uri = new System.Uri("http://www.bing.com");
using (var httpClient = new Windows.Web.Http.HttpClient())
{
// Always catch network exceptions for async methods
try
{
string result = await httpClient.GetStringAsync(uri);
}
catch (Exception ex)
{
// Details in ex.Message and ex.HResult.
}
}
_deferral.Complete();
}
but once I put HttpClient inside of SomeMethodAsync() it does not work with the error above.
This solution does not help HttpClient.GetAsync fails in background task with lock screen access and both TimeTrigger or MaintenanceTrigger
Thanks!
I simplified the solution a bit and removed the ThreadPoolTimer since I was not sure why it was being used from the code. Please mention if it is required for the solution.
If the ThreadPoolTimer is optional then you can try the following code :
public sealed class SampleBackgroundTask2 : IBackgroundTask
{
EasClientDeviceInformation currentDeviceInfo;
BackgroundTaskCancellationReason _cancelReason = BackgroundTaskCancellationReason.Abort;
BackgroundTaskDeferral _deferral = null;
//
// The Run method is the entry point of a background task.
//
public async void Run(IBackgroundTaskInstance taskInstance)
{
currentDeviceInfo = new EasClientDeviceInformation();
var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;
var settings = ApplicationData.Current.LocalSettings;
settings.Values["BackgroundWorkCost2"] = cost.ToString();
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
_deferral = taskInstance.GetDeferral();
await asynchronousAPICall();
_deferral.Complete(); //calling this only when the API call is complete and the toast notification is shown
}
private async Task asynchronousAPICall()
{
try
{
var httpClient = new HttpClient(new HttpClientHandler());
string urlPath = (string)ApplicationData.Current.LocalSettings.Values["ServerIPAddress"] + "/Api/Version1/IsUpdatePersonal";
HttpResponseMessage response = await httpClient.PostAsync(urlPath,
new StringContent(JsonConvert.SerializeObject(currentDeviceInfo.Id.ToString()), Encoding.UTF8, "application/json")); // new FormUrlEncodedContent(values)
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
string jsonText = await response.Content.ReadAsStringAsync();
var customObj = JsonConvert.DeserializeObject<bool>(jsonText, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All });
if (customObj) // Если TRUE то да надо сообщить пользователю о необходимости обновления
{
ShowToastNotification("Ttitle", "Message");
}
}
}
catch (HttpRequestException ex)
{
}
catch (Exception ex)
{
}
finally
{
_deferral.Complete();
}
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
_cancelReason = reason;
}
}

Throwing FormCancelledException from await instead of AggregateException in unit test

I'm currently trying to test the following code in an application that makes use of the Microsoft Bot Framework.
public async Task ResumeAfterCalculation_v2FormDialog(IDialogContext context, IAwaitable<Calculation_v2Form> result)
{
try
{
var extractedCalculationForm = await result;
//Removed additional code
}
catch (FormCanceledException ex)
{
var reply = "You have canceled the operation.";
await _chat.PostAsync(context, reply);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
context.Done<object>(null);
}
}
When a user types 'quit' to the bot the 'await result' code throws a FormCanceledException and the code quits the form.
When creating a test I implemented a class to mock the IAwaitable:
public class TaskAwaiterHelper<T> : IAwaiter<T>, IAwaitable<T>
{
public Task<T> Task { get; }
public TaskAwaiterHelper(T obj)
{
this.Task = System.Threading.Tasks.Task.FromResult(obj);
}
public TaskAwaiterHelper(Task<T> task)
{
this.Task = task;
}
public bool IsCompleted { get { return Task.IsCompleted; } }
public void OnCompleted(Action action)
{
SynchronizationContext context = SynchronizationContext.Current;
TaskScheduler scheduler = context == null ? TaskScheduler.Current
: TaskScheduler.FromCurrentSynchronizationContext();
Task.ContinueWith(ignored => action(), scheduler);
}
public T GetResult()
{
return Task.Result;
}
public IAwaiter<T> GetAwaiter()
{
return this;
}
}
I then created the following test:
[Fact]
public async Task ResumeAfterCalculation_v2FormDialog_WasCancelled_ThenCallsDone()
{
//Arrange
var chat = new Mock<IChatHelper>();
var calculationApi = new Mock<ICalculationApi>();
var dialogContextMock = new Mock<IDialogContext>();
var rootLuisDialog = new RootLuisDialog(chat.Object, calculationApi.Object);
var taskAwaiter = new TaskAwaiterHelper<Calculation_v2Form>(new Task<Calculation_v2Form>(() =>
{
throw new FormCanceledException("Error created for test test", null);
}));
taskAwaiter.Task.Start();
//Act
await rootLuisDialog.ResumeAfterCalculation_v2FormDialog(dialogContextMock.Object, taskAwaiter);
//Assert
chat.Verify(c => c.PostAsync(dialogContextMock.Object, "You have canceled the operation."), Times.Once());
dialogContextMock.Verify(t => t.Done<object>(null), Times.Once());
}
Now whatever I try to do I the exception that's being thrown in the IAwaitable is being wrapped in an AggregateException, so we always end up in the catch (Exception ex) instead of the desired catch (FormCanceledException ex)
Is there a way to make a Task throw a specific Exception instead of an AggregateException (I mean there should be as the bot framework itself seems to be able to do it).
I just found the answer, I basically created a new class:
public class ExceptionThrower : IAwaitable<Calculation_v2Form>
{
public IAwaiter<Calculation_v2Form> GetAwaiter()
{
throw new FormCanceledException("Error created for test test", null);
}
}
And just provided this to the method:
var exceptionThrower = new ExceptionThrower();
await rootLuisDialog.ResumeAfterCalculation_v2FormDialog(dialogContextMock.Object, exceptionThrower);

UWP Windows.Web.HttpClient fake for unit test

I try to do unit-test REST communication logic for UWP client. With reference to the answer for System.Web.HttpClient, I found that Windows.Net.HttpClient also accepts an arguement called IHttpFilter.
So, I try to make custom response with IHttpFilter but I don't know correct way to make a response.
public class TestFilter : IHttpFilter
{
public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
if (request.Method == HttpMethod.Get)
{
// response fake response for GET...
}
}
public void Dispose()
{
// do nothing
}
}
And the target method for unit-test is as below.
public async Task<string> PostResult(HttpClient httpClient, string username)
{
var json = new JsonObject
{
{"Username",
JsonValue.CreateStringValue(string.IsNullOrEmpty(username) ? CommonKey.UnAuthorizedUserPartitionKey : username)
},
};
var content = new HttpStringContent(json.Stringify());
content.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
// I want to make below line testable...
var response = await httpClient.PostAsync(new Uri(Common.ProcessUrl), content);
try
{
response.EnsureSuccessStatusCode();
return null;
}
catch (Exception exception)
{
return exception.Message ?? "EMPTY ERROR MESSAGE";
}
}
Note that It's NOT duplicate question related to System.Web.HttpClient mocking/faking. What I ask is Windows.Web.HttpClient specifically. I failed to implement with it.
Note that, Windows.Web.Http.IHttpClient is internal accessible and HttpClient is sealed. So hard to do Mock or inherit-and-override it.
While I agree with some that there are better ways to test HttpClient calls, I'll answer your question of how to create a "fake" response with an IHttpFilter implementation (System.Runtime.InteropServices.WindowsRuntime is your friend)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
namespace Project.UnitTesting
{
public class FakeResponseFilter : IHttpFilter
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
{
_fakeResponses.Add(uri, responseMessage);
}
public void Dispose()
{
// Nothing to dispose
}
public IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request)
{
if (_fakeResponses.ContainsKey(request.RequestUri))
{
var fakeResponse = _fakeResponses[request.RequestUri];
return DownloadStringAsync(fakeResponse);
}
// Alternatively, you might want to throw here if a request comes
// in that is not in the _fakeResponses dictionary.
return DownloadStringAsync(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
}
private IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> DownloadStringAsync(HttpResponseMessage message)
{
return AsyncInfo.Run(delegate (CancellationToken cancellationToken, IProgress<HttpProgress> progress)
{
progress.Report(new HttpProgress());
try
{
return Task.FromResult(message);
}
finally
{
progress.Report(new HttpProgress());
}
});
}
}
}

Categories

Resources