I have a Win Form that is called from within a ribbon inside an Outlook Add-In.
This WinForm calls the following code:
private void lnkReload_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Run().Wait();
}
private async Task Run()
{
// Create the service.
var service = new DiscoveryService(new BaseClientService.Initializer
{
ApplicationName = "Discovery Sample",
ApiKey = Properties.Settings.Default.ClientId
});
// Run the request.
Console.WriteLine("Executing a list request...");
var result = await service.Apis.List().ExecuteAsync();
// Display the results.
if (result.Items != null)
{
foreach (DirectoryList.ItemsData api in result.Items)
{
Console.WriteLine(api.Id + " - " + api.Title);
}
}
}
When debugging the code, at the line "car result=await"...
the code stops working. This sure can take SOME time, but reading that List should not take hours.
Any clue?
Try this:
private async void lnkReload_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
await Run();
}
Related
I have managed to enter the username and password in the appropriate fields on the website, but for reasons unknown to me they are displayed but not recognised as entered text. This means that the "Login" button is not activated. Please help me to solve this problem. What am I doing wrong?
public Form1()
{
InitializeComponent();
this.WindowState = FormWindowState.Maximized;
this.FormBorderStyle = FormBorderStyle.None;
}
private async void Form1_Load(object sender, EventArgs e)
{
await webViewKB.EnsureCoreWebView2Async(null);
webViewKB.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
webViewKB.CoreWebView2.DOMContentLoaded += new EventHandler<CoreWebView2DOMContentLoadedEventArgs>(InputUser);
webViewKB.CoreWebView2.DOMContentLoaded += new EventHandler<CoreWebView2DOMContentLoadedEventArgs>(InputPassword);
}
private async void InputUser(object sender, CoreWebView2DOMContentLoadedEventArgs args)
{
await webViewKB.ExecuteScriptAsync($"document.getElementsByClassName('MuiInput-input MuiInputBase-input css-mnn31')[0].value = '{user}'; ");
await webViewKB.CoreWebView2.ExecuteScriptAsync($"document.getElementById('signInUsername').value = '{user}'; ");
}
private async void InputPassword(object sender, CoreWebView2DOMContentLoadedEventArgs args)
{
await webViewKB.ExecuteScriptAsync($"document.getElementsByClassName('MuiInput-input MuiInputBase-input css-mnn31')[1].value = '{pw}'; ");
await webViewKB.CoreWebView2.ExecuteScriptAsync($"document.getElementById('signInPassword').value = '{pw}'; ");
}
private async void CoreWebView2_DOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
{
await webViewKB.ExecuteScriptAsync(#"document.getElementsByClassName('MuiButtonBase-root MuiFab-root MuiFab-circular MuiFab-sizeLarge MuiFab-primary css-8yw3ib')[0].click();");
await webViewKB.ExecuteScriptAsync(#"document.getElementsByClassName('MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root css-ou8xsw css-150zpdr')[0].click();");
}
}
}
It's not uncommon for websites to validate input fields requiring keyboard/osk input in an attempt to deter bots. You can workaround this by simulating key presses.
For this you can use WebView2.DevTools.Dom which is available via NuGet.org. It's free for anyone to use.
More details and examples in the Readme
private async void CoreWebView2_DOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
{
// Add using WebView2.DevTools.Dom; to access the CreateDevToolsContextAsync extension method
await using var devToolsContext = await webViewKB.CoreWebView2.CreateDevToolsContextAsync();
// https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
var userName = await devtoolsContext.QuerySelectorAsync("#signInUsername");
//Type text in an input field
await userName.TypeAsync($"{user}");
var password = await devtoolsContext.QuerySelectorAsync("#signInPassword");
//Type text in an input field
await password.TypeAsync($"{pw}");
// https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
// Select button via id, class, etc
var button = await devtoolsContext.QuerySelectorAsync(".MuiButtonBase-root .MuiFab-root .MuiFab-circular .MuiFab-sizeLarge .MuiFab-primary .css-8yw3ib");
//Click The element
await button.ClickAsync();
}
This will simulate both key presses and mouse move/clicks.
I'd advocate for removing the multiple event handlers, it's unnecessary and complicates your code.
I want to call a web api method on page load event of my project. But I want to wait for the execution of the function 'GetSelectedTaskDetails' to complete. So that I can manage with the values from DataRow row.
Could you please suggest how can i achieve this?
private DataRow row;
protected void Page_Load(object sender, EventArgs e)
{
GetSelectedTaskDetails(Id);
//other codes
}
private async void GetSelectedTaskDetails(int? selected_task_id)
{
try
{
url = baseUrl + "GetSelectedTaskDetails?task_id=" + selected_task_id;
using (var objClient = new HttpClient())
{
using (var response = await objClient.GetAsync(url))
{
if ((int)response.StatusCode == 401)//unauthorised or token expired
{
Response.Redirect("Default.aspx");
}
if (response.IsSuccessStatusCode)
{
var GetResponse = await response.Content.ReadAsStringAsync();
DataTable dt = JsonConvert.DeserializeObject<DataTable>(GetResponse);
if (dt.Rows.Count == 1)
{
row = dt.Rows[0];
}
}
}
}
}
catch (Exception ex)
{
var message = new JavaScriptSerializer().Serialize(ex.Message.ToString());
var script = string.Format("alert({0});", message);
ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "", script, true);
}
}
You should avoid async void - it's intended for event handlers. So GetSelectedTaskDetails should be async Task instead of async void. Once GetSelectedTaskDetails is properly returning a Task, you can await it in your Page_Load:
protected async void Page_Load(object sender, EventArgs e)
{
await GetSelectedTaskDetails(Id);
...
}
Note that for async to work properly on ASP.NET pre-Core, you need to set Page.Async to true and ensure httpRuntime#targetFramework is set to 4.5 or newer in your web.config
I hope I described the problem correctly.
In the following code the drive.IsReady takes some time to complete. Prior to this is the command to print the text "Scanning drives..." in the textbox. The text though appears after the foreach() has completed.
Why is this happening and how can I notify the user prior to the long-running task?
public Form1()
{
InitializeComponent();
button1.Click += new System.EventHandler(this.Button1_Click);
}
private void Button1_Click(object sender, EventArgs e)
{
richTextBox1.Text = "Scanning drives, please wait...";
PopulateComboBox();
}
void PopulateComboBox()
{
System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
foreach (System.IO.DriveInfo drive in drives)
{
if (drive.IsReady)
{
comboBox1.Items.Add(drive.Name + drive.VolumeLabel);
}
else
{
comboBox1.Items.Add(drive.Name);
}
}
}
These are the minimal changes required to make the slow part of your code (drive.IsReady) run asynchronously. It won't run faster, the intention is just to keep the UI responsive.
private async void Button1_Click(object sender, EventArgs e) // + async
{
richTextBox1.Text = "Scanning drives, please wait...";
await PopulateComboBox(); // + await
}
async Task PopulateComboBox() // async Task instead of void
{
System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
foreach (System.IO.DriveInfo drive in drives)
{
if (await Task.Run(() => drive.IsReady)) // + await Task.Run(() => ...)
{
comboBox1.Items.Add(drive.Name + drive.VolumeLabel);
}
else
{
comboBox1.Items.Add(drive.Name);
}
}
}
I have a long running method which I made async. I made my button click handler async as well, but when I try to access my label in my button click after the long method is done, it tells me it can't can't access it from another thread. Here is the code:
private void Migrate()
{
for (int i = 2; i <= excelData.GetUpperBound(0); i++)
{
var poco = new ExpandoObject() as IDictionary<string, object>;
foreach (var column in distributionColumnExcelHeaderMappings)
{
if (column.ColumnIndex > 0)
{
var value = excelData[i,column.ColumnIndex]?.ToString();
poco.Add(column.DistributionColumnName.Replace(" ", ""), value);
}
}
pocos.Add(poco);
}
migrationRepository.BulkInsert(insertToTable, "Id", pocos);
}
private async void btnMigrate_Click(object sender, EventArgs e)
{
Task task = new Task(()=> Migrate());
task.Start();
lblStatus.Text = "Migrating data....";
await task;
lblStatus.Text = "Migration Complete";
}
When the button is clicked, I see the status Migrating data..... When that is complete, it throws an error on lblStatus.Text = "Migration Complete". I thought after await, it goes back to the UI thread?
I cleared out most of the code and it still throws the same error. This is a VSTO excel add-in. Could that be part of the problem?
private void Migrate()
{
}
private async void btnMigrate(object sender, EventArgs e)
{
Task.Run(()=>Migrate());
lblStatus.Text = "Done"; //still get error here
}
Try and update your code to the following:
Instead of creating your task and then starting it manually, update it to just await on Task.Run:
private async void btnMigrate_Click(object sender, EventArgs e)
{
lblStatus.Text = "Migrating data....";
await Task.Run(()=> Migrate());
lblStatus.Text = "Migration Complete";
}
Edit:
You can use a helper method that will check to see if the label needs to be invoked before updating.
private async void btnMigrate_Click(object sender, EventArgs e)
{
SetLabelText(lblStatus, "Migrating data....");
await Task.Run(()=> Migrate());
SetLabelText(lblStatus, "Migration complete.");
}
private void SetLabelText(Label label, string text)
{
if (label.InvokeRequired)
{
label.BeginInvoke((MethodInvoker) delegate() {label.Text = text;});
}
else
{
label.Text = text;
}
}
I want to do web browser bot. It should click link and wait 25 seconds.
private void webBrowserMain_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) // This is only way It worked for me.
{
if (webBrowserMain.Url.AbsoluteUri == #"http://www.clix-cents.com/pages/clickads")
{
Regex regAddId = new Regex("onclick=\\'openad\\(\"([\\d\\w]+)\"\\);", RegexOptions.IgnoreCase); // Find link and click it.
if (regAddId.IsMatch(webBrowserMain.DocumentText))
{
string AddId = regAddId.Match(webBrowserMain.DocumentText).Groups[1].ToString();
webBrowserMain.Navigate(#"http://www.clix-cents.com/pages/clickads?h=" + AddId);
}
}
else if (webBrowserMain.Url.AbsoluteUri.Contains("http://www.clix-cents.com/pages/clickads?h=")) // up to there everything is ok. But problem starts here.
{
Thread.Sleep(25000); // It pouses whole thread and browser, so timer in browser is not counting down.
Regex regCaptchaCode = new Regex("src=\\'/pages/captcha\\?t=c&s=([\\d\\w\\W]+)\\'", RegexOptions.IgnoreCase);
if (regCaptchaCode.IsMatch(webBrowserMain.DocumentText))
{
pictureBox1.ImageLocation = #"http://www.clix-cents.com/pages/captcha?t=c&s=" + regCaptchaCode.Match(webBrowserMain.DocumentText).ToString();
}
}
}
How to write bot for something like that? I have no idea.
Don't reinvent the wheel - there's already solutions out there like WatiN which is mainly used for testing but is also suitable for automation.
Code example from the WatiN page:
[Test]
public void SearchForWatiNOnGoogle()
{
using (var browser = new IE("http://www.google.com"))
{
browser.TextField(Find.ByName("q")).TypeText("WatiN");
browser.Button(Find.ByName("btnG")).Click();
Assert.IsTrue(browser.ContainsText("WatiN"));
}
}
You could probably use a timer. Eg:
private Timer t = new Timer();
private string nextUrl = "";
private void buttonStart_Click(object sender, EventArgs e)
{
t.Interval = 2500;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(nextUrl))
webBrowser1.Navigate(nextUrl);
else
{
Regex regCaptchaCode = new Regex("src=\\'/pages/captcha\\?t=c&s=([\\d\\w\\W]+)\\'", RegexOptions.IgnoreCase);
if (regCaptchaCode.IsMatch(webBrowserMain.DocumentText))
{
pictureBox1.ImageLocation = #"http://www.clix-cents.com/pages/captcha?t=c&s=" + regCaptchaCode.Match(webBrowserMain.DocumentText).ToString();
}
}
}
private void webBrowserMain_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) // This is only way It worked for me.
{
if (webBrowserMain.Url.AbsoluteUri == #"http://www.clix-cents.com/pages/clickads")
{
Regex regAddId = new Regex("onclick=\\'openad\\(\"([\\d\\w]+)\"\\);", RegexOptions.IgnoreCase); // Find link and click it.
if (regAddId.IsMatch(webBrowserMain.DocumentText))
{
string AddId = regAddId.Match(webBrowserMain.DocumentText).Groups[1].ToString();
nextUrl = #"http://www.clix-cents.com/pages/clickads?h=" + AddId;
t.Start();
}
}
else if (webBrowserMain.Url.AbsoluteUri.Contains("http://www.clix-cents.com/pages/clickads?h=")) // up to there everything is ok. But problem starts here.
{
nextUrl = "";
t.Start();
}
}
The actual implementation will depend on the actual data on the site and how you want to use it. If all the links are on one page and you want to open each one, you could parse for all the links and store into a list. Then start the timer. At each Tick, you could open 1 item.