2008. 12. 20. 05:14

사용자 삽입 이미지
































플로우 차트만 보면 무척 간단하다.

업데이트할 내용이 있는지 검사해서 있으면 하고 없으면 말고. 이게 끝이다.


자동업데이트 기능은 인터넷의 혜택으로 인해 요즘 프로그램에선 필수 이다.

예제 보고 간단하게 함 따라 해봤는데 나름대로 구미에 맞게 수정하다 보니 제법 그럴싸해졌다.

첨엔 ini 파일을 사용했는데 그거보단 레지스트리가 난거 같아서 레지스트리에 업데이트 정보를 저장해서 사용한다.

DB와 연동해서 프로그램별로 버전관리가 가능하게 했다.



업데이트 관리 DB – 업데이트 프로그램 통합 관리

3개의 테이블 사용 프로그램 관리, 업데이트 관리, 업데이트 로그

프로그램 아이디로 구분 하나의 자동업데이트 프로그램을 이용해 모든 프로그램에 적용하여 사용할 수 있다.

설정파일의 프로그램 아이디로 업데이트 관리 테이블을 검색하고 업데이트 내용을 가져와서 해당 파일들을 다운로드 받은 후 업데이트 결과를 업데이트 로그 테이블에 저장해준다.

각 프로그램 단위로 업데이트 상황을 파악할 수 있다.


업데이트 파일 검색

버전 정보를 날짜 형식으로 지정해서 현재 버전 보다 높은 버전이 있는지 검색

SELECT TOP 1 IDX, ID, VERSION, CONTENTS FROM UPDATE_LIST WHERE ID=@ID AND VERSION>@VERSION ORDER BY VERSION ASC

업데이트 로그 등록

업데이트할 모든 파일을 다운로드 받은 후 로그 기록

INSERT INTO UPDATE_LOG (UPDATE_IDX, IP, DOWN_DATE) VALUES (@IDX, @IP, GETDATE())






DB는 아무거나 상관없다.

처음에는 프로그램에서 직접 디비에 접속을 했는데 업데이트 로그 기록하려고 클라이언트의 IP정보를 어떻게 받아오나 고민하다가

좀더 효과적인 방법을 찾았다.

프로그램에서 디비에 직접 접속하는게 아니고 특정 웹페이지로 접속을 해서 업데이트 정보를 가져온다.

가져온 정보로 업데이트 하고 다시 웹페이지를 접속해서 로그를 저장하면 된다.

이렇게 하면 우선 프로그램에 디비 접속 정보가 없어도 되기 때문에 좀더 보안에 안전하다.

그리고 약간의 유연성이 더 있을 것 같다.


프로그램이 처음 실행될때 업데이트를 체크하도록 했는데 시작 프로그램에 등록해 놓으니 프로그램이 먼저 시작하고 나서 나중에

인터넷에 연결이 되는 상황이 발생한다.

인터넷 연결이 나중에 되더라도 프로그램이야 잘 돌아가지만 업데이트는 전혀 안되는 상황이 생길 수 있다.

그래서 프로그램 실행 중간에 한번씩 업데이트 체크하는 부분을 추가해야 된다.



08.08.06 보완사항

메인 프로그램에서 10초에 한번씩 작업을 진행하는데 타이머 위치가 잘못 되서 특정 예외 상황 발생시 거의 무한 루프에 빠져버리는 문제점을 발견했다.

몇일 쉬다가 다시 코드를 보니 원인 찾는데 넘 오래 걸렸다.

타이머 시작 위치 변경 해서 해결 했다.


40대의 컴에서 동시에 작업을 하다 보니 디비 서버가 뻗어버렸다.

디비서버 동시 접속수 늘려주고 디비 작업 후 연결 닫는 구문을 모두 추가해주었다.


메인 프로그램에서 자동업데이트 체크하는 부분하고 마무리 작업 처리하는 부분만 추가해주면 일단 마무리.





자동업데이트 소스

using System;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Web;
using Microsoft.Win32;
using System.Data.SqlClient;
using MySql.Data.MySqlClient;
using System.ComponentModel;

