dealing with an error from a typed WebAPI HttpClient call - asp.net-web-api

I have a number of calls I make to a webapi client which return a Task
something like this
public async Task<TResp> GetMyThingAsync(TReq req)
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(BaseURI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xml"));
await HttpRuntime.Cache.GetToken().ContinueWith((t) =>
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("XXX", t.Result);
});
var httpResponseMessage = await client.PostAsXmlAsync<TReq>("This/That/", req);
httpResponseMessage.EnsureSuccessStatusCode();
var resp = await httpResponseMessage.Content.ReadAsAsync<TResp>();
return resp;
}
}
the calls to the api can of course return 500's or some other problem. EnsureSuccessStatusCode() obviously throws if something like that happens, but then its too late to do anything with any information in the response.
is there a nice way of dealing with this?
I understand you can add a messageHandler with the client, something like
HttpClient client = HttpClientFactory(new ErrorMessageHandler()) ..
var customHandler = new ErrorMessageHandler()
{ InnerHandler = new HttpClientHandler()};
HttpClient client = new HttpClient(customHandler);
is this the way to go? what would the ErroMessageHandler look like and do to return something useful to the calling controller...?
thanks muchly
nat

Creating a custom handler can be an elegant solution to go about logging the exception or validating the response. I am not sure if the called controller is waiting for a meaningful response from the clients end if it encounters an exception. I think the real important part is to make sure you (the client) handle the web apis errors gracefully.
This can be done in a couple of ways:
You can handle exceptions locally inside the calling method. You can use the HttpResponseMessage.IsSuccessStatusCode property which indicated if a bad response returned instead of calling httpResponseMessage.EnsureSuccessStatusCode() which throws an exception, and return a custom ErrorResponse (or do whatever you decide):
var client = new HttpClient() // No need to dispose HttpClient
client.BaseAddress = new Uri(BaseURI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xml"));
var token = await HttpRuntime.Cache.GetToken();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("XXX", token);
var httpResponseMessage = await client.PostAsXmlAsync<TReq>("This/That/", req);
if (!httpResponseMessage.IsSuccessStatusCode)
{
return Task.FromResult(new ErrorResponse()) // Create some kind of error response to indicate failure
}
var resp = await httpResponseMessage.Content.ReadAsAsync<TResp>();
return resp;
Create a ErrorLoggingHandler which can log exceptions (or do something else) received from the web api:
public class ErrorLoggingHandler : DelegatingHandler
{
private readonly StreamWriter _writer; // As a sample, log to a StreamWriter
public ErrorLoggingHandler(Stream stream)
{
_writer = new StreamWriter(stream);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
// This would probably be replaced with real error
// handling logic (return some kind of special response etc..)
_writer.WriteLine("{0}\t{1}\t{2}", request.RequestUri,
(int) response.StatusCode, response.Headers.Date);
}
return response;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_writer.Dispose();
}
base.Dispose(disposing);
}
}
Then, you can create your HttpClient using HttpClientFactory:
var httpclient = HttpClientFactory.Create(new ErrorLoggingHandler(new FileStream(#"Location", FileMode.OpenOrCreate)));

Related

Blazor Startup Error: System.Threading.SynchronizationLockException: Cannot wait on monitors on this runtime

I am trying to call an api during the blazor(client side) startup to load language translations into the ILocalizer.
At the point I try and get the .Result from the get request blazor throws the error in the title.
This can replicated by calling this method in the program.cs
private static void CalApi()
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"https://dummy.restapiexample.com/api/v1/employees");
string path = "ididcontent.json";
string response = httpClient.GetStringAsync(path)?.Result;
Console.WriteLine(response);
}
catch(Exception ex)
{
Console.WriteLine("Error getting api response: " + ex);
}
}
Avoid .Result, it can easily deadlock. You get this error because the mechanism is not (cannot be) supported on single-threaded webassembly. I would consider it a feature. If it could wait on a Monitor it would freeze.
private static async Task CalApi()
{
...
string response = await httpClient.GetStringAsync(path);
...
}
All events and lifecycle method overrides can be async Task in Blazor, so you should be able to fit this in.
In Program.cs
public static async Task Main(string[] args)
{
......
builder.Services.AddSingleton<SomeService>();
var host = builder.Build();
...
call your code here but use await
var httpClient = host.Services.GetRequiredService<HttpClient>();
string response = await httpClient.GetStringAsync(path);
...
var someService = host.Services.GetRequiredService<SomeService>();
someService.SomeProperty = response;
await host.RunAsync();
This is a example best:
var client= new ProductServiceGrpc.ProductServiceGrpcClient(Channel);
category = (await client.GetCategoryAsync(new GetProductRequest() {Id = id})).Category;

Async Http Request from component in Blazor

Im creating a list of components in Blazor, each one of these components need to request some data from a webpage. The list are created as follows on a .razor page:
#foreach(stringcomp in Complist){
<myComponent />
}
around 100 components are created. On all of these components the following URL request is preformed (using this code):
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await GetUrl("some url here");
}
}
public async Task<string> GetUrl(string url)
{
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("User-Agent", "get data service");
var response = await client.SendAsync(request).ConfigureAwait(false);
string res = null;
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var streamReader = new StreamReader(responseStream);
res = await streamReader.ReadToEndAsync().ConfigureAwait(false);
}
return res;
}
Doing this I'm running in to some problems where most of my calls to SendAsync never returns a value. I have come to understand that this is because of a lock-state but for the life of me can't figure out how to solve it. most similar answers suggest setting .ConfigureAwait(false) but this does not yeald a different result in my case.
So my question is: Hos can i request webbpages simultaneously in different components and be sure that they won't hang/lookup. As theres many requests that some times takes a long time (5-10 sec) to complete it's not an alternative to do them synchronously.
It might also be of importance to mention that me code and pages are separated, every .razor page are using #inherits to get its functions/logic
Try to use IHttpClientFactory as follows:
[Inject] public IHttpClientFactory clientFactory { get; set;}
using System.IO;
Your GetUrl method:
public async Task<string> GetUrl(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("User-Agent", "get data service");
var client = clientFactory.CreateClient();
var response = await client.SendAsync(request);
string res = null;
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
var streamReader = new StreamReader(responseStream);
res = await streamReader.ReadToEndAsync().ConfigureAwait(false);
}
return res;
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// More code here...
}
Hope this works...

