2011年6月23日 星期四

WCF Data Service 的優缺點

最近一直與 WCF Data Service 打交道。也漸漸了解這個技術的優勢與缺點了。整理如下

優點1:RESTful

如果使用WCF來開發一個 CustomerService,就免不了如下的Method

AddCustomer(Customer customer);
DeleteCustomer(int id);
UpdateCustomer(Customer customer);
FindCustomers(string condition);

新刪修查,至少要發佈4個 Method。如果使用 WCF Data Service,只需要一個 Url 來定位即可。如果再使用 Entity Framework,更是方便。

優點2:WCF Data Service Client Library

使用 WCF Data Service Client Library 來查詢 OData的資料,可再進一步使用 LINQ 的語法來查詢資料。新增/刪除/修改資料時,更像是在操作 Entity Framework 一般的方便。見更新資料服務 (WCF Data Services)

優點3:跨平台

這個年代,服務不能跨平台,就沒有人要使用。因此,WCF Data Service 是非常簡單的 Url 定址,與 xml 純文字的回傳資料,再加上 HTTP 的 Verbs,即可完成簡單的操作。跨平台,是最大的賣點。看一下 oData SDK,各式各樣的平台與技術都被支援哦!

缺點1:查詢大都只能支援 HTTP Method = GET

WCF Data Service要查詢資料目前大都只支援 GET。當有大量資料要上傳到伺服器時,由於 HTTP GET 有 Url的長度限制(  通常為 8096 bytes),所以只好轉用 POST。但 WCF Data Service Client Library 支援 POST 的能力很差哦!連呼叫都必須使用 HttpWebRequestWebClient 來呼叫,相對來說實在很難用。

缺點2:支援LINQ 語法尚差

查詢資料服務 (WCF Data Services),初看之下看來很強悍,但 check LINQ 考量 (WCF Data Services) 中的「不支援的 LINQ 方法」一節,不支援的 LINQ 語法還真多。最常用的 JoinCount竟然也不支援,聲勢立即弱了下來。

缺點3:不適合大量資料上傳到伺服器

由於缺點1的關係,想要傳較多資料到伺服器時,就必須使用 WebInvoke 的 service operation。在Service Operation上,並不能使用類似 WCF Service 的 Stream 來讀取 POST 的資料。原因很簡單,因為 Stream 並無法自動發佈成 metadata。因此,如果要讀POST 的內容,必須使用 ASP.NET 的 HttpContext.Current.Request.Form 來讀取。因此程式碼就完全相依服 ASP.NET了。之後有空,就寫另一篇來介紹這個作法。

缺點4:只能跑 Http,不能跑 binary

RESTful 使用了 Url 來定址,新刪修查分別對應 POST, DELETE, PUT,GET等 HTTP Method(Verbs),所在只能跑 http 的 protocal,無法跑 binary 來傳資料。傳輸資料量變大後,效能就會比較差。幸好, IIS 有動態壓縮的功能,可以有效減少傳輸的資料量。

結論

看起來好像缺點比優點多一些,但是,跨平台卻是相當大的優勢。未來是 HTML5的世界,WCF Data Service 一定大有可為。

安裝 HTML5 Support for Visual Studio 2010

HTML5 Support for Visual Studio 2010

微軟的 Visual Studio 2010 終於支援 HTML5 的編輯了。看了Announcing the Web Standards Update - HTML5 Support for the Visual Studio 2010 Editor,就下載了 Visual Studio Web Standards Update

問題

怪事發生了,一旦執行 Web Standards Update.msi,就會出現 Service pack 1 for visual studio 2010 must be installed 的警告訊息。我的確有安裝 SP1 for VS2010 啊!

SNAGHTML2ac48c

解決

如果有安裝 Windows SDK 的話,可從修改 msi 檔著手。以我的機器為例,在 C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\ 下有個 Orca.msi,安裝後,執行 Orca。

image

打開 Web Standard Update.msi 後, 在左方 Tables 內選 CustomAction, 在 VSDCA_VsdLaunchConditions 上按右鍵 ,執行 Drop Row功能。

SNAGHTML3205d8

記得按儲存鍵。

image

執行

