[WebServices] 之一:基礎知識

1. 有關生存期的補充

正常情況下,每次調用 WebMethod,服務器都會創建一個新的 WebService 對象,即便客戶端使用同一個代理對象多次調用 WebMethod。

而我們一旦調用了有緩存標記的 WebMethod,只要未超出緩存期,WebService 對象都不會被重新創建。在緩存期內調用沒有緩存標記的 WebMethod,也會繼續使用該 WebService 對象。有太多因素讓這個緩存機制變得不那麼可靠,因此我們不能奢望用緩存標記來維持特定的對象狀態,況且緩存機制的設計初衷也只是爲了快速輸出那些比較穩定非常大的數據。

基於多用戶併發調用這個環境,WebService 本身最好設計成無狀態對象,我們可以使用 Session 和 Application 來保持特定的狀態信息。

2. 異步調用

網上很多人在寫有關 .net 2.0 的文章時,都喜歡用“優雅”這個詞。的確,在 2.0 中編譯器和代碼生成器爲我們封裝了很多羅嗦的東西,諸如匿名方法、委託推斷等等,當然還有這 WebService 的異步調用。我們不用再寫那些個 BeginXXX、EndXXX 了,基於事件驅動的異步機制會自動爲每個 WebMethod 生成一個 XXXAsync 的異步方法和 XXXCompleted 事件,我們只需調用該方法,並處理該事件即可完成異步操作,當真是優雅了不少。不要小看 2.0 的這些封裝,我們編寫的代碼越少意味着出錯的機率越小。

下面的示例中,我們使用了匿名方法來處理事件,看上去更簡潔了些。

WebServices.cs
[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  [WebMethod]
  public string HelloWorld()
  {
    return "Hello World!";
  }
}

Client.cs
WebService ws = new WebService();
ws.HelloWorldCompleted += delegate(object sender, HelloWorldCompletedEventArgs e)
{
  Console.WriteLine(e.Result);
};

ws.HelloWorldAsync("xxx");

3. 緩存

WebMethodAttribute.CacheDuration 爲 WebService 提供了緩存申明機制。通過添加該標記,我們可以緩存輸出結果。不過緩存機制會影響 WebService 的生存期(見上)。

WebServices.cs
[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  [WebMethod(CacheDuration=10)]
  public DateTime TestCache()
  {
    return DateTime.Now;
  }
}

Client.cs
WebService ws = new WebService();

for (int i = 0; i < 20; i++)
{
  Console.WriteLine("{0}:{1}", i + 1, ws.TestCache());
  Thread.Sleep(1000);
}

4. 保持狀態

.NET WebService 是建立在 ASP.NET 基礎上,在 WebService 中我們同樣可以訪問 Session、User、Application 等上下文對象,不過在某些使用細節上可能有所不同。

由於 WebService 客戶端代理對象可能應用於 ConsoleApplication、WinForm 或 WebForm 等環境,而 Session 又必須通過 Cookie 來保存唯一的 SessionID,因此我們必須使用 CookieContainer 創建 Cookie 容器來保存 WebService 返回的 Session 信息,否則每次調用的 SessionID 都不同,自然無法使用 Session 來保存狀態了。

創建容器對象後,必須將其引用賦值給代理對象的 CookieContainer 屬性。在第一次調用 SessionEnabled WebMethod 後,該容器將持有 Session Cookie 信息。如果需要在多個代理對象中調用 SessionEnabled WebMethod,那麼它們必須持有同一個 Cookie 容器對象。

WebServices.cs
[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  [WebMethod(EnableSession = true)]
  public string TestSession()
  {
    string s = "TestSession";
    object o = Session[s];
    int i = o != null ? (int)o : 0;

    ++i;
    Session[s] = i;

    return Session.SessionID.ToString() + ":" + i;
  }
}

Client.cs
WebService ws = new WebService();

// 創建Cookie容器,保持SessionID。否則每次調用的 SessionID 都不同。
CookieContainer cookies = new CookieContainer();
ws.CookieContainer = cookies;

for (int i = 0; i < 10; i++)
{
  Console.WriteLine("{0}:{1}", i + 1, ws.TestSession());
}

至於 Application 的使用和 WebForm 中基本沒有什麼區別。

WebServices.cs
[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  [WebMethod]
  public DateTime TestApplicationState()
  {
    object o = Application["TestApplicationState"];
    if (o == null)
    {
      o = DateTime.Now;
      Application["TestApplicationState"] = o;
    }

    return (DateTime)o;
  }
}

Client.cs
for (int i = 0; i < 10; i++)
{
  WebService ws = new WebService();
  Console.WriteLine("{0}:{1}", i + 1, ws.TestApplicationState());
  Thread.Sleep(1000);
}

5. SoapHeader

SoapHeader 多數情況下用來傳遞用戶身份驗證信息,當然它的作用遠不止如此,有待於在實際應用中發掘。

SoapHeader 缺省情況下由客戶端代理對象發送給 WebService,當然我們可以通過 WebMethodAttribute.Direction 來改變傳送方向。

SoapHeader 使用步驟:

(1) 創建繼承自 System.Web.WebServices.SoapHeader 的自定義 SoapHeader 類型。
(2) 在 WebService 中創建擁有 public 訪問權限的自定義 SoapHeader 字段。
(3) 在需要使用 SoapHeader 的 WebMethod 上添加 SoapHeaderAttribute 訪問特性。SoapHeaderAttribute 構造必須指定 memberName 參數,就是我們在第二步中申明的字段名稱。
(4) 生成器會自動爲客戶端生成同名的自定義 SoapHeader 類型,只不過比起我們在 WebService 端創建的要複雜一些。同時還會爲代理類型添加一個 soapheaderValue 屬性。

在下面的演示代碼,客戶端將傳遞一個自定義 MyHeader 到 WebService。請注意,我們儘管在 WebService 中申明瞭 MyHeader 字段,但並沒有創建對象實例,這是因爲客戶端傳遞過來的 XML 中包含了 SoapHeader 信息,基礎結構會自動解析並創建對象實例,然後賦值給 my 字段。至於客戶端,自然需要創建一個 MyHeader 對象實例,並賦值給 WebService.MyHeaderValue 屬性。SoapHeaderAttribute.Direction 缺省就是 In,下面例子中的 "Direction = SoapHeaderDirection.In" 可以省略。

WebServices.cs
public class MyHeader : SoapHeader
{
  public string Username;
  public string Password;
}

[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  public MyHeader my;

  [WebMethod]
  [SoapHeader("my", Direction = SoapHeaderDirection.In)]
  public void TestSoapHeadIn()
  {
    System.Diagnostics.Debug.Write(my.Username);
    System.Diagnostics.Debug.Write(my.Password);
  }
}

Client.cs
WebService ws = new WebService();

MyHeader head = new MyHeader();
head.Username = "u2";
head.Password = "p2";

ws.MyHeadValue = head;
ws.TestSoapHeadIn();

我們改寫一下,將傳遞方向改爲從 WebService 到客戶端。自然我們需要調整 "Direction = SoapHeaderDirection.Out",在 WebMethod 中我們還必須創建 MyHeader 實例,因爲這次我們不會接受到客戶端傳遞的 SoapHeader 了。客戶端代理對象調用 WebMethod 後就可以使用 MyHeaderValue 屬性訪問其內容了。

WebServices.cs
public class MyHeader : SoapHeader
{
  public string Username;
  public string Password;
}

[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  public MyHeader my;

  [WebMethod]
  [SoapHeader("my", Direction = SoapHeaderDirection.Out)]
  public void TestSoapHeadOut()
  {
    my = new MyHeader();
    my.Username = "u1";
    my.Password = "p1";
  }
}

Client.cs
WebService ws = new WebService();
ws.TestSoapHeadOut();

Console.WriteLine(ws.MyHeaderValue.Username);
Console.WriteLine(ws.MyHeaderValue.Password);

6. 異常

ASP.NET WebService 通過 Fault XML 元素來傳遞異常信息,客戶端代理對象會生成一個 SoapException 的異常,並使用 Fault XML 信息填充其相關屬性,諸如 Message 等。另外我們可以對 WebService 進行異常包裝,除了傳遞 Exception Message 外,還可以傳遞一些錯誤狀態代碼,以便客戶端用戶做進一步處理。

WebServices.cs
[WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
  [WebMethod]
  public void TestException()
  {
    try
    {
      throw new Exception("aaa...");
    }
    catch (Exception e)
    {
      throw new SoapException(e.Message, new System.Xml.XmlQualifiedName("ErrorCode01"), e);
    }
  }
}

Client.cs
WebService ws = new WebService();

try
{
  ws.TestException();
}
catch (System.Web.Services.Protocols.SoapException e)
{
  Console.WriteLine(e.Message);
  Console.WriteLine(e.Code.Name);
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章