问题
I'm having trouble understanding the flow of this function I'm building.
public void PortalLogin(AutoResetEvent signal)
{
// Navigate to portal
string portalUrl = "website_name";
string portalEmail = "email@email.com";
string portalPassword = "password";
Action action2 = () =>
{
webBrowser2.Tag = signal;
webBrowser2.Navigate(portalUrl);
webBrowser2.DocumentCompleted -= WebBrowserDocumentCompleted;
webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
};
webBrowser2.Invoke(action2);
signal.WaitOne();
// Login to O365 portal
webBrowser2.Invoke(new Action(() =>
{
HtmlElement head = webBrowser2.Document.GetElementsByTagName("head")[0];
HtmlElement testScript = webBrowser2.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)testScript.DomElement;
element.text = "function PortalLogin() { document.getElementById('userid').value = '" + portalEmail + "'; document.getElementById('password').value = '" + portalPassword + "'; document.getElementById('login').submit(); }";
head.AppendChild(testScript);
webBrowser2.Document.InvokeScript("PortalLogin");
}));
}
... more functions after this
When I step through it, it doesn't seem to be invoking the document.getElementById('login').submit(); part of the script "in time". How can I make sure nothing happens until the InvokeScript has fully completed?
Also- if you see any superfluous code or stuff that can be cleaned up, that's awesome too.
EDIT: Here is DocumentCompleted function.
private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
{
((AutoResetEvent)((WebBrowser)sender).Tag).Set();
}
回答1:
A few points:
You can add DocumentCompleted event handler once outside PortalLogin and reuse the same handler. You are using AutoResetEvent which automatically resets into non-signaled state after signal.WaitOne(), so you should be fine with just one permanent handler for DocumentCompleted.
Are you sure document.getElementById('login') returns a valid element with submit method available? Verify that before calling InvokeScript. You could do the login in two steps, e.g.:
element.text = "function PortalLogin() { document.getElementById('userid').value = '" + portalEmail + "'; document.getElementById('password').value = '" + portalPassword + "'; }" +
"function ExecuteLogin() { document.getElementById('login').submit(); }";
head.AppendChild(testScript);
webBrowser2.Document.InvokeScript("PortalLogin");
// verify document.getElementById('login') here
webBrowser2.Document.InvokeScript("ExecuteLogin");
Note: if successful, the submit would eventually trigger another DocumentCompleted event.
I would refactor this code using single thread and await/async pattern. DocumentCompleted can be wrapped as a task with TaskCompletionSource (here's how).
Below is how it might look like using async/await. In places where there's MessageBox.Show you could do your DOM manipulations. Note, it's all done on the main UI thread (asynchronously, of course). Looks quite a bit easier to me.
void Form1_Load(object sender, EventArgs e)
{
var task = DoNavigationAsync();
task.ContinueWith((t) =>
{
MessageBox.Show("Navigation done!");
}, TaskScheduler.FromCurrentSynchronizationContext());
}
struct Void {}; // use an empty struct as parameter to generic TaskCompletionSource
async Task DoNavigationAsync()
{
Void v;
TaskCompletionSource<Void> tcs = null;
WebBrowserDocumentCompletedEventHandler documentComplete = null;
documentComplete = new WebBrowserDocumentCompletedEventHandler((s, e) =>
{
// more of DocumentCompleted can possibly be fired due to dynamic navigation inside the web page, we don't want them!
this.WB.DocumentCompleted -= documentComplete;
tcs.SetResult(v); // continue from where awaited
});
// navigate to www.bing.com
tcs = new TaskCompletionSource<Void>();
this.WB.DocumentCompleted += documentComplete;
this.WB.Navigate("http://www.bing.com");
await tcs.Task;
// do whatever you want with this instance of WB.Document
MessageBox.Show(this.WB.Document.Url.ToString());
// navigate to www.google.com
tcs = new TaskCompletionSource<Void>();
this.WB.DocumentCompleted += documentComplete;
this.WB.Navigate("http://www.google.com");
await tcs.Task;
// do whatever you want with this instance of WB.Document
MessageBox.Show(this.WB.Document.Url.ToString());
// navigate to www.yahoo.com
tcs = new TaskCompletionSource<Void>();
this.WB.DocumentCompleted += documentComplete;
this.WB.Navigate("http://www.yahoo.com");
await tcs.Task;
// do whatever you want with this instance of WB.Document
MessageBox.Show(this.WB.Document.Url.ToString());
return;
}
来源:https://stackoverflow.com/questions/18280487/flow-of-webbrowser-navigate-and-invokescript