由於我們修改了 msi,數位簽章就失效了,執行時會出現如下圖的警告訊息,這是正常的。按「Run」以繼續執行。

SNAGHTML34ad88

然後,就可以安裝了。

SNAGHTML35d9c4

SNAGHTML364f42

執行 Visual Studio 2010.建立一個新的 Web Application後,可以選擇 HTML5 來檢查格式。

image

當我們打入 <vi時,會出現 video 的 intellisense (智能提示)。果然好用。

image

參考

我將修改好的 msi 放在這裡,有需要的人可以自行下載。

2011年6月22日 星期三

WPF的UI更新方式

緣由

在以往的 VB6,或者是 Windows Form 應用程式中,更新 UI的方式極為簡單,往往只是 Application.DoEvents 就可以更新。Windows Form 中更有 InvokeBeginInvoke 可以彈性地使用。

那在 WPF 中,要如何更新 UI 的內容呢?

範例1:Bad Example

當然,要從一個不正確的範例開始。

Ex1Bad.xaml

<Window x:Class="WpfApplication10.Ex1Bad"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ex1Bad" Height="300" Width="300">
  <StackPanel>
    <Label Name="lblMsg" Content="Nothing" />
    <Button Content="Start" Name="btnStart" Click="btnStart_Click"/>
  </StackPanel>
</Window>

Ex1Bad.xaml.cs

using System.Threading;
using System.Windows;

namespace WpfApplication10
{
  public partial class Ex1Bad : Window
  {
    public Ex1Bad()
    {
      InitializeComponent();
    }

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      lblMsg.Content = "Starting...";
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Doing...";
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Finished...";
    }
  }
}

這裡以 Thread.Sleep(3000)讓 UI Thread 睡個3秒鐘,來模擬長時間的工作。

這是個常見的程式碼,但卻是沒有用。在 Windows Form 的 API 中有 Application.DoEvents() 可以呼叫。WPF 中沒有類似的嗎?

範例2:使用Windows Form的 DoEvents

原來,WPF 中雖然沒有類似的 api 可以呼叫,但仍可以直接呼叫 Windows Form 的 Application.DoEvents.當然,需要引用 System.Windows.Forms.dll。

Ex2WinformDoEvents.xaml

using System.Threading;
using System.Windows;
using swf = System.Windows.Forms;

namespace WpfApplication10
{
  public partial class Ex2WinformDoEvents : Window
  {
    public Ex2WinformDoEvents()
    {
      InitializeComponent();
    }

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      lblMsg.Content = "Starting...";
      swf.Application.DoEvents();
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Doing...";
      swf.Application.DoEvents();
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Finished...";
    }
  }
}

在更新UI後,呼叫 swf.Application.DoEvents(),就可以更新 UI 了。這樣的方式與之前的 VB6是一模一樣的手法。

範例3:WPF DoEvents

哦?WPF 沒有 DoEvents 可以使用,只能呼叫老前輩Windows Form 的 API 嗎?也不是。在 DispacherFrame 文章中就有sample.

Ex3WPFDoEvents.xaml.cs

using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace WpfApplication10
{
  public partial class Ex3WPFDoEvents : Window
  {
    public Ex3WPFDoEvents()
    {
      InitializeComponent();
    }

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      lblMsg.Content = "Starting...";
      DoEvents();
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Doing...";
      DoEvents();
      Thread.Sleep(3000); //執行長時間工作
      lblMsg.Content = "Finished...";
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public void DoEvents()
    {
      var frame = new DispatcherFrame();
      Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
          new DispatcherOperationCallback(ExitFrame), frame);
      Dispatcher.PushFrame(frame);
    }

    public object ExitFrame(object f)
    {
      ((DispatcherFrame)f).Continue = false;

      return null;
    }

    public static void DoEvents2()
    {
      Action action = delegate { };
      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Input, action);
    }
  }
}

DoEvents() 與 DoEvents2() 的效果相同。

DoEvents is Evil

DoEvents 這麼好用,為什麼WPF還要發明 Dispatcher,或 Windows Form 的 BeginInvoke 這些 API  呢?

跑以下的程式碼看看吧。

