程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#訪問Hotmail

C#訪問Hotmail

編輯:關於C#

POP郵件協議的優點在於它是一個開放的標准,有著完善的文檔,這就使得編寫POP郵件客戶程序不那麼困難,只要掌握了POP、SMTP的基礎知識,就可以寫出代理程序來執行各種任務,例如過濾廣告和垃圾郵件,或提供e-mail自動應答服務。

Hotmail是世界上影響最廣的Web郵件系統,遺憾的是,當我們要為Hotmail編寫獨立的客戶程序(不通過浏覽器訪問的客戶程序)時,馬上就會遇到Hotmail不提供POP網關這一障礙。

雖然Hotmail不提供POP支持,但浏覽器並非訪問Hotmail的唯一途徑。例如,利用Outlook Express可以直接連接到標准的Hotmail或MSN信箱,提取、刪除、移動或發送郵件。利用HTTP包監視器,我們可以監視到Outlook Express和Hotmail的通信過程,分析出客戶程序如何連接到Hotmail信箱。

Outlook Express利用了一種通常稱為HTTPMail的未公開的協議,借助一組HTTP/1.1擴展訪問Hotmail。本文將介紹HTTPMail的一些特點以及利用C#客戶程序訪問Hotmail的過程。本文的示例程序利用COM互操作將XMLHTTP用作一種傳輸服務。XMLHTTP組件提供了一個完善的HTTP實現,除了包括認證功能,還能夠在發送HTTP請求給服務器之前設置定制的HTTP頭。

一、連接HTTPMail網關

Hotmail信箱默認的HTTPMail網關在http://services.msn.com/svcs/hotmail/httpmail.asp。HTTPMail協議實際上是一個標准的WebDAV服務,只不過尚未公開而已。在編寫C#程序時,我們可以方便地調用.NET框架在System.Net名稱空間中提供的各個TCP和HTTP類。另外,由於我們要操作WebDAV,在C#環境下利用XMLHTTP連接Hotmail最為簡便,只需引用一下MSXML2組件就可以直接訪問。注意在本文的代碼片斷中,帶有下滑線後綴的變量是示例代碼中聲明的成員域:

// 獲得名稱空間
  using MSXML2;
  ...
  // 創建對象
  xmlHttp_ = new XMLHTTP();

為了連接到安全服務器,WebDAV協議要求執行HTTP/1.1驗證。HTTPMail客戶程序發出的第一個請求利用WebDAV PROPFIND方法查找一組屬性,其中包括Hotmail廣告條的URL以及信箱文件夾的位置:

<?xml version="1.0"?>
  <D:propfind xmlns:D="DAV:" xmlns:h="http://schemas.microsoft.com/hotmail/"
xmlns:hm="urn:schemas:httpmail:">
    <D:prop>
      <h:adbar/>
      <hm:contacts/>
      <hm:inbox/>
      <hm:outbox/>
      <hm:sendmsg/>
      <hm:sentitems/>
      <hm:deleteditems/>
      <hm:drafts/>
      <hm:msgfolderroot/>
      <h:maxpoll/>
      <h:sig/>
    </D:prop>
  </D:propfind>

通過XMLHTTP發送第一個請求時,首先指定WebDAV服務器URL,然後生成XML請求的內容:

// 指定服務器的URL
  string serverUrl = "http://services.msn.com/svcs/hotmail/httpmail.asp";
  // 構造查詢
  string folderQuery = null;
  folderQuery += "<?xml version='1.0'?><D:propfind xmlns:D='DAV:' ";
  folderQuery += "xmlns:h='http://schemas.microsoft.com/hotmail/' ";
  folderQuery += "xmlns:hm='urn:schemas:httpmail:'><D:prop><h:adbar/>";
  folderQuery += "<hm:contacts/><hm:inbox/><hm:outbox/><hm:sendmsg/>";
  folderQuery += "<hm:sentitems/><hm:deleteditems/><hm:drafts/>";
  folderQuery += "<hm:msgfolderroot/><h:maxpoll/><h:sig/></D:prop></D:propfind>";

XMLHTTP組件提供了一個open()方法來建立與HTTP服務器的連接:

void open(string method, string url, bool async, string user, string password);

open()方法的第一個參數指定了用來打開連接的HTTP方法,例如GET、POST、PUT或PROPFIND,通過這些HTTP方法我們可以提取文件夾信息、收集郵件或發送新郵件。為連接到Hotmail網關,我們指定用PROPFIND方法來查詢信箱。注意open()方法允許執行異步調用(默認啟用),對於帶圖形用戶界面的郵件客戶程序來說,異步調用是最理想的調用方式。由於本文的示例程序是一個控制台應用,我們把這個參數設置成false。

為了執行身份驗證,我們在open()方法中指定了用戶名字和密碼。在使用XMLHTTP組件時,如果open()方法沒有提供用戶名字和密碼參數,但網站要求執行身份驗證,XMLHTTP將顯示出一個登錄窗口。為了打開通向Hotmail網關的連接,我們把PROPFIND請求的頭設置成XML查詢的內容,消息的正文保持空白,然後發送消息:

// 打開一個通向Hotmail服務器的連接
  xmlHttp_.open("PROPFIND", serverUrl, false, username, password);
  // 發送請求
  xmlHttp_.setRequestHeader("PROPFIND", folderQuery);
  xmlHttp_.send(null);

二、分析信箱的文件夾列表

發送給services.msn.com的請求通常要經歷幾次重定向,經過服務器端的負載平衡處理,最後請求會被傳遞到一個空閒的Hotmail服務器,並執行身份驗證。在客戶端,這個重定向、執行身份驗證的交互過程由XMLHTTP組件負責處理。成功建立連接後,服務器還會要求設置一些Cookie、驗證當前會話的合法性,這部分工作同樣也由XMLHTTP組件自動處理。初始的連接請求發出之後,服務器將返回一個XML格式的應答:

// 獲得應答的內容
  string folderList = xmlHttp_.responseText;

服務器返回的應答包含許多有用的信息,其中包括信箱中文件夾的URL位置,下面是一個例子:

<?xml version="1.0" encoding="Windows-1252"?>
    <D:response>
      ...
      <D:propstat>
        <D:prop>
          <h:adbar>AdPane=Off*...</h:adbar>
          <hm:contacts>http://law15.oe.hotmail.com/...<;/hm:contacts>
          <hm:inbox>http://law15.oe.hotmail.com/...<;/hm:inbox>
          <hm:sendmsg>http://law15.oe.hotmail.com/...<;/hm:sendmsg>
          <hm:sentitems>http://law15.oe.hotmail.com/...<;/hm:sentitems>
          <hm:deleteditems>http://law15.oe.hotmail.com/...<;/hm:deleteditems>
          <hm:msgfolderroot>http://law15.oe.hotmail.com/...<;/hm:msgfolderroot>
          ...
      </D:prop>
    </D:response>
  </D:multistatus>

在本文的控制台示例程序中,我們感興趣的兩個文件夾是收件箱和發件箱的文件夾,它們分別用於接收和發送郵件。

在C#環境中解析XML的方法很多,由於我們肯定代碼涉及的所有XML文檔總是合法的,所以可以利用System.XML.XmlTextReader速度快的優勢。XmlTextReader是一個“只向前”的讀取器,下面把XML字符數據轉換成字符流,初始化XML讀取器:

// 初始化
  inboxUrl_ = null;
  sendUrl_ = null;
  // 裝入XML
  StringReader reader = new StringReader(folderList);
  XmlTextReader xml = new XmlTextReader(reader);

遍歷各個節點,選取出hm:inbox和hm:sendmsg節點,這兩個節點分別代表收件箱和發件箱:

// 讀取XML數據
  while(xml.Read())
  {
    // 是一個XML元素?
    if(xml.NodeType == XmlNodeType.Element)
    {
      // 獲取該節點
      string name = xml.Name;
      // 該節點代表收件箱?
      if(name == "hm:inbox")
      {
        // 保存收件箱URL
        xml.Read();
        inboxUrl_ = xml.Value;
      }
      // 該節點代表發件箱?
      if(name == "hm:sendmsg")
      {
        // 保存發件箱URL
        xml.Read();
        sendUrl_ = xml.Value;
      }
    }
  }

