I'm working on a Xamarin.Forms application in which I'm downloading some data from the server and showing them on the screen. This data is downloaded every 5-10 seconds in order to keep the application updated. My code for the data download looks like this:
public async Task<List<string>> RefreshProgressPageAsync()
{
var uri = new Uri(string.Format("http://someurladdress1"));
List<string> returnValue = new List<string>();
try
{
var response = await client.GetAsync(uri);
string allResult = string.Empty;
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
string[] valuePartsArray = result.Split(',');
string id = valuePartsArray[0].Substring(valuePartsArray[0].IndexOf(':') + 1);
string name = valuePartsArray[1].Substring(valuePartsArray[1].IndexOf(':') + 1);
string value = valuePartsArray[2].Substring(valuePartsArray[2].IndexOf(':') + 1);
returnValue.Add(string.Format("{0}|{1}|{2}", id, name, value));
}
uri = new Uri(string.Format("http://someurladdress2"));
response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
string[] valuePartsArray = result.Split(',');
string id = valuePartsArray[0].Substring(valuePartsArray[0].IndexOf(':') + 1);
string name = valuePartsArray[1].Substring(valuePartsArray[1].IndexOf(':') + 1);
string value = valuePartsArray[2].Substring(valuePartsArray[2].IndexOf(':') + 1);
returnValue.Add(string.Format("{0}|{1}|{2}", id, name, value));
}
return returnValue;
}
catch (Exception ex)
{
Debug.WriteLine(#" ERROR {0}", ex.Message);
return new List<string>();
}
}
Client is the HttpClient. There are two calls for the client because I want to download two different set of data in one RefreshProgressPageAsync call.
Now my problem with this is when second await client.GetAsync(uri) happens, I get a null point exception somewhere in the GetAsynch call, which is not caught by the try-catch block. I do have a workaround by not parsing the string and instead send it as is, but my question is what could cause this NullPointException?
Related
static void Main(string[] args)
{
token objtoken = new token();
var location = AddLocations();
OutPutResults outPutResultsApi = new OutPutResults();
GCPcall gCPcall = new GCPcall();
OutPutResults finaloutPutResultsApi = new OutPutResults();
var addressdt = new AddressDataDetails();
finaloutPutResultsApi.addressDatas = new List<AddressDataDetails>();
Console.WriteLine("Hello World!");
List<string> placeId = new List<string>();
var baseUrl = "https://maps.googleapis.com/maps/api/place/textsearch/json?";
var apiKey = "&key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".Trim();
foreach (var itemlocations in location)
{
var searchtext = "query=" + itemlocations.Trim();
var finalUrl = baseUrl + searchtext + apiKey;
gCPcall.RecursiveApiCall(finalUrl, ref placeId, objtoken.NextToken);
}
var ids = gCPcall.myPalceid;
}
public List<string> RecursiveApiCall(string finalUrl, ref List<string> placeId, string nextToken = null)
{
try
{
var token = "&pagetoken=" + nextToken;
using (var client = new HttpClient())
{
var responseTask = client.GetAsync(finalUrl + token);
responseTask.Wait();
var result = responseTask.Result;
if (result.IsSuccessStatusCode)
{
var readTask = result.Content.ReadAsStringAsync();
readTask.Wait();
var students = readTask.Result;
Rootobject studentsmodel = JsonConvert.DeserializeObject<Rootobject>(students);
nextToken = studentsmodel.next_page_token;
foreach (var item in studentsmodel.results)
{
placeId.Add(item.place_id);
}
}
}
if (nextToken != null)
{
RecursiveApiCall(finalUrl, ref placeId, nextToken);
}
return placeId;
}
catch (Exception ex)
{
throw;
}
}
My recursive method has some issue. Here whenever I am debugging this code it work fine. It goes in recursive call twice.
As debugging result I am getting list place_id with 20 items in first call and next call 9 items total 29 items in place_id object which is correct in static main method.
But if I run without debugging mode I am getting only 20 place_id. next recursive iteration data is missing even if it has next valid token.
I don't have any clue why this is happening. Can someone tell me what is the issue with my code?
Here are my suggestions, which may or may not solve the problem:
// First of all, let's fix the signature : Go async _all the way_
//public List<string> RecursiveApiCall(string finalUrl, ref List<string> placeId, string nextToken = null)
// also reuse the HttpClient!
public async Task ApiCallAsync(HttpClient client, string finalUrl, List<string> placeId, string nextToken = null)
{
// Loop, don't recurse
while(!(nextToken is null)) // C# 9: while(nextToken is not null)
{
try
{
var token = "&pagetoken=" + nextToken;
// again async all the way
var result = await client.GetAsync(finalUrl+token);
if (result.IsSuccessStatusCode)
{
// async all the way!
var students = await result.Content.ReadAsStringAsync();
Rootobject studentsmodel = JsonConvert.DeserializeObject<Rootobject>(students);
nextToken = studentsmodel.next_page_token;
foreach (var item in studentsmodel.results)
{
// Will be reflected in main, so no need to return or `ref` keyword
placeId.Add(item.place_id);
}
}
// NO recursion needed!
// if (nextToken != null)
// {
// RecursiveApiCall(finalUrl, ref placeId, nextToken);
// }
}
catch (Exception ex)
{
// rethrow, only is somewhat useless
// I'd suggest using a logging framework and
// log.Error(ex, "Some useful message");
throw;
// OR remove try/catch here all together and wrap the call to this method
// in try / catch with logging.
}
}
Mind that you'll need to make your main :
async Task Main(string[] args)
and call this as
await ApiCallAsync(client, finalUrl, placeId, nextToken);
Also create an HttpClient in main and reuse that:
"HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors." - Remarks
... which shouldn't do much harm here, but it's a "best practice" anyhow.
Now, as to why you get only 20 instead of expected 29 items, I cannot say if this resolves that issue. I'd highly recommend to introduce a Logging Framework and make log entries accordingly, so you may find the culprid.
I have a API Post method that takes is a string which represents a Bae64 string of bytes from a word document that the API converts to PDF. My test client sends multiple documents, each on its own task, to the API to be converted. The problem is with concurrency and writing the files. I end up with a file in use since the calls are parallel. I have tried a lot of different way to block the conversion process until a document is converted but none of it has worked. Everything works fine if it's jsut a single file being converted but as soon as it's 2 or more, the problem happens. Can anyone guide me in the correct direction to solve this issue?
API:
[HttpPost]
public async Task<SimpleResponse> Post([FromBody]string request)
{
var response = new SimpleResponse();
Task t = Task.Factory.StartNew(async () =>
{
try
{
Converter convert = new Converter();
var result = await convert.CovertDocToPDF(request, WebConfigurationManager.AppSettings["tempDocPath"], WebConfigurationManager.AppSettings["tempPdfPath"]);
response.Result = result;
response.Success = true;
}
catch (Exception ex)
{
response.Exception = ex;
response.Success = false;
response.Errors = new List<string>();
response.Errors.Add(string.Format("{0}, {1}", ex.Message, ex.InnerException?.Message ?? ""));
}
});
t.Wait();
return response;
}
Conversion code
public Task<string> CovertDocToPDF(string blob, string tempDocPath, string tempPdfPath)
{
try
{
// Convert blob back to bytes
byte[] bte = Convert.FromBase64String(blob);
// Process and return blob
return Process(bte, tempDocPath, tempPdfPath);
}
catch (Exception Ex)
{
throw Ex;
}
}
private async Task<string> Process(byte[] bytes, string tempDocPath, string tempPdfPath)
{
try
{
string rs = RandomString(16, true);
tempDocPath = tempDocPath + rs + ".docx";
tempPdfPath = tempPdfPath + rs + ".pdf";
// This is where the problem happens with concurrent calls. I added
// the try catch when the file is in use to generate a new
// filename but the error still happens.
try
{
// Create a temp file
File.WriteAllBytes(tempDocPath, bytes);
}
catch (Exception Ex)
{
rs = RandomString(16, true);
tempDocPath = tempDocPath + rs + ".docx";
tempPdfPath = tempPdfPath + rs + ".pdf";
File.WriteAllBytes(tempDocPath, bytes);
}
word.Application app = new word.Application();
word.Document doc = app.Documents.Open(tempDocPath);
doc.SaveAs2(tempPdfPath, word.WdSaveFormat.wdFormatPDF);
doc.Close();
app.Quit(); // Clean up the word instance.
// Need the bytes to return the blob
byte[] pdfFileBytes = File.ReadAllBytes(tempPdfPath);
// Delete temp files
File.Delete(tempDocPath);
File.Delete(tempPdfPath);
// return blob
return Convert.ToBase64String(pdfFileBytes);
}
catch (Exception Ex)
{
throw Ex;
}
}
Client:
public async void btnConvert_Click(object sender, EventArgs e)
{
var response = await StartConvert();
foreach (SimpleResponse sr in response)
{
if (sr.Success)
{
byte[] bte = Convert.FromBase64String(sr.Result.ToString());
string rs = RandomString(16, true);
string pdfFileName = tempPdfPath + rs + ".pdf";
if (File.Exists(pdfFileName))
{
File.Delete(pdfFileName);
}
System.IO.File.WriteAllBytes(pdfFileName, bte);
}
else
{
}
}
}
private async Task<IEnumerable<SimpleResponse>> StartConvert()
{
var tasks = new List<Task<SimpleResponse>>();
foreach (string s in docPaths)
{
byte[] bte = File.ReadAllBytes(s);
tasks.Add(ConvertDocuments(Convert.ToBase64String(bte)));
}
return (await Task.WhenAll(tasks));
}
private async Task<SimpleResponse> ConvertDocuments(string requests)
{
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
client.BaseAddress = new Uri(BaseApiUrl);
client.DefaultRequestHeaders.Add("Accept", "application/json");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//application/json
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, BaseApiUrl + ApiUrl);
var data = JsonConvert.SerializeObject(requests);
request.Content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage response1 = await client.PostAsync(BaseApiUrl + ApiUrl, request.Content).ConfigureAwait(false);
var response = JsonConvert.DeserializeObject<SimpleResponse>(await response1.Content.ReadAsStringAsync());
return response;
}
}
Random String Generator
public string RandomString(int size, bool lowerCase = false)
{
var builder = new StringBuilder(size);
// Unicode/ASCII Letters are divided into two blocks
// (Letters 65–90 / 97–122):
// The first group containing the uppercase letters and
// the second group containing the lowercase.
// char is a single Unicode character
char offset = lowerCase ? 'a' : 'A';
const int lettersOffset = 26; // A...Z or a..z: length = 26
for (var i = 0; i < size; i++)
{
var #char = (char)_random.Next(offset, offset + lettersOffset);
builder.Append(#char);
}
return lowerCase ? builder.ToString().ToLower() : builder.ToString();
}
First, get rid of Task.Factory.StartNew ... t.Wait() - you don't need an additional task, the root level method is async and your blocking Wait just spoils the benefits of async by blocking synchronously.
Second, like a comment suggested above, the file name random string generator is most likely to be not really random. Either do not supply anything to the seed value of your pseudo-random gen, or use something like Environment.TickCount which should be sufficient for this. Guid.NewGuid() will work too.
Another good option for temp files is Path.GetTempFileName (also generates an empty file for you): https://learn.microsoft.com/en-us/dotnet/api/system.io.path.gettempfilename?view=netstandard-2.0
[HttpPost]
public async Task<SimpleResponse> Post([FromBody]string request)
{
var response = new SimpleResponse();
try
{
...
var result = await convert.CovertDocToPDF(...);
...
}
catch (Exception ex)
{
...
}
return response;
}
Based on your code it seems that you have a "faulty" random string generator for file name (I would say _random.Next is a suspect, possibly some locking and/or "app wide" instance could fix the issue). You can use Guid.NewGuid to create random part of file name (which in theory can have collisions also but in most practical cases should be fine) or Path.GetTempFileName:
var rs = Guid.NewGuid().ToString("N");
tempDocPath = tempDocPath + rs + ".docx";
tempPdfPath = tempPdfPath + rs + ".pdf";
I had this piece of code that initially worked fine. However, After adding it to a class where I store my methods that are reused, it keeps failing. The exception that is caught states that the CancellationTokenSource has been Disposed. Can someone point me in the right direction?
I have tried creating a new client and Adding CancellationToken.None to the PutAsync() method from HTTPClient Class but it still fails with the CancellationTokenSource Disposed exception.
public async void AddProduct(Product product)
{
string storeId = "";
try
{
var storeData = JObject.Parse(Connect.Json).SelectToken("store").ToString();
var stores = JsonConvert.DeserializeObject<List<Store>>(storeData);
var store = stores[0];
storeId = store.Id;
store.Products.Add(product);
ProdInfo info = new Info();
foreach(Product p in store.Products)
{
info.AddedProducts = + p.Id;
}
var content = JsonConvert.SerializeObject(info);
using (Connect.Client)
using (var response = await Connect.Client.PutAsync(_url + "/stores/" + storeId, new StringContent(content)))
{
var cont = response.Content;
string result = await cont.ReadAsStringAsync();
if ((int)response.StatusCode == 200)
{
this.JobResult = result;
//this.JobResult = "Store has been successfully updated";
}
else
{
this.JobResult = result;
//this.JobResult = "Store was not updated!";
}
}
}
catch (Exception ex)
{
//this.JobResult = "Store has not been updated due to an error.";
this.JobResult = ex.Message;
}
}
I was able to solve this by simple removing 'using(Connect.Client)' from all of my methods. As #sellotape stated, They were disposing of the HttpClient before I was able to use it again. Thank you all for your contributions.
I am trying to call HttpClient request inside for loop as follows. It needs to do multiple consecutive calls to third party rest api.
But it only gives me fist service call result while loop exit before getting result from rest of the service call.
private void Search()
{
try
{
var i = 1;
using (var httpClient = new HttpClient())
{
while (i < 5)
{
string url = "https://jsonplaceholder.typicode.com/posts/" + i;
var response = httpClient.GetAsync(url).Result;
string jsonResult = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(jsonResult.ToString());
i++;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
When I run with debug points the program gives me all the result. But when I run it without debug points it gives me only the first result.
I tried this with using async, await methods too. It also gives me same result.
As I feel Program needs to wait until the async call returns data.
Please help me to solve this.
EDIT - async way
private async Task<string> SearchNew()
{
try
{
var i = 1;
var res = string.Empty;
using (var httpClient = new HttpClient())
{
while (i < 5)
{
string url = "https://jsonplaceholder.typicode.com/posts/" + i;
var response = httpClient.GetAsync(url).Result;
string jsonResult = await response.Content.ReadAsStringAsync();
res = res + jsonResult + " --- ";
i++;
}
}
return res;
}
catch (Exception ex)
{
return ex.Message;
}
}
Both are giving same result.
There's a few things here that you should be doing. First, move the HttpClient creation outside of your method and make it static. You only need one of them and having multiple can be really bad for stability (see here):
private static HttpClient _client = new HttpClient();
Next, extract the calls to the HttpClient into a single method, something simple like this:
//Please choose a better name than this
private async Task<string> GetData(string url)
{
var response = await _client.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
And finally, you create a list of tasks and wait for them all to complete asynchronously using Task.WhenAll:
private async Task<string[]> SearchAsync()
{
var i = 1;
var tasks = new List<Task<string>>();
//Create the tasks
while (i < 5)
{
string url = "https://jsonplaceholder.typicode.com/posts/" + i;
tasks.Add(GetData(url));
i++;
}
//Wait for the tasks to complete and return
return await Task.WhenAll(tasks);
}
And to call this method:
var results = await SearchAsync();
foreach (var result in results)
{
Console.WriteLine(result);
}
I have a list of reports page. I have a download button for each report. If I click 4-5 reports download button I only get the last one downloaded. Below you can see my code
public async Task<ActionResult> Download(ReportPage currentPage, string id)
{
var token = RystadIdentity.Current.AuthenticatedUser.Token;
try
{
DocumentRequestInput documentRequestInput = new DocumentRequestInput(int.Parse(id), 2);
documentRequestInput.Add(token);
}
catch
{ }
Report report = await RystadGlobal.api.GetReport(token, int.Parse(id));
string ext = Path.GetExtension(report.fileName);
byte[] byteArray = await RystadGlobal.api.DownloadReport(token, report.Id);
if (ext.ToLower() == ".pdf")
{
var name = RystadIdentity.Current.AuthenticatedUser.UserInfo.name;
var company = RystadIdentity.Current.AuthenticatedUser.UserInfo.company;
try
{
byteArray = PdfWatermarker.Watermark(byteArray, name, company);
}
catch (Exception ex)
{
//Ignore if failed and give the user the pdf anyway
}
}
return File(byteArray, System.Net.Mime.MediaTypeNames.Application.Octet, report.fileName);
}
public async Task<byte[]> DownloadReport(Token token, int reportId)
{
clientForDownloading.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.token_type, token.access_token);
var result = await clientForDownloading.GetAsync("api/" + ApiVersion + "/LibraryDocument/" + reportId + "/download");
if (!result.IsSuccessStatusCode)
throw new ApiException("Could not download the report");
var contentBytes = await result.Content.ReadAsByteArrayAsync();
return SevenZipHelper.Decompress(contentBytes);
}
public async Task<Report> GetReport(Token token, int reportId)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.token_type, token.access_token);
var result = await client.GetAsync("api/" + ApiVersion + "/LibraryDocument/" + reportId);
if (!result.IsSuccessStatusCode)
throw new ApiException("Could not get report");
Report report = await result.Content.ReadAsAsync<Report>();
return report;
}
Does anyone see the issue which I am missing?
You can try to do it Synchronously like so:
public ActionResult Download(ReportPage currentPage, string id)
{
var token = RystadIdentity.Current.AuthenticatedUser.Token;
try
{
DocumentRequestInput documentRequestInput = new DocumentRequestInput(int.Parse(id), 2);
documentRequestInput.Add(token);
}
catch
{ }
Report report = RystadGlobal.api.GetReport(token, int.Parse(id)).Result;
string ext = Path.GetExtension(report.fileName);
byte[] byteArray = RystadGlobal.api.DownloadReport(token, report.Id).Result;
if (ext.ToLower() == ".pdf")
{
var name = RystadIdentity.Current.AuthenticatedUser.UserInfo.name;
var company = RystadIdentity.Current.AuthenticatedUser.UserInfo.company;
try
{
byteArray = PdfWatermarker.Watermark(byteArray, name, company);
}
catch (Exception ex)
{
//Ignore if failed and give the user the pdf anyway
}
}
return File(byteArray, System.Net.Mime.MediaTypeNames.Application.Octet, report.fileName);
}
The other problem might be in this method:
RystadGlobal.api.GetReport(token, int.Parse(id))
I do not know that this method looks like inside
EDIT
Try creating new instances of your clients in the methods you added in your question:
public async Task<byte[]> DownloadReport(Token token, int reportId)
{
using(var clientForDL = new System.Net.Http.HttpClient())
{
clientForDL.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.token_type, token.access_token);
var result = await clientForDL.GetAsync("api/" + ApiVersion + "/LibraryDocument/" + reportId + "/download");
if (!result.IsSuccessStatusCode)
throw new ApiException("Could not download the report");
var contentBytes = await result.Content.ReadAsByteArrayAsync();
return SevenZipHelper.Decompress(contentBytes);
}
}
public async Task<Report> GetReport(Token token, int reportId)
{
using(var clientForGet = new System.Net.Http.HttpClient())
{
clientForGet.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.token_type, token.access_token);
var result = await clientForGet.GetAsync("api/" + ApiVersion + "/LibraryDocument/" + reportId);
if (!result.IsSuccessStatusCode)
throw new ApiException("Could not get report");
Report report = await result.Content.ReadAsAsync<Report>();
return report;
}
}