private void btnEvil_Click(object sender, RoutedEventArgs e)
{
  for (int i = 0; i < 10000000; i++)
  {
    System.Windows.Forms.Application.DoEvents();
  }
  MessageBox.Show("Ok");
}

執行時,記得打開工作管理員看看CPU 的負載,會持續飆高一斷時間。雖然 UI 沒有任何的更新,為何 CPU 會飆高呢?

DoEvent 的原理是execution loop,也就是以迴圈的方式來查詢是否有要更新的訊息(message)。一看到迴圈,各位看倌就知道是怎麼回事了吧。

範例3中的WPF 的 DoEvents 也是相同的道理。

範例4:BackgroundWorker

有沒有較正常的方式來更新 UI 呢?看一下Ex1Bad.xaml.cs的設計方式吧。更新lblMessage後執行一段工作,這基本上是同步的寫作方式。在 UI Thread 上執行工作,本來就會使得 UI 停頓,使用者感到不方變。

正確的方式,是使用BackgroundWorker來執行長時間的工作,並以非同步的方式更新在 UI Tread 上的UI內容。

Ex4BackgroundWorker.xaml.cs

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication10
{
  public partial class Ex4BackgroundWorker : Window
  {
    public Ex4BackgroundWorker()
    {
      InitializeComponent();
    }

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      ExecuteLongTimeWork(lblMsg, "Starting");
      ExecuteLongTimeWork(lblMsg, "Doing");
      ExecuteLongTimeWork(lblMsg, "Finished");
    }

    private void ExecuteLongTimeWork(Label label, string message)
    {
      var backgroundWorker = new BackgroundWorker();
      backgroundWorker.DoWork += (s, o) => {
        Thread.Sleep(3000); //執行長時間工作
      };

      backgroundWorker.RunWorkerCompleted += (s, args) =>
                                               {
                                                 Dispatcher.BeginInvoke(new Action(() =>
                                                 {
                                                   label.Content = message;
                                                 }));
                                               };
      backgroundWorker.RunWorkerAsync();
    }
  }
}

BackgroundWorker 工作方式,是建立一個新的 Thread 來執行 DoWork 的event handler,執行完畢後,再執行 RunWorkerCompleted 的event handler。因此,我們需要的是在RunWorkerCompleted 的event handler 中更新的 UI。

更新 UI 時,又使用 Dispatcher 來更新 UI。基本上,Dispatcher 是一個 Queue 的概念,凡是使用 Dispatcher 來更新 UI,WPF 會照 DispatcherPriority 的優先順序來更新 UI。

結論

雖然只是小小的UI 更新方式,也是有不少的學問呢!

2011年6月17日 星期五

翻譯錯誤又一件

http://msdn.microsoft.com/zh-tw/library/system.windows.threading.dispatcherframe.aspx

execution (執行) 怎麼會翻譯成例外(exception) 呢?機器翻譯時不會翻錯字,這明顯地不是機器翻譯的,而是人去翻譯的。

image

結論

沒有人會故意犯錯,但只要是人,就很可能犯錯。(當然包括我)

可以的話,還是看原文比較好。雖然較吃力,但至少不會被誤導而不自知。

2011年6月8日 星期三

WCF Data Service 在 PUT 時發生 405 Method Not Allowed

原因

最近在使用 WCF Data Services 時,WCF Data Services Client library 使用 DELETE HttpMethod 時,發生了 HTTP Error 405.0 - Method Not Allowed 的錯誤。

SNAGHTML1d31d0e

HTTP Error 405.0 - Method Not Allowed

The page you are looking for cannot be displayed because an invalid method (HTTP verb) is being used.

解決

經過不少的測試,也試過移除IIS 7 的 WebDAV module,都不成功。最簡單的方法,是在 Web.Config 中增加下面的設定。感謝 IIS 7。

<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
  <remove name="WebDAVModule" />
</modules>
<handlers>
  <remove name="WebDAV" />
</handlers>
</system.webServer>

 

參考

打開 IIS 7 的壓縮功能

緣由

近來幾乎所有的 Middle Tier 的功能,微軟都是使用 IIS 來作 Host,最有名的就是WCF。如果要建置跨平台的解決方案,使用 xml + Http 幾乎是不二人選,binary 的 protocal 就把它忘了吧。

