Async and await tasks are getting missed - c#

I'm getting a "Because this call is not awaited..." on
SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
Here's the button click:
private void btnCopyAllInvoices_Click(object sender, EventArgs e)
{
//sets up a list to store the incoming invoice numbers from the DB
List<string> InvoiceNums = new List<string>();
mySqlInterface.Connect();
InvoiceNums = mySqlInterface.GetNewInvoices();
//prep the visuals
lblStatus.Text = "";
InvoicePanel.Visible = true;
progressBarInvoice.Value = 0;
progressBarInvoice.Maximum = InvoiceNums.Count;
//for each invoice collected let's copy it
InvoiceNums.ForEach(delegate(string inv)
{
if (OrderDAL.CheckOrderExist(inv))
{
// the order already exist
Order myorder = new Order();
myorder = OrderDAL.GetOrder(inv);
CopyImages(myorder, true);
OrderDAL.UpdateFulfillment(string.Format("Images Copied"), inv);
}
});
//let the user know how we did
MessageBoxButtons buttons = MessageBoxButtons.OK;
string strError = string.Format("{0} Invoices copied.", InvoiceNums.Count);
MessageBox.Show(this, strError, "Copy New Invoices", buttons, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
InvoicePanel.Visible = false;
}
Here, CopyImages is called as part of the foreach loop above.
public void CopyImages(Order order, bool CopyAllInv)
{
string baseTarget = WorkSpace.Text;
string CLhotfolderTarget = string.Empty;
//check to see if the order has been photo released. If it has add "pr" to the end of the invoice number
string prInvoice = "";
if (order.Header.SignatureLine != "null" && order.Header.SignatureChecks != "null")
{
prInvoice = "pr";
}
string PackageName = null;
string CustomerName = null;
string Phone = null;
string email = null;
string PlayerInfo = null;
string PlayerName = null;
string PlayerNumber = null;
string MainEventName = null;
string MainEventCode = null;
string CLemail = null;
//go to the DB and get the info
mySqlInterface.Connect();
bool videoPackage = mySqlInterface.VideoInfo(order.Header.InvoiceNumber, out PackageName, out CustomerName, out Phone, out email, out PlayerName, out PlayerNumber, out MainEventName, out MainEventCode);
mySqlInterface.Close();
if (videoPackage)
{
if (PackageName.Contains("Video") || PackageName.Contains("Ultimate Ripken"))
{
CLemail = MainEventCode + "_" + email.Replace("#", "_").Replace(".", "_").Replace("+", "_");
PlayerInfo = PlayerName + " " + PlayerNumber;
int template_ID = 0;
if (txtCLtemplateID.Text != "")
{
template_ID = Convert.ToInt32(txtCLtemplateID.Text);
}
//we will always need a hotfolder. So let's set and create it now
CLhotfolderTarget = txtCLhotfolder.Text + "\\toUpload\\" + CLemail;
if (!System.IO.Directory.Exists(CLhotfolderTarget))
{
// create the directory
System.IO.Directory.CreateDirectory(CLhotfolderTarget);
}
int maxImages = 7;
int package_type = 2;
string[] favoritesArray = new string[maxImages];
//populate the array of images for the video
int count = 0;
foreach (Order.InvoiceImages image in order.ImageList)
{
favoritesArray[count] = image.ImageName;
count++;
}
//let's call the API and send info to CL
SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
}
}
}
public async Task SendPostAsync(string name, string email, string phone, int photo_count, string event_name, string event_id, string dir_name, int package_type, string video_text, int template_id, string[] favoritesArray)
{
string postURL = null;
string token = null;
int delivery_method = 2;
//production
postURL = "https://search.apicall.com/photographer/customer";
token = "token xxxxxxxxxxxxxxxxxxxxx";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(postURL);
client.DefaultRequestHeaders.Add("Authorization", token);
string POSTcall = JsonConvert.SerializeObject(new { name, email, phone, photo_count, event_id, event_name, dir_name, package_type, video_text, delivery_method, template_id, favorites = favoritesArray });
//Send string to log file for debug
WriteLog(POSTcall);
StringContent stringContent = new StringContent(POSTcall, UnicodeEncoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(new Uri(postURL), stringContent);
string POSTresponse = await response.Content.ReadAsStringAsync();
WriteLog(POSTresponse);
//simplified output for debug
if (POSTresponse.Contains("error") && POSTresponse.Contains("false"))
{
lblStatus.Text = "Error Sending to CL";
}
else
{
lblStatus.Text = "Successfully added to CL";
}
}
I have an await on the HttpResponseMessage response = await client.PostAsync
If I run this one at a time, it works. But when I run this through a loop and there are a bunch back to back, I think the PostAsyncs are getting stepped on. I'm missing entires in the WriteLog.
It seems I need to do the async/awaits further upstream, right? This way I can run the whole method.

Referencing Async/Await - Best Practices in Asynchronous Programming, event handlers allow async void so refactor the code to be async all the way through.
refactor CopyImages to await the posting of the data
public async Task CopyImages(Order order, bool CopyAllInv) {
//...omitted for brevity
if (videoPackage) {
if (PackageName.Contains("Video") || PackageName.Contains("Ultimate Ripken")) {
//...omitted for brevity
await SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
}
}
}
And update the event handler
private async void btnCopyAllInvoices_Click(object sender, EventArgs e) {
//sets up a list to store the incoming invoice numbers from the DB
List<string> InvoiceNums = new List<string>();
mySqlInterface.Connect();
InvoiceNums = mySqlInterface.GetNewInvoices();
//prep the visuals
lblStatus.Text = "";
InvoicePanel.Visible = true;
progressBarInvoice.Value = 0;
progressBarInvoice.Maximum = InvoiceNums.Count;
//for each invoice collected let's copy it
foreach(string inv in InvoiceNums) {
if (OrderDAL.CheckOrderExist(inv)) {
// the order already exist
Order myorder = OrderDAL.GetOrder(inv);
await CopyImages(myorder, true);
OrderDAL.UpdateFulfillment(string.Format("Images Copied"), inv);
}
}
//let the user know how we did
MessageBoxButtons buttons = MessageBoxButtons.OK;
string strError = string.Format("{0} Invoices copied.", InvoiceNums.Count);
MessageBox.Show(this, strError, "Copy New Invoices", buttons, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
InvoicePanel.Visible = false;
}
I would also advise against creating a HttpClient for each post request. Extract that out and use a single client.
static Lazy<HttpClient> httpClient = new Lazy<HttpClient>(() => {
var postURL = "https://search.apicall.com/photographer/customer";
var token = "token xxxxxxxxxxxxxxxxxxxxx";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(postURL);
client.DefaultRequestHeaders.Add("Authorization", token);
return client
});
public async Task SendPostAsync(string name, string email, string phone, int photo_count, string event_name, string event_id, string dir_name, int package_type, string video_text, int template_id, string[] favoritesArray)
{
var postURL = "https://search.apicall.com/photographer/customer";
int delivery_method = 2;
string POSTcall = JsonConvert.SerializeObject(new { name, email, phone, photo_count, event_id, event_name, dir_name, package_type, video_text, delivery_method, template_id, favorites = favoritesArray });
//Send string to log file for debug
WriteLog(POSTcall);
StringContent stringContent = new StringContent(POSTcall, UnicodeEncoding.UTF8, "application/json");
HttpResponseMessage response = await httpClient.Value.PostAsync(new Uri(postURL), stringContent);
string POSTresponse = await response.Content.ReadAsStringAsync();
WriteLog(POSTresponse);
//simplified output for debug
if (POSTresponse.Contains("error") && POSTresponse.Contains("false")) {
lblStatus.Text = "Error Sending to CL";
} else {
lblStatus.Text = "Successfully added to CL";
}
}

Related

Async /Await call gets stuck in web API

We have a web API application which runs on .net4.6.1. We have tried several times to figure out the root cause where it is getting deadlock, but failed. Below is the code snippet. We are hitting this API endpoint every 1 minute. It will pick 300 transaction at a time for processing from the DB. We have observed that it get stuck when there are no files to process from the DB. Not sure though. It would be helpful if someone can help us.TIA
public class TaxEngineIntegratorController : ApiController
{
public async Task Get(int id)
{
try
{
await MainFileMethod();
}
catch (Exception Ex)
{
SerilogMethods.LogError(log, Ex, "Get");
}
}
public async Task MainFileMethod()
{
List<FileTransaction> lstFTtoLock = new List<FileTransaction>();
try
{
List<int> lstStatusIds = new List<int>();
lstStatusIds.Add(objStatusManager.GetStatusIdbyName(Status.ConversionToXmlSucceded));
lstStatusIds.Add(objStatusManager.GetStatusIdbyName(Status.Reprocess));
//Getting the serviceURL of TRTaxEngine
string seriviceURL = objConfigManager.GetConfigurationdbyKey(ConfigurationList.TRTaxEngineURL);
//Getting the output path for the file to be placed after processing
string outputfilePath = objConfigManager.GetConfigurationdbyKey(ConfigurationList.TRTaxOutputXMLFolder);
FileMasterManager objFileMasterManager = new FileMasterManager();
TRTaxXMLOperations objxmlresp = new TRTaxXMLOperations();
//Getting all the files list for proccessing from the DB
List<FileTransaction> lstFiletoProcess = await objTransManager.GetFileListforProcessingAsync(lstStatusIds, true);
lstFTtoLock = lstFiletoProcess;
if (lstFiletoProcess.Count == 0)
return;
if (lstFiletoProcess.Count > 0)
{
var tasks = new List<Task<string>>();
using (HttpClient httpClnt = new HttpClient())
{
httpClnt.Timeout = TimeSpan.FromMilliseconds(-1);
foreach (FileTransaction item in lstFiletoProcess)
{
TRXMLResponseModel objRespModel = new TRXMLResponseModel();
objRespModel.strxmlResponse = string.Empty;
string fullFileName = item.FilePath + item.ConvertedName;
objRespModel.outputFilename = outputfilePath + item.ConvertedName;
FileMaster fileMaster = objFileMasterManager.GetById(item.FileId);
//Proccessing the file and getting the output filedata
Task<string> t = objxmlresp.GetXMLResponse(seriviceURL, fullFileName, fileMaster.CountryId.GetValueOrDefault(), httpClnt, objFileOperation, objRespModel.outputFilename, item);
tasks.Add(t);
objRespModel.strxmlResponse = await t;
}
var result = await Task.WhenAll(tasks);
}
SerilogMethods.LogCustomException(log, "Http Client Destroyed in Tax Engine", "GetXMLResponse");
}
}
catch (Exception Ex)
{
if (lstFTtoLock != null && lstFTtoLock.Count > 0)
{
objTransManager.UpdateFileTransactionIsPickedtoFalse(lstFTtoLock);
}
throw Ex;
}
}
}
//Getting all the files list for proccessing from the DB
public async Task<List<FileTransaction>> GetFileListforProcessingAsync(List<int> lstStatusList, bool IsActive)
{
try
{
List<FileTransaction> lstFTList = new List<FileTransaction>();
using (SUTBACDEVContext db = new SUTBACDEVContext())
{
//DataTable dtFileTransactions = GetFileTransactionListAsync(lstStatusList, IsActive);
string connectionString = db.Database.GetDbConnection().ConnectionString;
var conn = new SqlConnection(connectionString);
string query = #"[SUTGITA].[GetFileListforProcessing]";
using (var sqlAdpt = new SqlDataAdapter(query, conn))
{
sqlAdpt.SelectCommand.CommandType = CommandType.StoredProcedure;
sqlAdpt.SelectCommand.Parameters.AddWithValue("#StatusId", string.Join(",", lstStatusList.Select(n => n.ToString()).ToArray()));
sqlAdpt.SelectCommand.Parameters.AddWithValue("#IsActive", IsActive);
sqlAdpt.SelectCommand.CommandTimeout = 60000;
DataTable dtFileTransactions = new DataTable();
sqlAdpt.Fill(dtFileTransactions);
if (dtFileTransactions != null && dtFileTransactions.Rows.Count > 0)
{
IEnumerable<long> ids = dtFileTransactions.AsEnumerable().ToList().Select(p => p["id"]).ToList().OfType<long>();
lstFTList = await db.FileTransaction.Include(x => x.File.Country).Where(x => ids.Contains(x.Id)).OrderBy(x => x.Id).ToListAsync();
}
}
}
return lstFTList;
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<string> GetXMLResponse(string baseUrl, string fullFileName, int countryId, HttpClient client, FileOperations objFileOperation, string outputfilePath, FileTransaction item)
{
try
{
var fileData = new StringBuilder(objFileOperation.ReadFile(fullFileName));
using (HttpContent content = new StringContent(TransformToSOAPXml(fileData, countryId), Encoding.UTF8, "text/xml"))
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, baseUrl))
{
request.Headers.Add("SOAPAction", "");
request.Content = content;
using (HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
{
using (Stream streamToWriteTo = File.Open(outputfilePath, FileMode.Create))
{
await streamToReadFrom.CopyToAsync(streamToWriteTo);
}
}
var transactionEntry = new FileTransaction
{
FileId = item.FileId,
FilePath = outputfilePath,
ConvertedName = item.ConvertedName,
ActionedBy = Process.Process3,
TimeStamp = DateTime.UtcNow,
StatusId = objStatusManager.GetStatusIdbyName(Status.OutputXmlReceived),
IsActive = true,
CreatedBy = Others.Scheduler,
CreatedOn = DateTime.UtcNow,
ModifiedBy = Others.Scheduler,
ModifiedOn = DateTime.UtcNow
};
//Inserting the new record and Updating isActive filed of previous record in Tranasaction table(Calling updateDataonTRSuccess method of TRTaxXMLOperations class)
await updateDataonTRSuccessAsync(item, transactionEntry);
return "Success";
}
else
{
SerilogMethods.LogCustomException(log, "Error occured in Tax Engine", "GetXMLResponse");
//Log the SOAP response when the SOAP fails with an error message
if (response.Content != null)
{
throw new Exception(await response.Content.ReadAsStringAsync());
}
return null;
}
}
}
}
}
catch (Exception ex)
{
SerilogMethods.LogError(log, ex, "GetXMLResponse");
return null;
}
}
The following changes I have done to make it work to this specific method.
Removal of this line : objRespModel.strxmlResponse = await t;
and added configureawait(false) to this line :List lstFiletoProcess = await objTransManager.GetFileListforProcessingAsync(lstStatusIds, true).ConfigureAwait(false); Below is the working code
public async Task MainFileMethod()
{
List<FileTransaction> lstFTtoLock = new List<FileTransaction>();
try
{
List<int> lstStatusIds = new List<int>();
lstStatusIds.Add(objStatusManager.GetStatusIdbyName(Status.ConversionToXmlSucceded));
lstStatusIds.Add(objStatusManager.GetStatusIdbyName(Status.Reprocess));
//Getting the serviceURL of TRTaxEngine
string seriviceURL = objConfigManager.GetConfigurationdbyKey(ConfigurationList.TRTaxEngineURL);
//Getting the output path for the file to be placed after processing
string outputfilePath = objConfigManager.GetConfigurationdbyKey(ConfigurationList.TRTaxOutputXMLFolder);
FileMasterManager objFileMasterManager = new FileMasterManager();
TRTaxXMLOperations objxmlresp = new TRTaxXMLOperations();
//Getting all the files list for proccessing from the DB
List<FileTransaction> lstFiletoProcess = await objTransManager.GetFileListforProcessingAsync(lstStatusIds, true).ConfigureAwait(false);
lstFTtoLock = lstFiletoProcess;
if (lstFiletoProcess.Count == 0)
return;
if (lstFiletoProcess.Count > 0)
{
var tasks = new List<Task<string>>();
using (HttpClient httpClnt = new HttpClient())
{
httpClnt.Timeout = TimeSpan.FromMilliseconds(-1);
//Getting the files for processing
foreach (FileTransaction item in lstFiletoProcess)
{
TRXMLResponseModel objRespModel = new TRXMLResponseModel();
objRespModel.strxmlResponse = string.Empty;
string fullFileName = item.FilePath + item.ConvertedName;
objRespModel.outputFilename = outputfilePath + item.ConvertedName;
FileMaster fileMaster = objFileMasterManager.GetById(item.FileId);
//Proccessing the file and getting the output filedata
Task<string> t = objxmlresp.GetXMLResponse(seriviceURL, fullFileName, fileMaster.CountryId.GetValueOrDefault(), httpClnt, objFileOperation, objRespModel.outputFilename, item, objTransManager);
tasks.Add(t);
//objRespModel.strxmlResponse = await t;
}
var result = await Task.WhenAll(tasks);
}
}
}
catch (Exception Ex)
{
if (lstFTtoLock != null && lstFTtoLock.Count > 0)
{
objTransManager.UpdateFileTransactionIsPickedtoFalse(lstFTtoLock);
}
throw Ex;
}
}
My Recommendation:
The method "Get(int id)" is somewhat confusing. first, it takes "id" and does nothing with it. Also it return nothing so it is not a "Get" method. It is basically asking for all transactions with status "Status.ConversionToXmlSucceded" & "Status.Reprocess" and are active to be gotten and processed via the "objxmlresp.GetXMLResponse" method... You Dont Have To Await the "MainFileMethod();" in "Get(int id)" just return the task or return Ok(); and allow all the process to go on in the background. You can experiment with reducing the "sqlAdpt.SelectCommand.CommandTimeout = 60000;".

HttpResponseMessage PostAsync doesn't response

I dont get any response using client.PostAsync.
I created button in xamarin forms. It should send to server some sentences(json) and return info about them(again in json).
Button code:
private async void button_Analyze_Clicked(object sender, EventArgs e)
{
Request req = new Request()
{
UserId = this.UserId,
Language = Convert.ToString(picker_Language.SelectedItem) + ".",
Text = Convert.ToString(editor1.Text)
};
string jsonStr = JsonConvert.SerializeObject(req);
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("s", jsonStr);
FormUrlEncodedContent form = new FormUrlEncodedContent(dict);
HttpResponseMessage response = await client.PostAsync(markTextUrl, form).ConfigureAwait(false);
string result = await response.Content.ReadAsStringAsync();
Answer answ = JsonConvert.DeserializeObject<Answer>(result);
answersList.Add(answ);
await DisplayAlert("", " ", "Ok");
}
Controller code:
public string MarkText(string s) //работа с запросом из приложения
{
Request req = JsonConvert.DeserializeObject<Request>(s);
if (req != null)
{
Models.Request request = new Models.Request()
{
Text = req.Text,
Lang = req.Language,
UserId = int.Parse(req.UserId)
};
AnalyzeRequest(request);
Answer answ = new Answer()
{
Language = req.Language,
Text = req.Text,
Sentences = db.Histories.Last().Text,
Labels = db.Histories.Last().Label
};
return JsonConvert.SerializeObject(answ);
}
return null;
}
Problem is this code
HttpResponseMessage response = await client.PostAsync(markTextUrl, form).ConfigureAwait(false);
never return response and it doesnt reach controller function. If I wait for this code to complete Ill get System.OperationCanceledExeption:"The operation was canceled"

How to stop and re-excute methods in C#/XamarinForms?

I have a Display alert that ask if the user wants to retry syncing the data. My problem is when the user chose "Yes" my method overlaps it causes my application to crash. I there a way to for example when the user chooses yes the method execution stops and re-execute the method?
Here is my full code:
public async void FirstTimeSyncTown(string host, string database, string contact, string ipaddress)
{
try
{
syncStatus.Text = "Checking internet connection";
string apifile = "first-time-sync-town-api.php";
if (CrossConnectivity.Current.IsConnected)
{
syncStatus.Text = "Initializing first-time town sync";
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
var getData = conn.QueryAsync<TownTable>("SELECT * FROM tblTown WHERE Deleted != '1'");
var resultCount = getData.Result.Count;
var current_datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
int count = 1;
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
if (resultCount == 0)
{
syncStatus.Text = "Getting data from the server";
var link = "http://" + ipaddress + ":" + Constants.port + "/" + Constants.apifolder + "/api/" + apifile;
string contentType = "application/json";
JObject json = new JObject
{
{ "Host", host },
{ "Database", database }
};
HttpClient client = new HttpClient();
var response = await client.PostAsync(link, new StringContent(json.ToString(), Encoding.UTF8, contentType));
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(content))
{
try
{
var dataresult = JsonConvert.DeserializeObject<List<TownData>>(content, settings);
var datacount = dataresult.Count;
for (int i = 0; i < datacount; i++)
{
syncStatus.Text = "Syncing town " + count + " out of " + datacount;
var item = dataresult[i];
var townID = item.TownID;
var provinceID = item.ProvinceID;
var town = item.Town;
var lastsync = DateTime.Parse(current_datetime);
var lastupdated = item.LastUpdated;
var deleted = item.Deleted;
var insertdata = new TownTable
{
TownID = townID,
ProvinceID = provinceID,
Town = town,
LastSync = lastsync,
LastUpdated = lastupdated,
Deleted = deleted
};
await conn.InsertOrReplaceAsync(insertdata);
count++;
}
synccount += "Total synced town: " + count + "\n";
var logType = "App Log";
var log = "Initialized first-time sync (<b>Town</b>) <br/>" + "App Version: <b>" + Constants.appversion + "</b><br/> Device ID: <b>" + Constants.deviceID + "</b>";
int logdeleted = 0;
Save_Logs(contact, logType, log, database, logdeleted);
}
catch
{
var retry = await DisplayAlert("Application Error", "Syncing failed. Failed to send the data.\n\n Error:\n\n" + content + "\n\n Do you want to retry?", "Yes", "No");
if (retry.Equals(true))
{
FirstTimeSyncTown(host, database, contact, ipaddress);
}
else
{
First_Time_Sync_Failed();
}
}
}
Preferences.Set("townchangelastcheck", current_datetime, "private_prefs");
SyncUserLogsClientUpdate(host, database, contact, ipaddress);
}
else
{
var retry = await DisplayAlert("Application Error", "Syncing failed. Please connect to the internet to sync your data. Do you want to retry?", "Yes", "No");
if (retry.Equals(true))
{
FirstTimeSyncTown(host, database, contact, ipaddress);
}
else
{
First_Time_Sync_Failed();
}
}
}
else
{
SyncTownServerUpdate(host, database, contact, ipaddress);
}
}
else
{
var retry = await DisplayAlert("Application Error", "Syncing failed. Please connect to the internet to sync your data. Do you want to retry?", "Yes", "No");
if (retry.Equals(true))
{
FirstTimeSyncTown(host, database, contact, ipaddress);
}
else
{
First_Time_Sync_Failed();
}
}
}
catch (Exception ex)
{
Crashes.TrackError(ex);
var retry = await DisplayAlert("Application Error", "Syncing failed. Failed to send the data.\n\n Error:\n\n" + ex.Message.ToString() + "\n\n Do you want to retry?", "Yes", "No");
if (retry.Equals(true))
{
FirstTimeSyncTown(host, database, contact, ipaddress);
}
else
{
First_Time_Sync_Failed();
};
}
}
In my case, I've used Task.Factory for executing methods in background, and also CancellationToken for cancelling executing.
Firstly, you need to create a Token parameter like so:
public CancellationTokenSource Ts { get; set; } = new CancellationTokenSource();
(I did it as public global param of the class for accessing from outside).
And when I execute the background methods, I use this lines of code:
// Get Token for Task.Factory
var ct = Ts.Token;
try
{
Task.Factory.StartNew(() =>
{
// your code for background task
...
// This is for defining whether user cancelled
// and in that place your code stops
if (ct.IsCancellationRequested)
{
// do the staff and return
return;
}
}, ct);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
}
When user click on "Cancel", I handle this event and call this:
// Call it to stop thread
yourCustomClass.Ts.Cancel();
After that you can re-execute your method. Hope it helps!

Access additional twitter user info

I'm using Azure Mobile Services to authorize users and am now trying to get additional user info from the providers. I have it working for all of them except Twitter. To authenticate for all the other I'm using something similar to this:
var identities = await user.GetIdentitiesAsync();
var result = new JObject();
var fb = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (fb != null)
{
var accessToken = fb.AccessToken;
result.Add("facebook", await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken));
}
Would I be able to do something like this:
var tw = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (tw != null)
{
var accessToken = tw.AccessToken;
var accessTokenSecret = tw.AccessTokenSecret;
result.Add("twitter", await
GetProviderInfo("https://api.twitter.com/1.1/account/verify_credentials.json?token=" + accessToken + "&token_secret=" + accessTokenSecret + "&consumer_key=***************" + "&consumer_secret=******************************"));
}
or would I have to do something completely different?
Woops, just found a similar question here: Twitter single url request
Yes, it is possible, but it's more work than for other providers.
This is the code for your api controller (maybe needs some refactoring)
[HttpPost]
[Route("current/identity")]
public async Task<HttpResponseMessage> GetIdentityInfo()
{
var currentUser = User as ServiceUser;
if (currentUser != null)
{
var identities = await currentUser.GetIdentitiesAsync();
var googleCredentials = identities.OfType<GoogleCredentials>().FirstOrDefault();
if (googleCredentials != null)
{
var infos = await GetGoolgeDetails(googleCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var facebookCredentials = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (facebookCredentials!= null)
{
var infos = await GetFacebookDetails(facebookCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var microsoftCredentials = identities.OfType<MicrosoftAccountCredentials>().FirstOrDefault();
if (microsoftCredentials != null)
{
var infos = await GetMicrosoftDetails(microsoftCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var twitterCredentials = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (twitterCredentials != null)
{
var infos = await GetTwitterDetails(currentUser, twitterCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
private async Task<JToken> GetTwitterDetails(ServiceUser currentUser, TwitterCredentials twitterCredentials)
{
var twitterId = currentUser.Id.Split(':').Last();
var accessToken = twitterCredentials.AccessToken;
string consumerKey = ConfigurationManager.AppSettings["MS_TwitterConsumerKey"];
string consumerSecret = ConfigurationManager.AppSettings["MS_TwitterConsumerSecret"];
// Add this setting manually on your Azure Mobile Services Management interface.
// You will find the secret on your twitter app configuration
string accessTokenSecret = ConfigurationManager.AppSettings["FG_TwitterAccessTokenSecret"];
var parameters = new Dictionary<string, string>();
parameters.Add("user_id", twitterId);
parameters.Add("oauth_token", accessToken);
parameters.Add("oauth_consumer_key", consumerKey);
OAuth1 oauth = new OAuth1();
string headerString = oauth.GetAuthorizationHeaderString(
"GET", "https://api.twitter.com/1.1/users/show.json",
parameters, consumerSecret, accessTokenSecret);
var infos = await GetProviderInfo("https://api.twitter.com/1.1/users/show.json?user_id=" + twitterId, headerString);
return infos;
}
private async Task<JToken> GetMicrosoftDetails(MicrosoftAccountCredentials microsoftCredentials)
{
var accessToken = microsoftCredentials.AccessToken;
var infos = await GetProviderInfo("https://apis.live.net/v5.0/me/?method=GET&access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetFacebookDetails(FacebookCredentials facebookCredentials)
{
var accessToken = facebookCredentials.AccessToken;
var infos = await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetGoolgeDetails(GoogleCredentials googleCredentials)
{
var accessToken = googleCredentials.AccessToken;
var infos = await GetProviderInfo("https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetProviderInfo(string url, string oauth1HeaderString = null)
{
using (var client = new HttpClient())
{
if (oauth1HeaderString != null)
{
client.DefaultRequestHeaders.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(oauth1HeaderString);
}
var resp = await client.GetAsync(url).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
string rawInfo = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
return JToken.Parse(rawInfo);
}
}
Then you need this class to build a valid OAuth 1.0 authentication header:
(almost all of the following code ist from LinqToTwitter, https://linqtotwitter.codeplex.com)
public class OAuth1
{
const string OAUTH_VERSION = "1.0";
const string SIGNATURE_METHOD = "HMAC-SHA1";
const long UNIX_EPOC_TICKS = 621355968000000000L;
public string GetAuthorizationHeaderString(string method, string url, IDictionary<string, string> parameters, string consumerSecret, string accessTokenSecret)
{
string encodedAndSortedString = BuildEncodedSortedString(parameters);
string signatureBaseString = BuildSignatureBaseString(method, url, encodedAndSortedString);
string signingKey = BuildSigningKey(consumerSecret, accessTokenSecret);
string signature = CalculateSignature(signingKey, signatureBaseString);
string authorizationHeader = BuildAuthorizationHeaderString(encodedAndSortedString, signature);
return authorizationHeader;
}
internal void AddMissingOAuthParameters(IDictionary<string, string> parameters)
{
if (!parameters.ContainsKey("oauth_timestamp"))
parameters.Add("oauth_timestamp", GetTimestamp());
if (!parameters.ContainsKey("oauth_nonce"))
parameters.Add("oauth_nonce", GenerateNonce());
if (!parameters.ContainsKey("oauth_version"))
parameters.Add("oauth_version", OAUTH_VERSION);
if (!parameters.ContainsKey("oauth_signature_method"))
parameters.Add("oauth_signature_method", SIGNATURE_METHOD);
}
internal string BuildEncodedSortedString(IDictionary<string, string> parameters)
{
AddMissingOAuthParameters(parameters);
return
string.Join("&",
(from parm in parameters
orderby parm.Key
select parm.Key + "=" + PercentEncode(parameters[parm.Key]))
.ToArray());
}
internal virtual string BuildSignatureBaseString(string method, string url, string encodedStringParameters)
{
int paramsIndex = url.IndexOf('?');
string urlWithoutParams = paramsIndex >= 0 ? url.Substring(0, paramsIndex) : url;
return string.Join("&", new string[]
{
method.ToUpper(),
PercentEncode(urlWithoutParams),
PercentEncode(encodedStringParameters)
});
}
internal virtual string BuildSigningKey(string consumerSecret, string accessTokenSecret)
{
return string.Format(
CultureInfo.InvariantCulture, "{0}&{1}",
PercentEncode(consumerSecret),
PercentEncode(accessTokenSecret));
}
internal virtual string CalculateSignature(string signingKey, string signatureBaseString)
{
byte[] key = Encoding.UTF8.GetBytes(signingKey);
byte[] msg = Encoding.UTF8.GetBytes(signatureBaseString);
KeyedHashAlgorithm hasher = new HMACSHA1();
hasher.Key = key;
byte[] hash = hasher.ComputeHash(msg);
return Convert.ToBase64String(hash);
}
internal virtual string BuildAuthorizationHeaderString(string encodedAndSortedString, string signature)
{
string[] allParms = (encodedAndSortedString + "&oauth_signature=" + PercentEncode(signature)).Split('&');
string allParmsString =
string.Join(", ",
(from parm in allParms
let keyVal = parm.Split('=')
where parm.StartsWith("oauth") || parm.StartsWith("x_auth")
orderby keyVal[0]
select keyVal[0] + "=\"" + keyVal[1] + "\"")
.ToList());
return "OAuth " + allParmsString;
}
internal virtual string GetTimestamp()
{
long ticksSinceUnixEpoc = DateTime.UtcNow.Ticks - UNIX_EPOC_TICKS;
double secondsSinceUnixEpoc = new TimeSpan(ticksSinceUnixEpoc).TotalSeconds;
return Math.Floor(secondsSinceUnixEpoc).ToString(CultureInfo.InvariantCulture);
}
internal virtual string GenerateNonce()
{
return new Random().Next(111111, 9999999).ToString(CultureInfo.InvariantCulture);
}
internal virtual string PercentEncode(string value)
{
const string ReservedChars = #"`!##$^&*()+=,:;'?/|\[] ";
var result = new StringBuilder();
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
var escapedValue = Uri.EscapeDataString(value);
// Windows Phone doesn't escape all the ReservedChars properly, so we have to do it manually.
foreach (char symbol in escapedValue)
{
if (ReservedChars.IndexOf(symbol) != -1)
{
result.Append('%' + String.Format("{0:X2}", (int)symbol).ToUpper());
}
else
{
result.Append(symbol);
}
}
return result.ToString();
}
}

How can I call two GET Web API REST methods, the first to get the count of records that the second one will return?

I know this is kludgy, but in my existing Web API client code, the way I stop reading records is when my call to webRequest.GetResponse() crashes on reading and finding none, and eating the exception (I'm reading a "block/chunk" at a time, due to the bandwidth limitations of the handheld device which calls the Web API REST method).
As my conscience (so to speak) was bothering me about doing it this way (but it works!), I thought maybe I could first get the count of records and use that knowledge to preclude reading beyond the pale/edge of the world, thus avoiding the exception.
However, as can be seen here: Why is my Web API routing being re-routed / falsely routed?, I'm finding no success in trying to access multiple GET methods - I could only get the desired GET method to be called by eliminating the other one!
My existing client code is:
private void buttonGetInvItemsInBlocks_Click(object sender, EventArgs e)
{
string formatargready_uri = "http://localhost:28642/api/inventoryItems/{0}/{1}";
// Cannot start with String.Empty or a blank string (" ") assigned to lastIDFetched; they both fail for some reason - Controller method is not even called. Seems like a bug in Web API to me...
string lastIDFetched = "0";
const int RECORDS_TO_FETCH = 100;
bool moreRecordsExist = true;
try
{
while (moreRecordsExist)
{
formatargready_uri = string.Format("http://localhost:28642/api/InventoryItems/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
webRequest.Method = "GET";
var webResponse = (HttpWebResponse)webRequest.GetResponse(); // <-- this throws an exception when there is no longer any data left
// It will hit this when it's done; when there are no records left, webResponse's content is "[]" and thus a length of 2
if ((webResponse.StatusCode != HttpStatusCode.OK) || (webResponse.ContentLength < 3)) {
moreRecordsExist = false;
}
else // ((webResponse.StatusCode == HttpStatusCode.OK) && (webResponse.ContentLength > 2))
{
var reader = new StreamReader(webResponse.GetResponseStream());
string s = reader.ReadToEnd();
var arr = JsonConvert.DeserializeObject<JArray>(s);
foreach (JObject obj in arr)
{
string id = (string)obj["Id"];
lastIDFetched = id;
int packSize = (Int16)obj["PackSize"];
string description = (string)obj["Description"];
int dept = (Int16)obj["DeptSubdeptNumber"];
int subdept = (Int16)obj["InvSubdepartment"];
string vendorId = (string)obj["InventoryName"];
string vendorItem = (string)obj["VendorItemId"];
double avgCost = (Double)obj["Cost"];
double unitList = (Double)obj["ListPrice"];
inventoryItems.Add(new WebAPIClientUtils.InventoryItem
{
Id = id,
InventoryName = vendorId,
UPC_PLU = vendorId,
VendorItemId = vendorItem,
PackSize = packSize,
Description = description,
Quantity = 0.0,
Cost = avgCost,
Margin = (unitList - avgCost),
ListPrice = unitList,
DeptSubdeptNumber = dept,
InvSubdepartment = subdept
});
// Wrap around on reaching 100 with the progress bar; it's thus sort of a hybrid determinate/indeterminate value that is shown
if (progressBar1.Value >= 100)
{
progressBar1.Value = 0;
}
progressBar1.Value += 1;
}
} // if ((webResponse.StatusCode == HttpStatusCode.OK) && (webResponse.ContentLength > 0))
} // while
if (inventoryItems.Count > 0)
{
dataGridViewGETResults.DataSource = inventoryItems;
}
MessageBox.Show(string.Format("{0} results found", inventoryItems.Count));
}
catch (Exception ex)
{
// After all records are read, this exception is thrown, so commented out the message; thus, this is really *expected*, and is not an "exception"
//MessageBox.Show(ex.Message);
}
}
}
...and the Web API REST Controller code is:
public class InventoryItemsController : ApiController
{
static readonly IInventoryItemRepository inventoryItemsRepository = new InventoryItemRepository();
public IEnumerable<InventoryItem> GetBatchOfInventoryItemsByStartingID(string ID, int CountToFetch)
{
return inventoryItemsRepository.Get(ID, CountToFetch);
}
}
...and the (im?)pertinent Repository code is:
public IEnumerable<InventoryItem> Get(string ID, int CountToFetch)
{
return inventoryItems.Where(i => 0 < String.Compare(i.Id, ID)).Take(CountToFetch);
}
One would think I could have a method like this:
public IEnumerable<InventoryItem> Get()
{
return inventoryItems.Count();
}
...which would be recognized due to having no args as being different from the other one (even without the routing extravaganza, all attempts at appeasement by me failing ignominiously anyway yesterday).
I found I can get a record count with this code:
CLIENT
private int getInvItemsCount()
{
int recCount = 0;
const string uri = "http://localhost:28642/api/InventoryItems";
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.Method = "GET";
using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
var reader = new StreamReader(webResponse.GetResponseStream());
string s = reader.ReadToEnd();
Int32.TryParse(s, out recCount);
}
}
return recCount;
}
private void GetInvItemsInBlocks()
{
Cursor.Current = Cursors.WaitCursor;
try
{
string lastIDFetched = "0";
const int RECORDS_TO_FETCH = 100;
int recordsToFetch = getInvItemsCount();
bool moreRecordsExist = recordsToFetch > 0;
int totalRecordsFetched = 0;
while (moreRecordsExist)
{
string formatargready_uri = string.Format("http://localhost:28642/api/InventoryItems/{0}/{1}", lastIDFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest)WebRequest.Create(formatargready_uri);
webRequest.Method = "GET";
using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
var reader = new StreamReader(webResponse.GetResponseStream());
string s = reader.ReadToEnd();
var arr = JsonConvert.DeserializeObject<JArray>(s);
foreach (JObject obj in arr)
{
var id = (string)obj["Id"];
lastIDFetched = id;
int packSize = (Int16)obj["PackSize"];
var description = (string)obj["Description"];
int dept = (Int16)obj["DeptSubdeptNumber"];
int subdept = (Int16)obj["InvSubdepartment"];
var vendorId = (string)obj["InventoryName"];
var vendorItem = (string)obj["VendorItemId"];
var avgCost = (Double)obj["Cost"];
var unitList = (Double)obj["ListPrice"];
inventoryItems.Add(new WebAPIClientUtils.InventoryItem
{
Id = id,
InventoryName = vendorId,
UPC_PLU = vendorId,
VendorItemId = vendorItem,
PackSize = packSize,
Description = description,
Quantity = 0.0,
Cost = avgCost,
Margin = (unitList - avgCost),
ListPrice = unitList,
DeptSubdeptNumber = dept,
InvSubdepartment = subdept
});
if (progressBar1.Value >= 100)
{
progressBar1.Value = 0;
}
progressBar1.Value += 1;
} // foreach
} // if (webResponse.StatusCode == HttpStatusCode.OK)
} // using HttpWebResponse
int recordsFetched = WebAPIClientUtils.WriteRecordsToMockDatabase(inventoryItems, hs);
label1.Text += string.Format("{0} records added to mock database at {1}; ", recordsFetched, DateTime.Now.ToLongTimeString());
totalRecordsFetched += recordsFetched;
moreRecordsExist = (recordsToFetch > (totalRecordsFetched+1));
} // while
if (inventoryItems.Count > 0)
{
dataGridViewGETResults.DataSource = inventoryItems;
}
}
finally
{
Cursor.Current = Cursors.Default;
}
}
SERVER
Repository Interface:
interface IInventoryItemRepository
{
. . .
int Get();
. . .
}
Repository Interface Implementation:
public class InventoryItemRepository : IInventoryItemRepository
{
private readonly List<InventoryItem> inventoryItems = new List<InventoryItem>();
. . .
public int Get()
{
return inventoryItems.Count;
}
. . .
}
Controller:
static readonly IInventoryItemRepository inventoryItemsRepository = new InventoryItemRepository();
public int GetCountOfInventoryItems()
{
return inventoryItemsRepository.Get();
}

Categories

Resources