namespace Keyword_Search_Starter
{
    public partial class Starter : Form
    {
private string sLocalFilePath; // 실행 경로 private string sServerFilePath; // 다운받을 서버주소 private string sResultMsg; // 업데이트 체크 결과 메세지(Error, OK) private string sID = null; // 프로그램 아이디 private string sIDX = null; // 업데이트 리스트 키값 private string sAuto = null; // 자동업데이트 유무(1, 0) private string sVersion = null; // 업데이트 버전 - 6자리날짜(080803) private string sStartProgram = null; // 업데이트 후 실행할 프로그램 private string sUserIP = null; // 접속 아이피 로그기록 private int iUpdateCount = 0; // 파일 다운로드 숫자 - 전송완료 될 때마다 증가함 private string sRegKey = "Software\\Pharos"; private string sUpdateAddr = "http://blog.naver.com/inidu2/Upload/UpdateListView.aspx"; public Starter() { InitializeComponent(); } #region Starter_Load 레지스트리 정보 읽기 private void Starter_Load(object sender, EventArgs e) { sLocalFilePath = AppDomain.CurrentDomain.BaseDirectory; // 레지스트리 설정 내용 확인 RegistryKey reg = Registry.LocalMachine; reg = reg.CreateSubKey(sRegKey); sID = (string)reg.GetValue("ID", null); sStartProgram = (string)reg.GetValue("StartProgram", null); sVersion = (string)reg.GetValue("Version", null); sAuto = Convert.ToString(reg.GetValue("Auto", null)); if (reg.GetValue("UpdateAddr", null) != null) { sUpdateAddr = Convert.ToString(reg.GetValue("UpdateAddr", null)); } lbTitle.Text += " ID : " + sID; GetUpdateList(); } #endregion #region GetUpdateList() 업데이트 리스트 가져오기 private void GetUpdateList() { string path = sUpdateAddr + "?id=" + sID + "&version=" + sVersion; try { HttpWebRequest hwr = (HttpWebRequest)WebRequest.Create(path); HttpWebResponse hwrp = (HttpWebResponse)hwr.GetResponse();
Stream strm = hwrp.GetResponseStream(); StreamReader sr = new StreamReader(strm); listView1.Items.Clear(); int line = 1, n = 1; string temp = null; while (sr.Peek() > -1) { temp = sr.ReadLine(); switch (line) { case 1: sResultMsg = temp; break; // 결과 메시지 case 2: sUserIP = temp; break; // 접속 IP case 3: sIDX = temp; break; // 업데이트 리스트 키 case 4: sVersion = temp; break; // Version case 5: sServerFilePath = temp; break; // Path default: // File List listView1.Items.Add(n.ToString()); listView1.Items[n - 1].SubItems.Add(temp); listView1.Items[n - 1].SubItems.Add("전송대기"); n++; break; } line++; } sr.Close(); strm.Close(); } catch { } finally { } // 업데이트 항목이 없으면 무시하고 프로그램 실행 if (listView1.Items.Count == 0) { AppStart(); } else { btnStart.Enabled = false; DownLoad(); } } #endregion #region DownLoad() 다운로드 protected void DownLoad() { this.Cursor = Cursors.WaitCursor; // 다운로드할 파일이 있으면 다운로드 if (listView1.Items.Count > 0) { for (int i = 0; i < listView1.Items.Count; i++) { string serverFile = sServerFilePath + listView1.Items[i].SubItems[1].Text; string localFile = sLocalFilePath + listView1.Items[i].SubItems[1].Text; try { WebClient webClient = new WebClient(); listView1.Items[i].SubItems[2].Text = "전송중"; webClient.DownloadFileCompleted +=
new AsyncCompletedEventHandler(Completed); webClient.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(ProgressChanged); webClient.DownloadFileAsync(new Uri(serverFile), @localFile); listView1.Items[i].SubItems[2].Text = "전송완료"; } catch (Exception ex) { listView1.Items[i].SubItems[2].Text = "전송에러"; MessageBox.Show(serverFile + "\r\n" + ex.Message + "\r\n" + ex.StackTrace); } } } this.Cursor = Cursors.Default; } #endregion private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e) { progressBar1.Value = e.ProgressPercentage; lbState.Text = e.ProgressPercentage.ToString() + "%"; } #region Completed 전송완료 private void Completed(object sender, AsyncCompletedEventArgs e) { iUpdateCount++; if (iUpdateCount == listView1.Items.Count) { this.UpdateLog(); this.AppStart(); } } #endregion #region AppStart() 프로그램 실행 private void AppStart() { try { string AppPath = AppDomain.CurrentDomain.BaseDirectory; System.Diagnostics.Process ps = new System.Diagnostics.Process(); ps.StartInfo.FileName = AppPath + sStartProgram; ps.Start(); // 타이머 작업으로 업데이트 감시 Application.Exit(); } catch (Exception ex) { MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace); } } #endregion #region UpdateLog() 업데이트 로그 작성 및 버전 정보 업데이트 private void UpdateLog() { // 레지스트리 설정 내용 확인 RegistryKey reg = Registry.LocalMachine; reg = reg.CreateSubKey(sRegKey); reg.SetValue("Version", sVersion); reg.SetValue("LastUpdate", DateTime.Now); // 업데이트 로그 string path = sUpdateAddr + "?mode=log&idx=" + sIDX; HttpWebRequest hwr = (HttpWebRequest)WebRequest.Create(path); HttpWebResponse hwrp = (HttpWebResponse)hwr.GetResponse(); } #endregion private void btnStart_Click(object sender, EventArgs e) { DownLoad(); } } }