然而, xml + http 為人最為垢病的地方,就是它實在痴肥。為了讓它減肥,我不得已來 study 如何在 IIS 7 上壓縮網頁的輸出。

如下圖,我呼叫叫了一個 WebService,回傳的是 xml,但大小竟然是 1.5 MB!!這叫我如何能接受?

image

過程

Step1: 安裝Dynamic Content Compression

說實話,我已在 IIS 6 上實作過了。IIS 6 的過程實在不人道,我也不想 POST 到部落格上。而 IIS 7 就簡單許多。

首先,要了解的是 IIS 7  預設就打開了靜態內容的壓縮。不像 IIS 6預設都不壓縮。這大概與 IIS 壓縮的成熟度有關吧。

打開 IIS 7, 點到 Compression 的功能,右方出現一排字「The dynamic content compression module is not installed」,意思就是沒有安裝 dynamic content compression 這一個 module。

imageimage

使用 Programs and Features,執行左方 Menu  的 Turn Windows features on or off,並勾選「Dynamic Content Compression」,按OK建進行安裝。

imageSNAGHTML70f490

安裝完成後,再到 IIS 上執行Compress 的設定。check 一下,果然設定成功了。

image

Step 2: 設定 全機壓縮

只有安裝 dynamic content compression 模組是不夠的。IIS 此時尚不知道要對什麼動態內容作壓縮,接下來設定的就是:「告訴 IIS 什麼樣的內容要作動態壓縮」

在 IIS 上,點選 machine 名稱,執行「Configuration Editor」。

SNAGHTML82361b

選 Section : system.webServer/httpCompression,並調整「dynamicTypes」

image

按右方 Actions Pane 的 Add 連結,並輸入 application/atom+xml

image

記得完成後,要按右上的 Apply 鍵完成設定。

最後,還要重新起動 IIS

SNAGHTML8e7694

結果

再使用Fiddler 測試一下相同的網頁。Oh, My God, 1.5MB的內容壓縮過後只剩 149 KB,壓縮成原來的 10% ,省下的頻寬實在可觀。

image

2011年6月2日 星期四

TFS 的 Web 自動部署

繼上次的 TFS 建置:Coded UI 自動測試 問題後,好久好久我都沒再碰 TFS 的 Build 設定了。

這次,目的在建立 Web 的自動部署。

環境

這次的環境較為複雜。有開發機,TFS Server,Build Server,Test WebServer.

希望可以達成的需求:在開發機上要求一個 Build後,TFS Server 向 Build Server 要求啟動一個 Build。Build Server 完成 Compile 後,將 Web Application 自動部署到 Test WebServer。

過程還有些漫長,最大的原因還是「太久遠的記憶」是不可靠的。下面是我試出來的結果。

Build Definition

在 MSBuild Arguments 我設定如下

/p:DeployOnBuild=True
/p:DeployTarget=MsDeployPublish
/p:MSDeployPublishMethod=RemoteAgent
/p:CreatePackageOnPublish=True
/p:DeployIisAppPath="Default Web Site/YourApp"
/p:MsDeployServiceUrl=http://172.199.199.199/MsDeployAgentService
/p:username=administrator
/p:password=adminpassword

image

 

Build Server

Build Server 上,必須安裝了 Visual Studio 2010。我試的結果,不裝,就不會自動部署。我不知道到底我錯過了什麼環結,雖然設定了上述的 MSBuild Arguments,但就是不跑 Deploy。

安裝時,我選了 c# 與Visual Web Developer。理論上只需安裝 Visual Web Developer即可,但我不想再浪費時間試了。

image

另外,在 Visual Studio 2010 Web Application 的專案屬性上,也有一些玄機。如果有勾選「Include IIS settings as configured in IIS」或「Include application pool settings used by this Web project」的話,在 Build Server 就要安裝 IIS 並且有相同的設定。因為當 Build Server 要打包 package 時,需要讀IIS的設定。讀不到設定就會出現下面的錯誤訊息。

C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets (1657): "MapUriToIisWebServer" 工作發生未預期的失敗。 System.Runtime.InteropServices.COMException (0x80005000): 未知的錯誤 (0x80005000)  

image

