Что я пытаюсь сделать
Я пытаюсь создать решение любого вида, которое будет работать ночью на сервере Windows, проходить проверку подлинности на веб-сайте, проверять веб-страницу на сайте на наличие новых ссылок, указывающих на новую версию zip-файла, использовать новые ссылки (если имеется), чтобы загрузить ZIP-файл, разархивировать загруженный файл в существующую папку на сервере, использовать разархивированное содержимое (сценарии SQL и т. д.), чтобы создать экземпляр базы данных, и записывать все, что происходит с текстовым файлом. .
Приложение Forms: часть, которая работает как Сорта
Я создал приложение для Windows Forms, которое использует несколько элементов управления WebBrowser, несколько потоков и несколько таймеров для выполнения всего этого, кроме работы по ночам. Он отлично работает в качестве формы, когда я вошел в систему и запустил ее, но мне нужно, чтобы она (или что-то в этом роде) работала самостоятельно, как служба или запланированная задача.
Моя попытка обслуживания
Итак, я создал службу Windows, которая работает каждый час, и, если System.DateTime.Now.Hour> = 22, пытается запустить приложение Windows Forms, чтобы выполнить свою задачу. Когда служба пытается запустить форму, возникает эта ошибка:
Элемент управления ActiveX '8856f961-340a-11d0-a96b-00c04fd705a2' не может быть создан, поскольку текущий поток не находится в однопоточной квартире.
, который я исследовал и попытался найти, либо поместив атрибут [STAThread] в метод Main класса программы Сервиса, либо используя некоторый код, подобный этому, в нескольких местах, включая конструктор Form:
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 были единственной вещью, которую я мог заставить работать, но теперь я волнуюсь, что это может быть причиной того, что запланированное задание не будет работать.
Относительно моего выбора WebBrowser Control
Я читал о классе System.WebClient в качестве альтернативы элементу управления WebBrowser, но на первый взгляд он не выглядит так, как будто он обладает тем, что мне нужно для этого. Например, мне нужно (или я думаю, что мне нужно) события DocumentCompleted и FileDownload WebBrowser для обработки задержек при загрузке страниц, загрузке файлов и т. Д. Есть ли что-то еще, что я не вижу в WebClient? Есть ли другой класс, кроме WebBrowser, который более удобен в обслуживании и сработает?
В итоге
Боже, это долго. Сожалею! Было бы полезно даже получить рекомендации высокого уровня для лучшего способа делать то, что я пытаюсь сделать, потому что ничего из того, что я пробовал, не сработало.
Обновление 10/22/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 для обоих наборов кода (моего рабочего приложения и этого) и они идентичны.
Я даже не уверен, как решить эту проблему дальше. Кто-нибудь видит что-нибудь очевидное относительно того, почему это не работает?
Заключение 10/23/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 показал два дополнительных запроса, которые произошли, когда это не было установлено, и сломал остальную часть сценария.