How do I exercise Formatters in tests using HttpServer?

In my Web API app, I'm using HttpServer to contain my controller in unit tests, and I'm using HttpClient to call it directly, eg:
[Fact]
public void TestMyController()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("default", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
var server = new HttpServer(config);
var client = new HttpClient(server);
var response = client.GetAsync("http://localhost/api/test/values").Result;
}
I've noticed (by stepping through the debugger, and confirmed on other SO posts), that the JsonFormatter is not really running - it's initialized, but not exercised. Since this test isn't opening a socket, and the HttpClient is directly invoking the HttpServer through the HttpMessageHandler API, it does make sense that formatting/serialization isn't run because it's not needed.
In my case, I have some custom formatting/serialization/deserialization code that isn't being hit during these tests, but it's hit when I run in a real web server. I'd like to exercise that code in these tests; and it also just seems risky to exclude the serialization/deserialization code path when testing. Any advice on this?
Following is a quick example of what you could do to force formatters to go through serialization/deserialization. Here we are converting ObjectContent to StreamContent. In the below code, the call to CopyToAsync triggers a path where formatters are forced to serialize. In case of deserilization, in order to make sure we go through formatters we want the content to be of type other than ObjectContent as ReadAsAsync has internal logic which special cases ObjectContnent and we want to circumvent it.
HttpClient client = new HttpClient(new InMemoryHttpContentSerializationHandler(new HttpServer(config)));
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await ConvertToStreamContentAsync(request.Content);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Content = await ConvertToStreamContentAsync(response.Content);
return response;
}
private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
{
if (originalContent == null)
{
return null;
}
StreamContent streamContent = originalContent as StreamContent;
if (streamContent != null)
{
return streamContent;
}
MemoryStream ms = new MemoryStream();
await originalContent.CopyToAsync(ms);
// Reset the stream position back to 0 as in the previous CopyToAsync() call,
// a formatter for example, could have made the position to be at the end
ms.Position = 0;
streamContent = new StreamContent(ms);
// copy headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return streamContent;
}
}

how to perform post method in windows 8 metro?

I have followed the HttpClient samples but couldn't figure it out how to post a method with 2 parameters.
Below is what I tried but it return bad gateway error:
private async void Scenario3Start_Click(object sender, RoutedEventArgs e)
{
if (!TryUpdateBaseAddress())
{
return;
}
Scenario3Reset();
Scenario3OutputText.Text += "In progress";
string resourceAddress = "http://music.api.com/api/search_tracks";
try
{
MultipartFormDataContent form = new MultipartFormDataContent();
// form.Add(new StringContent(Scenario3PostText.Text), "data");
form.Add(new StringContent("Beautiful"), "track");
form.Add(new StringContent("Enimem"), "artist");
HttpResponseMessage response = await httpClient.PostAsync(resourceAddress, form);
}
catch (HttpRequestException hre)
{
Scenario3OutputText.Text = hre.ToString();
}
catch (Exception ex)
{
// For debugging
Scenario3OutputText.Text = ex.ToString();
}
}
I looked all over the internet, but couldn't find any working examples or documents that show how to perform the http post method. Any materials or samples would help me a lot.
Try FormUrlEncodedContent instead of MultipartFormDataContent:
var content = new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("track", "Beautiful"),
new KeyValuePair<string, string>("artist", "Enimem")
}
);
I prefer to take the following approach where you set the POST data into the request content body. Having to debug it is much easier!
Create your HttpClient object with the URL you're posting to:
string oauthUrl = "https://accounts.google.com/o/oauth2/token";
HttpClient theAuthClient = new HttpClient();
Form your request with the Post method to your url
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, oauthUrl);
Create a content string with your parameters explicitly set in POST data format and set these in the request:
string content = "track=beautiful" +
"&artist=eminem"+
"&rating=explicit";
request.Method = HttpMethod.Post;
request.Content = new StreamContent(new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(content)));
request.Content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
Send the request and get a response:
try
{
HttpResponseMessage response = await theAuthClient.SendAsync(request);
handleResponse(response);
}
catch (HttpRequestException hre)
{
}
Your handler will be called once the request returns and will have response data from your POST. The following example shows a handler that you could put a breakpoint into to see what the response content is, at that point, you could parse it or do whatever you need to do with it.
public async void handleResponse(HttpResponseMessage response)
{
string content = await response.Content.ReadAsStringAsync();
if (content != null)
{
// put your breakpoint here and poke around in the data
}
}

Return response from HTTPWebRequest somehow?

I'm trying to validate a user in my WP7 app by validating username/pass on a server.
In my "validate user" method I create a HTTPWebRequest with the stuff I need validated.
Then as far as I can see, the only option to execute is to use request.BeginGetResponse, with an async callback.
But I want to return the response from the request in the same method that I created the request in, how can I accomplish this?
I'm not sure why you would like to make it return in the calling method. I would just use the standard WebClient behavior like below:
public void ValidateUser()
{
WebClient webClient = new WebClient();
Uri uri = new Uri(url);
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
webClient.DownloadStringAsync(new Uri(url));
}
void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//Validate result from response by using e.Result
}
BeginGetResponse returns an IAsyncResult which has a wait handle that is signalled when the request completes. All you have to do is wait on this handle: [ doesn't work on WP7 ]
UPDATE: use a ManualResetEvent
var mre = new ManualResetEvent( false );
var iar = myHttpWebRequest.BeginGetResponse( state => mre.Set(), null );
mre.WaitOne();
var response = myHttpWebRequest.EndGetResponse( iar );
The only real way to achieve what you need would be to create an event in your worker class, and fire that event when the async call completes, so (pseudocode)...
void StartDownload(object[] parameters)
{
var req = HttpWebRequest.Create("http://google.com");
req.BeginGetRequestStream(Completed_handler, req);
}
void Completed_handler(object sender, DownloadStringCompletedEventArgs e)
{
var request = (HttpWebRequest)result.AsyncState;
var response = request.EndGetResponse(result);
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
var contents = reader.ReadToEnd();
NotifyCallerOfContent(contents)
}
}
public event EventHandler<MyCustomEventArgsClass> DownloadHasFinished;
public NotifyCallerOfDownload(string content)
{
if(null != DownloadHasFinished)
{
DownloadHasFinished(this, new MyCustomEventArgsClass(content));
}
}
and then subscribe to the DownloadHasFinished event in your calling class.
As for calling out specifically by a HttpWebRequest, take a look at this question, where you will find some working samples.

Resources