此時的我,顧不得完美了,先不要勾選這兩個選項了。

Test Web Server

1 在待部署的 Web Server 上,必須安裝 Web Deployment Tool。安裝時,必須注意要選「完整」安裝。

2 安裝完畢後,需要在服務中啟動 Web Development Agent Service

image

3  Build 執行後出現下面的錯誤

Web deployment task failed.((2011/6/2 下午 05:14:42) An error occurred when the request was processed on the remote computer.)  (2011/6/2 下午 05:14:42) An error occurred when the request was processed on the remote computer. The application pool that you are trying to use has the 'managedRuntimeVersion' property set to 'v2.0'. This application requires 'v4.0'.

由於我沒有選擇要打包 IIS 的設定,部署後的應用程式是下 Default Web Site 下。Default Web Site 預設是.net framework v2.0, MSDeploy 在建立 application 時會預設同上一層的 application pool。

我的解法是直接將上一層的 Default Web Site 改用 v4.0 的 application pool。

結論

上述設定,一定還有調整優化的空間。之後有空再寫 blog好了。

雖然很麻煩,但想到之後不必再手動部署測試環境時,心裡也就舒坦了許多。

參考

危險的 HTTP method 測試

緣故

今天早上客戶寄來一封資安檢測結果,說我們維護的Web系統有漏洞。

弱點大意是:Web Server 提供了 PUT, TRACE, DELETE, CONNECT 的 Http Method. 應該關閉。

並且把這項缺失列為中等風險,寫的很厲害的樣子。「PUT method 允許用戶端上傳檔案至伺服器, DELETE method 允許用戶端將伺服器的檔案刪除. 將允許遠端攻擊者可任意上傳檔案至伺服器而置換網頁, 若攻擊者上傳惡意程式到具有執行權限的目錄, 將可達到更大的破壞.」

過程

記得我們沒有開 WebDAV 啊!為什麼寫的這麼離譜?使用 Fiddler 查一下吧!

使用 Fiddler 的 Request Builder 查開發機 IIS

image

再選到 Inspectors,看查詢的 Request 及 Response (結果)
OPTIONS 會列出該 Web Server 可使用的 Http Method.

image

結果列出來,只有一項 TRACE 是有爭議的。根據 RFC 2616 指出,這是用來 Debug 用的 Method,可能揭露出不必要的資訊。因此會被列為風險。在 Apache 上更可能被偷取 cookie

IIS

為什麼在IIS 上 TRACE 預設是可以使用的呢?再使用 Fiddler,這次使用 TRACE 查一下吧。

image

image

這次的結果是 HTTP/1.1 501 Not Implemented (未實作)。HttpStatus 500 以上代表伺服器錯誤。

雖然有開放 TRACE 這個 Method,但一律回未實作。這妙吧。
因此,沒有漏洞。

再使用 DELETE 查一下。結果是 405 Not allowed(不允許)。這證明 IIS 上的WebDAV 果然沒有開啟。

image

結論

一看到資安檢測結果時,雖然只有這一項目,還是心裡不舒服。

看來該資安廠商是使用工具掃出來的結果。雖然只有一個 TRACE 看來符合,但把整個 PUT, DELETE, TRACE, CONNECT 全部列上去,看來比較有「價值」。

另外, IIS 為什麼要列為可用的Method,然後再回應 501 未實作呢?這裡就不深究原因了。

2011年6月1日 星期三

Enterprise Library on NuGet

NuGet 也可以直接安裝 Enterprise Library 了。

問題

舉例來說,之前在使用 Enterprise Library Exception Handling Application Block 時,需要在專案上加入下列參考

Microsoft.Practices.EnterpriseLibrary.Common
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling

沒有經驗的人,很難一次引用正確。

解決方式:NuGet

Add Library Package Reference

image

安裝指定的 package

image

完成!還比想像中多了一些 dll,這些 unity 在用的 dll,什麼時候會用到我也不知道。

image

可惜的是,引用 package 並不會直接產生 .config 的設定。原因很簡單,它不知道我們要設定些什麼。

Enterprise Library 的設定太靈活了,要設定的話,還是乖乖地使用 Enterprise Library Configuration Editor 來設定吧。

Share with Facebook