[출처] 자동 업데이트|작성자 파로스

Posted by 굿데이
2008. 12. 20. 05:05
1. 소스를 정리하였습니다.

아래 표는 각 프로젝트와 그에 속한 클래스에 대한 설명입니다.

Hau 자동 업데이터 컴퍼넌트 프로젝트. 아래 두 프로젝트에 의해 참조됨.
     AutoUpdater 자동 업데이터 컴퍼넌트
     RemoteFile 웹서버의 파일을 표현
     TransferingInfo 전송 상태를 표현
     UpdateListDataSet 업데이트 파일 목록을 읽고 쓰기 위한 스키마
Hau.Sample 자동 업데이터 컴퍼넌트를 이용하는 샘플 프로젝트
     NomAutoUpdater 자동 업데이터 컴퍼넌트를 이용하여 구현된 NoM의 자동 업데이터
Hau.UpdateListEditor 업데이트 파일 목록 편집기 프로젝트
    UpdateListEditorForm 업데이트 파일 목록 편집기

2. AutoUpdater에 LocalRoot 라는 속성이 추가되었습니다.

이전 버전에는 다운로드 위치가 항상 AutoUpdater 컴퍼넌트가 사용된 실행파일과 같은 위치였지만, 이제는 LocalRoot 속성을 지정하면 아무 곳에나 다운로드를 받을 수 있게 되었습니다.


3. 업데이트 파일 목록 편집기에서 제외 정규식과, 그리드의 편집 기능이 없어졌습니다.


4. 업데이트 파일 목록 편집기 '마지막 수정 시각 1초 증가' 버튼이 추가되었습니다.

이 기능은 기존 업데이트 목록을 열어서 특정 파일만 최신 버전으로 지정하는 경우에 유용하게 사용할 수 있습니다.

1초가 증가된 행의 배경색은 베이지로 변경됩니다. 또한 한번 1초가 증가된 행은 더 이상 증가되지 않습니다. (논리적으로 2초 이상을 증가시킬 이유가 없습니다.)


5. 업데이트 파일 목록 편집기의 그리드에서 Delete 키를 누르면 행이 두 개씩 삭제되던 버그를 수정하였습니다.




출처
http://kimgwajang.tistory.com/135

Posted by 굿데이
2008. 12. 20. 05:03