只有先獲取當前這次會話的合法的收件箱和發件箱URL,才可以發送和接收郵件。

三、列舉文件夾內容

得到了信箱文件夾(如收件箱)的URL之後,就可以向該文件夾的URL發送WebDAV請求列舉其內容。示例程序定義了一個托管類型MailItem,用來保存文件夾裡一項內容(即一個郵件)的信息。文件夾內容列舉從初始化一個MailItems數組開始:

// 初始化
  ArrayList mailItems = new ArrayList();

為獲得郵件主題、收件人地址、發件人地址之類的郵件基本信息,我們要用到下面XML格式的WebDAV查詢:

<?xml version="1.0"?>
  <D:propfind xmlns:D="DAV:" xmlns:hm="urn:schemas:httpmail:" xmlns:m="
  urn:schemas:mailheader:">
    <D:prop>
      <D:isfolder/>
      <hm:read/>
      <m:hasattachment/>
      <m:to/>
      <m:from/>
      <m:subject/>
      <m:date/>
      <D:getcontentlength/>
    </D:prop>
  </D:propfind>

生成上述XML查詢字符串的C#代碼:

// 構造查詢
  string getMailQuery = null;
  getMailQuery += "<?xml version='1.0'?><D:propfind xmlns:D='DAV:' ";
  getMailQuery += "xmlns:hm='urn:schemas:httpmail:' ";
  getMailQuery += "xmlns:m='urn:schemas:mailheader:'><D:prop><D:isfolder/>";
  getMailQuery += "<hm:read/><m:hasattachment/><m:to/><m:from/><m:subject/>";
  getMailQuery += "<m:date/><D:getcontentlength/></D:prop></D:propfind>";

就象前面獲取信箱文件夾清單的方式一樣,上述請求也通過XMLHTTP用PROPFIND方法發送,這次我們把請求的正文設置成查詢字符串。由於當前會話的用戶身份已經通過驗證,所以XMLHTTP open()調用中不必再提供用戶名字和密碼:

// 獲取郵件信息
  xmlHttp_.open("PROPFIND", folderUrl, false, null, null);
  xmlHttp_.send(getMailQuery);
  string folderInfo = xmlHttp_.responseText;

如果請求成功,服務器返回的應答XML流包含了該文件夾中各個郵件的信息:

<D:multistatus>
    <D:response>
      <D:href>
        http://sea1.oe.hotmail.com/cgi-bin/hmdata/...
      </D:href>
      <D:propstat>
        <D:prop>
          <hm:read>1</hm:read>
          <m:to/>
          <m:from>Mark Anderson</m:from>
          <m:subject>RE: New Information</m:subject>
          <m:date>2002-08-06T16:38:39</m:date>
          <D:getcontentlength>1238</D:getcontentlength>
        </D:prop>
        <D:status>HTTP/1.1 200 OK</D:status>
      </D:propstat>
    </D:response>
    ...

觀察服務器返回的應答,我們發現每一個節點包含一組標識郵件的域,例如通過標記可提取出郵件。下面我們再次使用System.XML.XmlTextReader解析這個XML數據流,首先初始化流讀取器:

MailItem mailItem = null;

// 裝入XML
  StringReader reader = new StringReader(folderInfo);
  XmlTextReader xml = new XmlTextReader(reader);

四、分析郵件基本信息

為了遍歷一次就解析好整個XML文檔,我們在每次打開元素時就創建一個新的MailItem實例,一遇到標記的末尾就保存該實例,在此期間,我們提取並設置MailItem的域:

// 讀取XML數據
  while(xml.Read())
  {
    string name = xml.Name;
    XmlNodeType nodeType = xml.NodeType;
    // 是一個email?
    if(name == "D:response")
    {
        // 開始?
        if(nodeType == XmlNodeType.Element)
        {
          // 創建一個新的MailItem
          mailItem = new MailItem();
        }
        // 結束?
        if(nodeType == XmlNodeType.EndElement)
        {
          // 保存email
          mailItems.Add(mailItem);
          // 清除變量
          mailItem = null;
        }
      }

// 是一個元素?
      if(nodeType == XmlNodeType.Element)
      {
        // 郵件的URL屬性
        if(name == "D:href")
        {
          // 繼續讀取
          xml.Read();
          mailItem.Url = xml.Value;
        }

// 郵件的“已閱讀”屬性
        if(name == "hm:read")
        {
          // 繼續讀取
          xml.Read();
          mailItem.IsRead = (xml.Value == "1");
        }

// 其他MailItem的屬性...
      }
    }

上面的代碼枚舉指定文件夾內的每一個MailItem,分別提取各個MailItem的下列屬性:

XML節點  說明 
D:href  用來提取郵件的URL 
hm:read  如果郵件已閱讀,則該標記被設置 
m:to  收件人 
m:from  發件人 
m:subject  郵件主題 
m:date  時間標記 
D:getcontentlength  郵件的大小(字節數)

五、接收郵件

枚舉出文件夾裡面的MailItem之後,我們就可以利用MailItem的URL獲得郵件本身,只需要向Hotmail服務器發送一個HTTP/1.1 GET請求就可以了。示例代碼中的LoadMail()函數輸入一個MailItem實例作為參數,返回郵件的內容:

/// <summary>
  /// 下載MailItem指定的郵件
  /// </summary>
  public string LoadMail(MailItem mailItem)
  {
    // 郵件的URL
    string mailUrl = mailItem.Url;
    // 打開Hotmail服務器連接
    xmlHttp_.open("GET", mailUrl, false, null, null);
    // 發送請求
    xmlHttp_.send(null);
    // 獲取應答
    string mailData = xmlHttp_.responseText;
    // 返回郵件數據
    return mailData;
  }

六、發送郵件

LoadMail()方法通過發送HTTP/1.1 GET請求獲取郵件,類似地,用Hotmail發件箱發送郵件時我們提交一個POST請求,如下面的SendMail()方法所示。

/// <summary>
  /// 發送一個郵件
  /// </summary>
  public void SendMail(string from, string fromName,
    string to, string subject, string body)
  {
    ...
  }

首先准備好後面要用到的引號字符以及郵件的時間標記:

// 引號字符
  string quote = "\u0022";

// 時間標記
  DateTime now = DateTime.Now;
  string timeStamp = now.ToString("ddd, dd MMM yyyy hh:mm:ss");

HTTPMail協議采用與SMTP相似的通信模式。Outlook Express用MIME格式發送郵件,但為簡單計,本例我們只發送純文本的郵件:

// 構造POST請求的內容
  string postBody = null;
  // 郵件頭.
  postBody += "MAIL FROM:<" + from + ">\r\n";
  postBody += "RCPT TO:<" + to + ">\r\n";
  postBody += "\r\n";
  postBody += "From: " + quote + fromName + quote + " <" + from + ">\r\n";
  postBody += "To: <" + to + ">\r\n";
  postBody += "Subject: " + subject +"\r\n";
  postBody += "Date: " + timeStamp + " -0000\n";
  postBody += "\r\n";
  // 郵件正文
  postBody += body;

發送郵件時,我們要把Content-Type請求頭設置成message/rfc821,表示這個請求包含一個遵從RFC821的消息。最後要做的就是把郵件發送到服務器:

// 打開連接
  xmlHttp_.open("POST", sendUrl_, false, null, null);
  // 發送請求
  xmlHttp_.setRequestHeader("Content-Type", "message/rfc821");
  xmlHttp_.send(postBody);

只要目標地址正確無誤,Hotmail就會把郵件發送到目的地。

結束語:

Hotmail是世界上最大的免費Web郵件提供商。但是,Hotmail使用的HTTPMail協議是非公開的,從而為編寫直接訪問Hotmail的客戶程序帶來了困難。本文示范了如何在C#環境中利用XMLHTTP組件直接連接到Hotmail,以及如何發送和接收郵件,證明了通過HTTPMail連接Hotmail可以做到象使用POP3、IMAP4、SMTP等協議一樣簡單。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved