Xamarin Form HttpClient Stuck - c#

I'm trying to get response from soundcloud API. Here is my code.
public static async Task<string> GetTheGoodStuff()
{
var client = new HttpClient(new NativeMessageHandler());
var response = await client.GetAsync("http://api.soundcloud.com/playlists?client_id=17ecae4040e171a5cf25dd0f1ee47f7e&limit=1");
var responseString = response.Content.ReadAsStringAsync().Result;
return responseString;
}
But it's stucks on var response = await client.GetAsync. How can I fix this?
Thanks!

I did just use your code in a PCL, only thing I changed is the url (to https) to satisfy iOS ATS requirements, and called it from an async method. Seems to work fine running on iOS device. I did grab references to Microsoft.Net.Http in the PCL, and ModernHttpClient in the PCL and in the platform-specific projects (via NuGet).
Your code in some PCL view model class:
using System.Net.Http;
using System.Threading.Tasks;
using ModernHttpClient;
public class ItemsViewModel
{
...
public async Task<string> GetPlaylist()
{
// Use https to satisfy iOS ATS requirements.
var client = new HttpClient(new NativeMessageHandler());
var response = await client.GetAsync("https://api.soundcloud.com/playlists?client_id=17ecae4040e171a5cf25dd0f1ee47f7e&limit=1");
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
...
}
Then in a PCL page class that instantiates and uses an instance of the view model:
public partial class ItemsPage : ContentPage
{
public ItemsPage()
{
InitializeComponent();
Vm = new ItemsViewModel();
BindingContext = Vm;
}
protected override async void OnAppearing()
{
var playlist = await Vm.GetPlaylist();
// Do something cool with the string, maybe some data binding.
}
// Public for data binding.
public ItemsViewModel Vm { get; private set; }
}
Hope this helps.

I have the same problem. I fixed it by:
var response = httpClient.GetAsync(ApiUrl).ConfigureAwait(false).GetAwaiter().GetResult();
you can try it.

Related

Variable not assigned to result fetch from URL in C#

I have been having this problem for some time so to reproduce the error I created a new Blazor project to show you all the files.
This is my index.razor file:
#using BlazorApp1.Models.TestSuiteModel;
#inject BlazorApp1.Services.TestSuiteService TestSuiteServ;
#page "/"
<b>
#TestSuitesResult.count;
</b>
#code {
protected override async Task OnInitializedAsync()
{
this.GetTestSuites();
}
TestSuiteModel TestSuitesResult;
protected async Task GetTestSuites()
{
TestSuiteServ.url = "https://dev.azure.com/****/_apis/test/Plans/12/suites?api-version=5.0";
TestSuiteServ.PersonalAccessToken = "****";
TestSuitesResult = await TestSuiteServ.GetTestSuites();
await base.OnInitializedAsync();
}
}
The problem is #TestSuitesResult.count; is always null and throws this error:
Below is my Model
namespace BlazorApp1.Models.TestSuiteModel
{
public class TestSuiteModel
{
public List<Value> value { get; set; }
public int count { get; set; }
}
// .. mode classes
}
Below is my Service
using BlazorApp1.Models.TestSuiteModel;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net.Http.Headers;
namespace BlazorApp1.Services
{
public class TestSuiteService
{
public string url { get; set; }
public string PersonalAccessToken { get; set; }
TestSuiteModel result;
public async Task<TestSuiteModel> GetTestSuites()
{
using (HttpClient client = new HttpClient())
{
// accept response as JSON
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
// add DevOps token to the HTTP Header request
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.Encoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", PersonalAccessToken)
)
)
);
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseData = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TestSuiteModel>(responseData);
}
}
}
}
That is the only thing I have in this project. I didn't modify or delete any file from the default Blazor project except registering the service in program.cs
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<TestSuiteService>(); <!-- my service -->
var app = builder.Build();
Also Newtonsoft is added so it could not be a missing library.
GetTestSuites is never awaited
protected override async Task OnInitializedAsync()
{
this.GetTestSuites();
}
It should be :
protected override async Task OnInitializedAsync()
{
await this.GetTestSuites();
}
Right now OnInitializedAsync completes and the page is rendered before GetTestSuites has a chance to complete.
There are other problems with TestSuiteService too. HttpClient is meant to be reused, not defined in a using block. Especially in Blazor, the HttpClient instance is provided by the browser. The entire GetTestSuites method could be replaced with a single await _httpClient.GetFromJsonAsync<TestSuiteModel>(url);, where _httpClient can be an HttpClient registered through AddHttpClient. Everything else is the default behavior of GetFromJsonAsync:
public interface ITestSuiteService
{
Task<TestSuiteModel> GetTestSuites(string url);
}
public class TestSuiteService:ITestSuiteService
{
public TestSuiteService(HttpClient client)
{
_httpClient=client;
}
public async Task<TestSuiteModel> GetTestSuites(string url)
{
var model=await _httpClient.GetFromJsonAsync<TestSuiteModel>(url);
return model;
}
}
This service can be registered as a typed HttpClient :
builder.Services.AddHttpClient<ITestSuiteService,TestSuiteService>(client=>{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
System.Text.Encoding.ASCII.GetBytes(
$":{PersonalAccessToken}")
)
)
);
});
At this point the token can be retrieved from Configuration.
Another option is to use GetAsyn, inspect the response and use HttpContent.ReadFromJsonAsync. This avoids the cost of exceptions, especially in APIs when responses like 429 (Too Many Requests) are expected :
public async Task<TestSuiteModel> GetTestSuites(string url)
{
var response=await _httpClient.GetAsync(url);
if(response.IsSuccessStatusCode)
{
var model=await response.Content.ReadFromJsonAsync<TestSuiteModel>();
return model;
}
else
{
...
}
}
The initial state of your object is null.
You have do do a null check here or provide a default object. You have three ways to fix this.
1: Check for null using if
<b>
#if(TestSuitesResult is not null)
{
#TestSuitesResult.count;
}
</b>
2: Check for null using the Elvis operator
<b>
#TestSuitesResult?.count;
</b>
3: Provide a default value
TestSuiteModel TestSuitesResult = new TestSuiteModel();
Also make sure to await your function in OnInitializedAsync otherwise you are calling your function and forgetting it.
In blazor 6 you should always check if your object value is null, because the page is ALWAYS prerendered before the lyfeevents execute.
Is the only way.
Is not a problem of your applicattion , is just a Blazor bug.
This error is being solved in .net7 with #bind-after new tag.
https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-preview-7/

HttpClient: This instance has already started one or more requests. Properties can only be modified before sending the first request

I have an ASP.NET MVC application which invokes an ASP.NET Web API REST Service each time a button is pressed in the UI.
Each time this button is pressed below DumpWarehouseDataIntoFile method is executed.
public class MyClass
{
private static HttpClient client = new HttpClient();
public async Task DumpWarehouseDataIntoFile(Warehouse myData, string path, string filename)
{
try
{
//Hosted web API REST Service base url
string Baseurl = "http://XXX.XXX.XX.X:YYYY/";
//using (var client = new HttpClient()) --> I have declared client as an static variable
//{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Serialize parameter to pass to the asp web api rest service
string jsonParam = Newtonsoft.JsonConvert.SerializeObject(myData);
//Sending request to find web api REST service resource using HttpClient
var httpContent = new StringContent(jsonParam, Encoding.UTF8, "application/json");
HttpResponseMessage Res = await client.PostAsync("api/Warehouse/DumpIntoFile", httpContent);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
// Some other sftuff here
}
//}
}
catch (Exception ex)
{
// Do some stuff here
} // End Try
} // End DumpWarehouseDataIntoFile method
} // End class
Warehouse class object:
public class Warehouse
{
public DataTable dt { get; set; }
public string Filepath { get; set; }
}
I have found in this post that pattern:
using (var myClient = new HttpClient())
{
}
is not recommended to be used since it leads to socket exhaustion (System.Net.Sockets.SocketException). There it is recommended to use HttpClient as static variable and reuse it as it helps to reduce waste of sockets. So I have used a static variable.
The problem with this approach (in my scenario) is that it only works first button is pressed, next times button is pressed and DumpWarehouseDataIntoFile method is executed, below exception is thrown:
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: This instance has already started
one or more requests. Properties can only be modified before sending
the first request.
As error says, properties like base address, etc. can only be modified once before sending the first request.
I have googled and found some solutions proposed:
First solution
So it seems like singleton pattern would be a good option, as proposed here. Below the singleton proposed by Alper:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class MyApiClient : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "my-api-client-v1";
private const string MediaTypeJson = "application/json";
public MyApiClient(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> PostAsync(string url, object input)
{
EnsureHttpClientCreated();
using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
{
using (var response = await _httpClient.PostAsync(url, requestContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
{
var strResponse = await PostAsync(url, input);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
{
var strResponse = await GetAsync(url);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PutAsync(string url, object input)
{
return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
}
public async Task<string> PutAsync(string url, HttpContent content)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.PutAsync(url, content))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> DeleteAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.DeleteAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
Usage
using (var client = new MyApiClient("http://localhost:8080"))
{
var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
The problem I see here is that if you call above code many times (in my case would be each time I press the button on the UI and I call DumpWarehouseDataIntoFile method), you create and instance of MyApiClient each time and therefore a new instance of HttpClient is created and I want to reuse HttpClient, not to make many instances of it.
Second solution
Creating a kind of factory as proposed here by Nico. Below the code he proposes:
public interface IHttpClientFactory
{
HttpClient CreateClient();
}
public class HttpClientFactory : IHttpClientFactory
{
static string baseAddress = "http://example.com";
public HttpClient CreateClient()
{
var client = new HttpClient();
SetupClientDefaults(client);
return client;
}
protected virtual void SetupClientDefaults(HttpClient client)
{
client.Timeout = TimeSpan.FromSeconds(30); //set your own timeout.
client.BaseAddress = new Uri(baseAddress);
}
}
Usage
public HomeController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
readonly IHttpClientFactory _httpClientFactory;
public IActionResult Index()
{
var client = _httpClientFactory.CreateClient();
//....do your code
return View();
}
Here again you create a new instance of HttpClient each time you call CreateClient. You do not reuse HttpClient object.
Third Solution
Making HTTP requests using IHttpClientFactory as explained here.
The problem is that it is only available for .NET Core, not standard ASP.NET Framework, though it seems it is available by installing this nuget package. It seems like it automatically manages efficiently HttpClient instances and I would like to apply it to my scenario. I want to avoid to
reinvent the wheel.
I have never used IHttpClientFactory and I have no idea on how to use it: configure some features like base address, set request headers, create an instance of HttpClient and then invoke PostAsync on it passing as parameter the HttpContent.
I think this is the best approach so could someone tell me the necessary steps I need to do in order to make the same things I do in DumpWarehouseDataIntoFile method but using IHttpClientFactory? I am a bit lost, I do not know how to apply IHttpClientFactory to do the same as I do within DumpWarehouseDataIntoFile method.
Any others solutions not proposed here and also some code snippets will be highly appreciated.
HttpClient
The HttpClient can throw InvalidOperationException in the following cases:
When the BaseAddress setter is called after a request has been sent out
When the Timeout setter is called after a request has been sent out
When the MaxResponseContentBufferSize setter is called after a request has been sent out
When an operation has already started and resend was requested
In order to avoid these you can set the first two on per request level, for example:
CancellationTokenSource timeoutSource = new CancellationTokenSource(2000);
await httpClient.GetAsync("http://www.foo.bar", timeoutSource.Token);
HttpClientFactory
You can use the IHttpClientFactory in .NET Framework with the following trick:
AddHttpClient registers the DefaultHttpClientFactory for IHttpClientFactory
Then you can retrieve it from the DI container
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());
container.ContainerScope.RegisterForDisposal(serviceProvider);
This sample uses SimpleInjector but the same concept can be applied for any other DI framework.
I'm not sure but will what happen if you move this lines to constructor:
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
I think that re-initialization is problem.
Better to add the request url and the headers at the message. Don't use httpClient.BaseAddress or httpClient.DefaultRequestHeaders unless you have a default requirement.
HttpRequestMessage msg = new HttpRequestMessage {
Method = HttpMethod.Put,
RequestUri = new Uri(url),
Headers = httpRequestHeaders;
};
httpClient.SendAsync(msg);
It works well for reusing the HttpClient for many requests

Xamarin Forms and Asp.Net Web Api

I have been following a youtube tutorial on connecting my a xamarin forms app to an asp.net web api. Unfortunately my Listview is not getting populated by data from the api.
The Xamarin forms app has the following Files:
RestClient.cs
public class RestClient<T> {
private const string WebServiceUrl = "http://localhost:49864/api/Oppotunities/";
public async Task<List<T>> GetAsync()
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(WebServiceUrl);
var OppotunityList = JsonConvert.DeserializeObject<List<T>>(json);
return OppotunityList ;
} }
MainViewModel.cs
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var oppotunitiesServices = new OppotunitiesServices();
OppotunitiesList = await oppotunitiesServices.GetOppotunitiesAsync();
}
OppotunityServices.cs
public class OppotunitiesServices
{
public async Task<List<Oppotunity>> GetOppotunitiesAsync()
{
RestClient<Oppotunity> restClient = new RestClient<Oppotunity >();
var oppotunitiesList = await restClient.GetAsync();
return oppotunitiesList;
}
}
If you are debugging from an emulator, you should not use localhost to reach your development machine. You have to use the IP address or your running service.
You can test IP addresses directly from the browser of your emulator so you don't waste time starting/stopping your app to debug this...
Hope it helps

Challenge in calling dependency service from Xamarin

I'm working on accessing REST Api from Xamarin forms. I have created an interface in portable class like this by adding
public interface IRestService<T>
{
void Post(T item, string resourceURL);
}
public interface IRepository
{
}
and in Drod Project I'm implemented the interface like this.
[assembly:Xamarin.Forms.Dependency(typeof(RestOperationsDroid))]
namespace VLog.Droid.DependencyServices
{
public class RestOperationsDroid : IRestService<IRepository>
{
private const string BaseURL = "http://127.0.0.1/logger/v1/";
HttpClient client;
public RestOperationsDroid()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
}
public async void Post(object item, string url)
{
var uri = new Uri(string.Format(BaseURL + url));
var jsoncontent = new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
response = await client.PostAsync(uri, jsoncontent);
if (response.IsSuccessStatusCode)
{
//Debug(#" TodoItem successfully saved.");
}
else
{
//throw new Exception("Failed to Post data");
}
}
}
}
Calling in Xamarin.Forms (PCL project)
DependencyService.Get<IRestService<Log>>().Post(_logitem, "sync");
I'm getting an error says :
Object reference not set to an instance of an object.
What went wrong in this implementation ?
You are trying to resolve an IRestService<Log> from the DependencyService, while your class implements IRestService<IRepository>. These are different types. You should implement a class that implements the specific type you want for IRestService<T>.

How to Unit Test method that uses a Webservice on Windows Phone?

I'm working on a project that retrieves information from an external webservice API, but I'm not sure how I'm supposed to test it, I'm quite new at Testing and I have done just a couple of Unit Test, but as far as I know I have to mock the webservice functionality, I've been looking for info regarding this subject but haven't found anything for Windows Phone yet. What's the standard procedure for these type of cases?
Here's a simple version of what I want to test:
public async Task<List<Song>> FetchSongsAsync(String query)
{
if (String.IsNullOrEmpty(query))
return null;
string requestUrl = "webservice url";
var client = new HttpClient();
var result = await client.GetStringAsync(new Uri(requestUrl,UriKind.Absolute));
try
{
var result = JsonConvert.DeserializeObject<RootObject>(result);
return result;
}
catch (Exception)
{
return null;
}
}
Thanks!
Decouple your code from its dependencies: make content loading and its deserialization replaceable:
private readonly IClient client;
private readonly ISerializer serializer;
public YourService(IClient client, ISerializer serializer)
{
_client = client;
_serializer = serializer;
}
public async Task<List<Song>> FetchSongsAsync(String query)
{
try
{
var result = await _client.GetStringAsync(new Uri("http://example.com"));
return _serializer.DeserializeObject<RootObject>(result);
}
catch (Exception)
{
return null;
}
}
The first thing that may help is understand and use dependency injection. Basically taking any dependencies of your object/method/etc and (as it states) injecting them into the object/method/etc. For example, you are having a difficult time figuring out how to test the method because the method depends on being able to access the web service. There are a couple things you can do after this.
One thing to do is to check out mocking frameworks such as Moq.
Another thing I recently did was I added an overloaded constructor (dependency injection) that takes a HttpMessageInvoker object (note HttpClient derives from this). This way I could instantiate the class with my own response message:
public class MyLoader()
{
protected HttpMessageInvoker MessageInvoker { get; set; }
private HttpRequestMessage requestMessage;
public MyLoader() // default constructor
{
MessageInvoker = new HttpClient();
}
public MyLoader(HttpMessageInvoker httpMessageInvoker)
{
MessageInvoker = httpMessageInvoker;
}
public object DoSomething()
{
var response = await MessageInvoker.SendAsync(requestMessage, cancellationTokenSource.Token);
}
Here is my mock message invoker:
public class MockMessageInvoker : HttpMessageInvoker
{
public string ResponseString { get; set; }
public MockMessageInvoker(string responseString)
: base(new HttpClientHandler())
{
ResponseString = responseString;
}
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return Task.Run<HttpResponseMessage>(() =>
{
HttpResponseMessage responseMessage = new HttpResponseMessage(
System.Net.HttpStatusCode.OK);
var bytes = Encoding.ASCII.GetBytes(ResponseString);
var stream = new System.IO.MemoryStream(bytes);
responseMessage.Content = new StreamContent(stream);
return responseMessage;
});
}
}
I can call it all like so:
MyLoader loader = new MyLoader(new MockMessageInvoker(validJsonResponse));
loader.DoSomething() // I've removed the dependency on the service and have control of the content in the response
It's quick and dirty, but does the trick.
Hope this helps.

Categories