http://kimgwajang.tistory.com/category/HAU
에 있는 포스트를 참조하십시오.


김과장! 이게 뭐야?

자동 업데이터 입니다.

클릭온스 같은 거야?

네. 훨씬 더 단순한 로직이긴 하지만 결과적으로 비슷한 일을 합니다.

그런데 '클릭원스' 라고 발음해야 하는 것 아닌가요? 클릭(click) 한 번(once)으로 설치가 된다는 의미일테니 '원스'가 맞지 않겠습니까? '온스'는 ounce 라는 질량 단위가 있긴 하지만 클릭과는 상관이 없잖아요.

따지기는, 양놈들이 뭐라고 하든 국내에서는 클릭온스로 통해. 그리고 유명한 XX님도 클릭온스라고 발음해.

그건 그렇고, 왜 만든거야?

부장님도 아시다시피, 제가 클릭원스와 링크(LINQ) 매니아지 않습니까?

링크도 XX님은 '링큐'라고 하던데.

'링크'가 맞을 것 같아요. 왜냐 하면...

아니 됐고, 하던 이야기나 마저 해봐.

클릭원스가 나오기 전에, 그러니까 닷넷 1.X 시절에 HAU의 초기 버전을 만들어서 사용하곤 했었는데요. 클릭원스가 등장하면서 부터는 클릭원스만 애용하고 있습니다.

어떤 사람들은 클릭원스가 필드에 맞지 않는 쓸모 없는 기술이라고도 이야기하는데요. Smart Client Deployment with ClickOnce라는 책을 읽고 나서는 클릭원스는 제가 알고 있는 것보다 훨씬 많은 일을 할 수 있다는 것을 알게 되었습니다.

하지만 클릭원스를 사용하기 위해서는 몇 가지 요구사항, 즉 배포 서버에 IIS와 프론트페이지 서버 익스텐션이 설치되어 있어야 하는데, 필드에서는 상황에 따라선 이런 요구 사항이 충족되지 않는 경우가 종종 있었습니다.

그래서 클릭원스를 사용하는 것이 배포의 베스트 프랙티스라고 생각은 하지만, 클릭원스를 사용할 수 없는 환경도 있으니까 대안으로 웹 서버를 이용한 단순한 자동 업데이터를 만들게 된 것입니다.

웹 서버만 있으면 된다고? 리눅스에 아파치가 설치된 서버도 돼?

네 그렇습니다.

사장님이 좋아하시겠네.

사장님 좋아하시라고 만든 건 아니고요.

김과장이 만든 자동 업데이터는 어떤 장점이 있는데? 가볍다거나 무료다라는 것 말고. 그건 보통 완성도가 떨어지는 소프트웨어에 관습적으로 붙이는 말이니까.

풍부한 이벤트를 제공합니다.

HAU는 물리적으로 컴퍼넌트(System.ComponentModel.Component)로 되어 있는데 이 컴퍼넌트를 윈도우 폼이나 사용자 정의 컨트롤에 올려 놓아 사용할 수 있습니다. HAU가 다양한 이벤트를 제공하기 때문에 HAU를 사용하여 자동 업데이터를 개발하는 개발자는 자유로운 UI를 구현할 수 있습니다.

또?

계층 구조로 된 파일의 자동 업데이트를 지원합니다.

또?

업데이트 체크를 위한 웹 서버에의 요청을 최소화 하여 속도가 빠릅니다.

예컨데, A, B, C 세 개의 파일이 서버에 있고, 이 중 A와 B가 최신 버전이면, HAU는 세 파일에 대해서 각각 웹 서버에 요청을 보내 최신 버전 여부를 확인하지 않고, 업데이트 파일 목록를 구하는 한 번의 요청으로 최신 버전 여부를 판단합니다.

만일 배포된 파일이 200개 라면, 매 사용자가 프로그램을 실행할 때 마다 최신 버전이 있든 없든 항상 웹 서버에 200번의 요청을 보내야 하는 오버헤드가 발생할 수 있습니다.

업데이트 파일 목록이 뭐야? 어떻게 만드는 거야?

서버에 배포된 파일의 정보(경로, 크기, 수정 시각)를 저장하고 있는 XML 파일입니다.

업데이트 파일 목록의 XML 스키마는 너무나 단순하지만 직접 작성하기에는 번거로운 일이라서, 업데이트 파일 목록 편집기를 같이 만들었습니다. 이를 이용하면 업데이트 파일 목록을 쉽게 만들 수 있습니다.

더 있어?

음... 이런 말씀은 안드리려고 했는데, 가볍고 무료입니다.

좀 구차하지 않나?

소스가 단순하고 직관적입니다.

무슨 근거로?

닷넷 프레임웍 2.0에는 WebClient 라는 클래스가 추가됨으로 해서 HTTP 프로토콜을 사용한 다운로드가 무척 간단해졌습니다. 이를 직접 구현한 HAU의 이전 버전에 비하면 몇 백 줄 이상이 줄어들었습니다.

그리고 처음부터 공개를 목적으로 작성한 소스이기 때문에 최대한 다른 개발자가 쉽게 파악하기 용이한 구조로 작성하고 주석을 성실하게 달았습니다. 쓰레드에 대한 약간의 지식만 있으면 누구나 쉽게 커스터마이징이 가능할 것으로 자신합니다.

김과장이 보내준 비주얼 스튜디오 프로젝트에 들어 있는 파일들은 다 뭐야?

AutoUpdater는 자동 업데이트 기능을 구현한 컴퍼넌트 입니다. 따라서 업데이트 진행 상황을 표시하는 UI 같은 것은 포함되어 있지 않습니다. 대신 AutoUpdater이 노출하는 이벤트에 대한 핸들러를 작성하여 UI를 붙일 수 있습니다.

SampleForm는 바로 이러한 UI를 구현하는 예제로 만든 폼입니다.

마지막으로 UpdateListEditor는 업데이트 파일 목록 편집기입니다.

어떻게 사용하는 거야? 그림과 함께 상세하게 설명해줘봐.

NoM의 배포를 예로 들어 설명해보겠습니다.

NoM의 배포에 필요한 파일의 목록은 아래와 같습니다.

파란색은 파일, 빨간색은 폴더를 나타내는 계층구조를 가지고 있습니다.

참고로 dummy 폴더는 NoM의 배포에 필요하지는 않지만 설명의 편의 상 추가한 폴더입니다.

파일들을 모두 D:\NoM 폴더에 모아 두고 이후 작업을 진행합니다.

업데이트 목록 만들기

먼저 위 파일들을 가지고 업데이트 목록을 만들어야 합니다.

소스에 있는 UpdateListEditor를 실행합니다.

쌩뚱맞게 '제외 정규식'은 뭐야?

정규식을 입력하면, 그에 매치되는 파일은 업데이트 목록에서 제외하는 기능입니다.

지금은 제외할 파일이 없으니까 비워 두고 디렉토리 읽기를 클릭합니다.

폴더 찾아보기 대화상자가 열리면 D:\NoM를 선택합니다.

필요하다면 그리드에서 목록을 수정할 수 있지만, 지금은 바로 '저장하기'를 클릭하여 이 목록을 XML파일로 저장합니다.

D:\NoM\UpdatingList.xml로 저장합니다.

이제 업데이트 목록 파일이 만들어졌습니다.

배포 파일을 웹서버로 업로드 하기

위 파일들을 계층 구조를 그대로 유지한채로 웹서버에 올립니다. 위에서 만든 UpdatingList.xml 파일도 같이 올려야 합니다.

여기서는 http://www.dolsan.org/nullnull/Nom에 업로드한 것으로 하겠습니다.

여기까지가 배포 서버를 준비한 과정이었습니다. 지금부터는 배포 서버를 이용하여 자동으로 업데이트를 하는 응용 프로그램을 만들도록 하겠습니다.

자동 업데이터 응용프로그램 만들기

비주얼 스튜디오에서 새 윈폼 응용프로그램을 만듭니다.

