Что я пытаюсь сделать
Я пытаюсь создать решение любого типа, которое будет работать каждую ночь на сервере Windows, аутентифицироваться на веб-сайте, проверять веб-страницу на сайте на наличие новых ссылок, указывающих на новую версию zip-файла, использовать новые ссылки (если они есть ), чтобы загрузить zip-файл, распаковать загруженный файл в существующую папку на сервере, использовать разархивированное содержимое (сценарии sql и т. д.) для создания экземпляра базы данных и регистрировать все, что происходит с текстовым файлом.
Приложение Forms: часть, которая работает с Sorta
Я создал приложение Windows Forms, которое использует пару элементов управления WebBrowser, пару потоков и несколько таймеров для всего этого, кроме работы в ночное время. Он отлично работает как форма, когда я вхожу в систему и запускаю ее, но мне нужно получить ее (или что-то в этом роде), чтобы она работала сама по себе, как служба или запланированная задача.
Моя попытка обслуживания
Итак, я создал службу Windows, которая работает каждый час и, если System.DateTime.Now.Hour> = 22, пытается запустить приложение Windows Forms для этого. Когда Сервис пытается запустить форму, возникает эта ошибка:
Элемент управления ActiveX «8856f961-340a-11d0-a96b-00c04fd705a2» не может быть создан, поскольку текущий поток не находится в однопоточном апартаменте.
который я исследовал и попытался решить, поместив атрибут [STAThread] в метод Main класса Program службы или используя некоторый подобный код в нескольких местах, включая конструктор формы:
webBrowseThread = new Thread(new ThreadStart(InitializeComponent));
webBrowseThread.SetApartmentState(ApartmentState.STA);
webBrowseThread.Start();
Я не мог заставить работать ни один из подходов. В последнем подходе элементы управления в форме (которые инициализируются внутри IntializeComponent) не инициализируются, и я получаю исключения нулевой ссылки.
Моя запланированная попытка выполнения задания
Итак, я попытался создать задачу, запланированную на ночь, используя свои собственные учетные данные, для локального запуска формы на моем компьютере разработчика (просто тестирование). Он идет дальше, чем служба, но зависает в диалоговом окне загрузки файла.
Связанное примечание: Чтобы отправить последовательности клавиш для прохождения диалоговых окон "Загрузка файла" и "Сохранить файл как", моя форма фактически запускает несколько файлов vbscript, которые используют WScript.Shell.SendKeys. Хорошо, стыдно признавать, но я пробовал несколько разных вещей, включая SendMessage в Win32 API и ссылку на IWshRuntimeLibrary для использования SendKeys в моем коде C #. Когда я изучал, как работать с диалоговыми окнами, мне показалось, что рекомендуется использовать Win32 API, но я не мог этого понять. Файлы vbscript были единственной вещью, с которой я мог работать, но сейчас я беспокоюсь, что это может быть причиной того, что запланированная задача не будет работать.
Что касается моего выбора управления веб-браузером
Я читал о классе System.WebClient в качестве альтернативы элементу управления WebBrowser, но на первый взгляд не похоже, что у него есть то, что мне нужно для этого. Например, мне понадобились (или я думаю, что мне понадобились) события DocumentCompleted и FileDownload WebBrowser для обработки задержек при загрузке страниц, загрузке файлов и т. Д. Есть ли в WebClient что-то еще, чего я не вижу? Есть ли другой класс, помимо WebBrowser, который более удобен для обслуживания и поможет?
В сводке
Блин, это долго. Прости! Было бы полезно иметь даже высокоуровневую рекомендацию для лучшего способа делать то, что я пытаюсь сделать, потому что ничего из того, что я пробовал, не сработало.
Обновление 22.10.09
Что ж, я думаю, что я ближе, но я снова застрял. У меня должен получиться zip-файл приличного размера с несколькими файлами в нем, но zip-файл, полученный из моего кода, пуст. Вот мой код:
// build post request
string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(targetHref);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
// encoding to use
Encoding enc = Encoding.GetEncoding(1252);
// build post string containing authentication information and add to post request
string poststring = "returnUrl=" + fixCharacters(targetDownloadFileUrl);
poststring += getUsernameAndPasswordString();
poststring += "&login2.x=0&login2.y=0";
// convert to required byte array
byte[] postBytes = enc.GetBytes(poststring);
request.ContentLength = postBytes.Length;
// write post to request
Stream postStream = request.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length);
postStream.Close();
// get response as stream
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
// writes stream to zip file
FileStream writeStream = new FileStream(fullZipFileName, FileMode.Create, FileAccess.Write);
ReadWriteStream(responseStream, writeStream);
response.Close();
responseStream.Close();
Код ReadWriteStream выглядит так.
private void ReadWriteStream(Stream readStream, Stream writeStream)
{
// taken verbatum from http://www.developerfusion.com/code/4669/save-a-stream-to-a-file/
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
Построение строки сообщения взято из моего предыдущего работающего приложения форм. Я сравнил полученные значения в poststring для обоих наборов кода (моего приложения рабочих форм и этого), и они идентичны.
Я даже не знаю, как это исправить дальше. Кто-нибудь видит что-нибудь очевидное, почему это не работает?
Заключение 23.10.09
Наконец-то это работает. Мне пришлось преодолеть пару важных препятствий. У меня возникли проблемы с кодом метода ReadWriteStream, который я получил в сети. Не знаю почему, но у меня это не сработало. Помог парень по имени JB на встрече с виртуальным коричневым мешком Клаудио Лассала я придумал этот код, который работал намного лучше для моих целей:
private void WriteResponseStreamToFile(Stream responseStreamToRead, string zipFileFullName)
{
// responseStreamToRead will contain a zip file, write it to a file in
// the target location at zipFileFullName
FileStream fileStreamToWrite = new FileStream(zipFileFullName, FileMode.Create);
int readByte = responseStreamToRead.ReadByte();
while (readByte != -1)
{
fileStreamToWrite.WriteByte((byte)readByte);
readByte = responseStreamToRead.ReadByte();
}
fileStreamToWrite.Flush();
fileStreamToWrite.Close();
}
Как Уилл предположил ниже, у меня возникли проблемы с аутентификацией. Следующий код помог решить эту проблему. Было добавлено несколько комментариев, касающихся ключевых проблем, с которыми я столкнулся.
string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm";
HttpWebRequest firstRequest = (HttpWebRequest)WebRequest.Create(targetHref);
firstRequest.AllowAutoRedirect = false; // this is critical, without this, NLM redirects and the whole thing breaks
// firstRequest.Proxy = new WebProxy("127.0.0.1", 8888); // not needed for production, but this helped in order to debug the http traffic using Fiddler
firstRequest.Method = "POST";
firstRequest.ContentType = "application/x-www-form-urlencoded";
// build post string containing authentication information and add to post request
StringBuilder poststring = new StringBuilder("returnUrl=" + fixCharacters(targetDownloadFileUrl));
poststring.Append(getUsernameAndPasswordString());
poststring.Append("&login2.x=0&login2.y=0");
// convert to required byte array
byte[] postBytes = Encoding.UTF8.GetBytes(poststring.ToString());
firstRequest.ContentLength = postBytes.Length;
// write post to request
Stream postStream = firstRequest.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length); // Fiddler shows that post and response happen on this line
postStream.Close();
// get response as stream
HttpWebResponse firstResponse = (HttpWebResponse)firstRequest.GetResponse();
// create new request for new location and cookies
HttpWebRequest secondRequest = (HttpWebRequest)WebRequest.Create(firstResponse.GetResponseHeader("location"));
secondRequest.AllowAutoRedirect = false;
secondRequest.Headers.Add(HttpRequestHeader.Cookie, firstResponse.GetResponseHeader("Set-Cookie"));
// get response to second request
HttpWebResponse secondResponse = (HttpWebResponse)secondRequest.GetResponse();
// write stream to zip file
Stream responseStreamToRead = secondResponse.GetResponseStream();
WriteResponseStreamToFile(responseStreamToRead, fullZipFileName);
responseStreamToRead.Close();
sl.logScriptActivity("Downloading update.");
firstResponse.Close();
Я хочу подчеркнуть, что установка для AllowAutoRedirect значения false в первом экземпляре HttpWebRequest была критичной для всей работы. Fiddler показал два дополнительных запроса, которые произошли, когда он не был установлен, и сломал остальную часть скрипта.