Справка по Windows Service / Scheduled Task, которая должна использовать веб-браузер и файловые диалоги - PullRequest
0 голосов
/ 07 октября 2009

Что я пытаюсь сделать

Я пытаюсь создать решение любого вида, которое будет работать ночью на сервере 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 показал два дополнительных запроса, которые произошли, когда это не было установлено, и сломал остальную часть сценария.

Ответы [ 3 ]

1 голос
/ 07 октября 2009

Вы пытаетесь использовать элементы управления пользовательского интерфейса, чтобы сделать что-то в службе Windows. Это никогда не сработает.

Что вам нужно сделать, это просто использовать WebRequest и WebResponse классы для загрузки содержимого веб-страницы.

var request = WebRequest.Create("http://www.google.com"); 
var response = request.GetResponse(); 
var stream = response.GetResponseStream();

Вы можете вывести содержимое потока, проанализировать текст в поисках обновлений и затем создать новый запрос для URL файла, который вы хотите загрузить. В этом ответном потоке будет файл, который вы можете записать в файловую систему и т. Д. И т. Д.

Прежде чем задуматься, GetResponse будет блокироваться до тех пор, пока не вернется ответ, и поток будет блокироваться при получении данных, поэтому вам не нужно беспокоиться о событиях, запускаемых после загрузки всего.

0 голосов
/ 09 октября 2009

Единственное, что я могу добавить, это то, что, как только вы очистите свой экран и получите полное имя файла, который хотите загрузить, вы можете передать его в команду Windows / DOS 'get', которая будет загружать файлы через HTTP. При желании вы также можете написать клиент FTP из командной строки. Я давно пробовал что-то подобное в Windows, но я думаю, что вы почти у цели. После того, как вы выбрали правильный файл, создание командного файла для выполнения всего остального должно быть довольно простым. Если вам удобнее работать с Unix, google "unix services for windows" просто следите за сервисами, которые они запускают (DHCP и т. Д.). Есть несколько полезных утилит, которые позволят вам рассматривать dos как оболочку, похожую на unix (ls -l, grep и т. Д.). Наконец, вы можете попробовать другой язык, такой как Perl или Python, но я не думаю, что это был совет находясь в поиске. :)

0 голосов
/ 07 октября 2009

Вам определенно необходимо переосмыслить свой подход (как вы уже начали это делать), чтобы исключить подход на основе форм. Описываемая вами служба должна работать вообще без пользовательского интерфейса.

Я не знаком с деталями System.WebClient, но так как он

предоставляет общие методы отправки данные и получение данных от ресурс, идентифицируемый URI,

вероятно, это будет ваш ответ. На первый взгляд WebClient.DownloadFile (...) или WebClient.DownloadFileAsync (...) будет делать то, что вам нужно.

...