소스에 있는 Framework.AutoUpdater.dll에 대한 참조를 추가한 후 도구상자에서 AutoUpdater 컴퍼넌트를 선택하여 폼으로 끌어다 놓습니다.

autoUpdater1의 속성 중 RootUri를 http://www.dolsan.org/nullnull/Nom로 UpdateListFileName을 UpdatingList.xml로 설정합니다.

구현할 UI에 따라 autoUpdater1의 이벤트 핸들러를 적절하게 구현합니다.

자세한 내용은 소스에 포함된 SampleForm을 참조하시기 바랍니다.

각 이벤트의 의미는 다음과 같습니다.

이벤트

의미

UpdateCompleted 업데이트가 끝났음
UpdatableListFound 업데이트 할 파일의 목록이 발견되었음
UpdatableFileFound 업데이트 할 파일이 발견되었음
FileTransfering 개별 파일의 전송이 시작되려고 함
FileTransfered 개별 파일의 전송이 완료되었음
UpdateProgressChanged 업데이트 진행 상황이 변경되었음

물론 이 중에서 가장 중요한 이벤트는 업데이트가 끝났음을 알리는 UpdateCompleted라고 할 수 있겠습니다.

이에 대한 이벤트 핸들러는 일반적으로 다음과 같이 구현될 것입니다.

NoM.exe를 실행하고 HAU는 종료하는 코드입니다.

시간이 지나 NoM이 업데이트 되면, 업데이트 목록 파일 만들기와 웹서버로 업로드하기를 반복하면 됩니다.

아 그렇군. 김과장 수고했어.

네.

Posted by 굿데이
2008. 12. 20. 03:11
지난번 HttpHelper 클래스를 이어 이번에는 WebClient를 이용한 Downloader 클래스를 만들어 보도록 하겠습니다.

다들 아시겠지만 Downloader 클래스는 실버라이틑 1.1에는 있었지만 실버라이트 2가 나오면서 사라지고 WebClient로 대체가 되었습니다.

물론 바뀐 WebClient가 사용방법이 어려운건 아니지만 좀 더 사용하기 편하게 하기 위해서 WebClient를 이용한 Downloader 클래스를 작성해 보았습니다.

WebClient를 이용한 Downloader 클래스 만들기

1. Downloader 클래스 작성

Downloader 클래스도 HttpHelper 클래스와 마찬가지로 기본 클래스, 이벤트, 다운로드 구분자로 구성 되어 있습니다.

사용할 네임스페이스

using System.Net;
using System.IO;

다운로드 구분

public enum DownloadKind
{
    Text,
    Binary
}

다운로드 구분은 크게 2가지로 텍스트와 바이너리로 구성이 됩니다. 텍스트는 txt, xml, html, js, cs, asp, aspx 같은 순수 텍스트로만 이루어진 파일이며, 바이너리는 wmv, zip, exe 같은 동영상이나 압축파일 등등 텍스트를 제외한 모든 파일이라고 생각해도 좋을거 같습니다.

이벤트 핸들러

public delegate void DownloadCompleteEventHandler(DownloadCompleteEventArgs e);
public class DownloadCompleteEventArgs : EventArgs
{
    public Stream ResultStream { get; set; }
    public string ResultString { get; set; }
    public DownloadCompleteEventArgs(Stream result)
    {
        this.ResultStream = result;
    }
    public DownloadCompleteEventArgs(string result)
    {
        this.ResultString = result;
    }
}
public delegate void DownloadErrorEventHandler(DownloadErrorEventArgs e);
public class DownloadErrorEventArgs : EventArgs
{
    public string Error { get; set; }
    public DownloadErrorEventArgs(string error)
    {
        this.Error = error;
    }
}

이벤트 핸들러는 크게 다운로드 완료 이벤트와 다운로드 에러 이벤트로 구성됩니다.

다음은 기본 클래스의 구성 입니다.

전역

WebClient       m_WebClient;
DownloadKind m_DownloadKind;
Uri                 m_DownloadUri;
public event DownloadCompleteEventHandler DownloadComplete;
private void OnDownloadComplete(DownloadCompleteEventArgs e)
{
    if (this.DownloadComplete != null)
    {
        this.DownloadComplete(e);
    }
}
public event DownloadErrorEventHandler DownloadError;
private void OnDownloadError(DownloadErrorEventArgs e)
{
    if (this.DownloadError != null)
    {
        this.DownloadError(e);
    }
}

다운로드에 필요한 변수와 이벤트를 발생 시키기 위한 내용입니다.

생성자

public Downloader(Uri downloadUri, DownloadKind kind)
{
    m_WebClient = new WebClient();
    m_DownloadKind = kind;
    m_DownloadUri  = downloadUri;
    if (m_DownloadKind == DownloadKind.Binary)
    {
        m_WebClient.OpenReadCompleted += new OpenReadCompletedEventHandler(m_WebClient_OpenReadCompleted);
    }
    else
    {
        m_WebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(m_WebClient_DownloadStringCompleted);
    }
}

다운로드 구분에 따라서 이벤트를 다르게 생성해 줍니다.

다운로드 시작 함수

public void Start()
{
    if (m_DownloadKind == DownloadKind.Binary)
    {
        m_WebClient.OpenReadAsync(m_DownloadUri);
    }
    else
    {
        m_WebClient.DownloadStringAsync(m_DownloadUri);
    }
}

역시 다운로드 구분에 따라서 Async 함수를 구분해서 실행 합니다.

바이너리 파일 다운로드 완료 이벤트

void m_WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if ((e.Error == null) && (e.Cancelled == false))
    {
        OnDownloadComplete(new DownloadCompleteEventArgs(e.Result));
    }
    else
    {
        OnDownloadError(new DownloadErrorEventArgs(e.Error.Message));
    }
}

텍스트 파일 다운로드 완료 이벤트

void m_WebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if(e.Error == null)
    {
        OnDownloadComplete(new DownloadCompleteEventArgs(e.Result));
    }
    else
    {
        OnDownloadError(new DownloadErrorEventArgs(e.Error.Message));
    }
}

이렇게 해서 Downloader 클래스 작성을 완료 했습니다. 이 클래스에 자신이 원하는 기능을 추가해서 확장 클래스를 작성해서 사용해도 좋을 것입니다.

Downloader 클래스 다운로드



2. Downloader 클래스 사용방법

다운로드 하기

Uri uri = new Uri(http://localhost/DownloaderTest.aspx);
Downloader down = new Downloader(uri, DownloadKind.Text);
down.DownloadComplete += new DownloadCompleteEventHandler(down_DownloadComplete);
down.DownloadError += new DownloadErrorEventHandler(down_DownloadError);
down.Start();

이벤트

void down_DownloadError(DownloadErrorEventArgs e)
{
    txtResult.Text = "에러 : " + e.Error;
}
void down_DownloadComplete(DownloadCompleteEventArgs e)
{
    // 텍스트일 경우
    txtResult.Text = e.ResultString;

    // 바이너리일 경우
    MediaElement media = new MediaElement();
    media.SetSource = e.ResultStream;
    ...
}

도메인 접근시 크로스 도메인이 허용이 안된 사이트는 에러 이벤트를 발생 시킵니다.
이 경우 에러는 Download Failure 라는 에러가 나오네요!!

이것으로 다운로더 클래스 사용 방법도 마치도록 하겠습니다.
아무쪼록 이 글을 보시는 분들께 많은 도움이 되었으면 좋겠습니다.

작성자 : 상현넘™ [http://www.shblitz.net]
Posted by 굿데이
2008. 12. 20. 02:21
//POST 방식으로 데이터를 업로드 하는 예제 입니다.

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Web;

class OpenWrite
{
        static void Main()
        {               
                string uriString = "http://***.***.***.**/write_ok.php";

                string name = "tester";
                string email = "oracler@oraclejava.co.kr";
                string subject = "방가와요...";
                string password = "1111";
                string memo = "사이트 오픈을 축하 합니다.";
                string id = "guest";

                string postData = "id=" + HttpUtility.UrlEncode(id) + "&" +
                                  "name=" + HttpUtility.UrlEncode(name) + "&" +
                                  "email=" + HttpUtility.UrlEncode(email) + "&" +
                                  "subject" + HttpUtility.UrlEncode(subject) + "&" +
                                  "password=" + HttpUtility.UrlEncode(password) + "&" +
                                  "memo" + HttpUtility.UrlEncode(memo);
                                       
                byte[] postArray = Encoding.Default.GetBytes(postData);

                // Create a new WebClient instance.
                WebClient myWebClient = new WebClient();
                myWebClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

                // postStream implicitly sets HTTP POST as the request method.
                Console.WriteLine("Uploading to {0} ...",  uriString);                                                        
                Stream postStream = myWebClient.OpenWrite(uriString);

                postStream.Write(postArray,0,postArray.Length);

                // Close the stream and release resources.
                postStream.Close();

                Console.WriteLine("\n 방명록에 성공적으로 글을 올렸습니다...");
        }
}
Posted by 굿데이
2008. 12. 20. 01:58

오랫만에 아주 간단하지만 편리한 Tip을 한가지 소개합니다.

C#으로 네트워크 통신을 하려면, System.Net 네임스페이스 안에 속한 WebClient 클래스는 거의 필수라고 해도 과언이 아닙니다.

사용법도 아주 간단합니다.
예를 들어 POST  방식으로 웹서버에 request를 던지고, 그 결과(UTF8)를 받는 부분이 필요하다고 가정을 해봅시다.
아래와 같이 몇줄로 가능하지요.

           using (WebClient client = new WebClient())
           {
                NameValueCollection col = new NameValueCollection();
                col.Add("param1", "aaaa");
                col.Add("param2", "bbbb");

                byte[] byteResponse = client.UploadValues("http://someuri", col);
                string response = Encoding.UTF8.GetString(byteResponse);

                //.....
            }


때때로 WebRequest 라는 보다 low level의 클래스를 사용하기도 합니다.

            string postData = "param1=aaaa";
            postData += "&param2=bbbbb";
            byte[] data = Encoding.UTF8.GetBytes(postData);
            HttpWebRequest request =
                                   (HttpWebRequest)WebRequest.Create("http://someuri");
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = data.Length;
            Stream writeStream = request.GetRequestStream();
            writeStream.Write(data, 0, data.Length);
            writeStream.Close();


WebClient 클래스와 비교하면 꽤나 성가신 코드가 추가되어야 합니다.
Request의 Method, ContentType 을 써주어야 하고, RequestStream을 얻어와서 byte 배열을 직접 써야합니다.

그러나 WebRequest 클래스는 WebClient가 제공하지 않는 여러가지 기능을 제공합니다.
예를 들어 오늘 Tip의 주제인 Timeout 입니다.

WebClient에 Timeout 기능이 포함되어 있지 않아서 정말 단순한 기능인데도 WebRequest 클래스를 이용하는 경우를 더러 보았습니다.

그러나 WebClient 클래스를 조금만 살펴보면,
내부적으로 WebRequest를 사용하는 것을 알 수 있습니다.
바로 GetWebRequest라는 함수인데요.


그럼 WebClient 클래스를 상속받아서 이 함수를 override 하는 클래스를 만들어 보겠습니다.

class MyWebClient : WebClient
        {
            protected override WebRequest GetWebRequest(Uri address)
            {
                WebRequest request = base.GetWebRequest(address);
                request.Timeout = 1000;
                return request;
            }
        }


위의 Timeout에 들어가는 값은 밀리초 입니다.
그러니까 1000 ms = 1 sec가 되겠죠?

어떤가요?
WebClient를 사용하면서 Timeout도 되는 클래스..
쉽죠? ^^


chaoskcuf
by chaoskcuf’s lab


출처
http://chaoskcuf.com/entry/C-WebClient에-timeout을-사용하는-방법

Posted by 굿데이