程式碼高𠅙

顯示具有 c# 標籤的文章。 顯示所有文章
顯示具有 c# 標籤的文章。 顯示所有文章

2009/09/15

WinForm: 如何判斷表單是否開啟在 Visual Studio 的 Designer 中

最近工作上用到比較多微軟的開發工具,像是 Visual Studio, Reporting Servoces, ASP.NET 等。

我們採用了一套 UI Framework,可以在 ASP.NET 上,以 WinForm 的程式設計模式來開發 Web 表單。在表單開發上,可完全免除自行撰寫 Client 端 JavaScript 的需要。簡言之,這種開發模式具有 WinForm 的優點,相對的,開發 WinForm 時所需要面對的問題,這裡同樣要面對。

例如,在開發 WinForm 表單時,我們常會在元件的 Load 事件載入資料來源,進行資料綁定。在執行環境這樣的邏輯沒有問題,但在開發環境,以 Visual Studio 的 Designer 開啟元件時,就可能會遇到無法讀取設定檔、資料連結錯誤等等問題,而造成 Designer 無法順利開啟表單。

我查詢了一下,在 MSDN Windows Developer Center 上也有人詢問這個問題,其中有個開發者已經給了一個可用的 solution,但因為他的描述過於複雜,且要求改寫基底類別,所以我擷取了他的解法,加以簡化。

首先,我們撰寫一個 GuiUtil 類別,其中包括下列方法:

public static bool InDesignMode() 
{
    try 
    { 
        return System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv";
    }
    catch
    {
        return false;
    }
}

如此一來,我們就可以採用以下的程式碼,在 Client 端程式的 Load 事件中進行呼叫,而不會產生設計畫面出不來的狀況了。

if (!GuiUtil.InDesignMode())
{
        InitDataBinding();
}

-- 完 --

2009/04/23

C # 裡面 ?? (雙問號)運算子的妙用

?? 運算子是在 2.0 版後新增的,至今我在別人的程式中還很少看到有在使用。
它的使用方式如下:

a = b ?? c;

上式的意思是,如果 b != null,那就把 b 的值指定給 a,否則就把 c 的值指定給 a。
照理講,?? 的功能完全可以由 ?: 運算子替代,如上式可以改寫如下:

a = b != null ? b : c;

不過,?? 對於簡化程式碼很有幫助,我相信許多 one liner 會很喜歡它,例如:

if (Session["WEB_TYPE"] == null)
{
Session.Add("WEB_TYPE", "");
}

可以改寫為:

Session["WEB_TYPE"] = Session["WEB_TYPE"] as string ?? "";

?? 的另一個特點是使用上可以串連,例如:

string a = null;
string b = null;
string c = "c";
string d = a ?? b ?? c;

這樣的意思,也就是由左到右,找到第一個非 null 值的變數,指定給 d。

於是,如果有一段程式如下 (抓取系統中的舊程式碼舉個例子):

if (Session["WEB_TYPE"] == null)
{
Session.Add("WEB_TYPE", "");
}

//接收 Web_type
if (!IsPostBack)
{
if (Request.QueryString["WEB_TYPE"] != null)
{
webtype = Request.QueryString["WEB_TYPE"].ToString();
Session["WEB_TYPE"] = webtype;
}
}

可以用 ?? 運算子改寫成以下版本:

Session["WEB_TYPE"] = Session["WEB_TYPE"] as string ?? Request.QueryString["WEB_TYPE"] ?? "";

這樣程式就精簡許多,也更好理解與維護了。

2009/02/24

如何取得 Web 應用程式的根目錄 Context URL

我們常常在開發 Web 應用程式時,會有需要取得 Context URL。例如,為了透過這個 URL 取得某些資源。Context URL,也就是 Web 應用程式根目錄的 URL。例如,給定一個 URL 如下:

    http://test.com:8080/AppContext/path/to/your/page

則 Context URL 指的是:

    http://test.com:8080/AppContext   

如果我們都是以相對路徑 (相對於根目錄或或目前路徑) 的方式來存取資源,那直接使用 Context URL 的必要性就比較低了。不過有時候並無法使用相對路徑,例如:

  • 因為配合 API 的呼叫,你不曉得這個 API 會在虛擬路徑的第幾層呼叫,因此就無法提供相對路徑。
  • 因為配合某種 code gen 機制,你要傳入一個 URL。
  • 因為採用了某種 URL dispatch 的機制,導致你網頁相對路徑發生偏差。

當然還有其他原因。而面對這個問題的解法,傳統上是在 web.config 或 web.xml 裡面去定義一個類似 HOST_URL 的參數來解決。但缺點是,每次 deploy 時,就得變更 HOST_URL 的設定,除了顯得不便之外,因會增加 API 與環境變數的相依性,實在不能算是很好的做法。因此透過程式自動抓取 ContextURL 的作法,便有其必要。

現在,我們假設我們的 ContextURL 就是以上的形式,而不是 WebServer 的根目錄 (例如 http://test.com:8080/),那麼取得 Context URL 的作法,在一般情況下並不難。首先看看 ASP.NET/C# 版本:

   int idx = Request.Url.AbsoluteUri.IndexOf(Request.ApplicationPath);
   string serviceUri = Request.Url.AbsoluteUri.Substring(0, idx); // 伺服器 URL
   string applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL

JSP/Java 版本如下:

   int idx = request.getRequestURL().toString().indexOf(request.getContextPath());
   String serviceUri = request.getRequestURL().substring(0, idx); // 伺服器 URL
   String applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL

不過有些情況比較特殊,例果透過某種 URL dispatch 的機制來存取網頁,你會發現用以上方式取得的 URL,與 Client 端發送的 URL 並不相同。我們以 IBM Tivoli Access Manager 為例,它的 URL 組成格式如下:

      http://sso.com/JUNCTION/AppContext/path/to/your/page
  • sso.com: 是一個用來作 singal sign-on 的網站
  • JUNCTION: 用來試別要將 client 端 request dispatch 到後端哪個實發處理的網站
  • /AppContext/path/to/your/page: 後端處理要求的網頁或程式,實際位址可能是 http://192.168.1.111:8080 /AppContext/path/to/your/page 之類的。

也就是說,當 Client 端發送 http://sso.com/JUNCTION/AppContext/path/to/your/page 這個請求時,經過 Tivoli Access Manager 轉送後,Server 端實際取得的服務位址是 http://192.168.1.111:8080/AppContext/path/to/your/page。

為了解決這個問題,我們需要動用到 HTTP Header 裡面的一個參數 -- referer。referer 是指目前存取這個網頁,是透過哪個網頁連結過來的。有了這層認識,我們可以將上面的程式碼改成如下。 ASP.NET/C# 版本:

   if(IsPostBack) {
      idx = Request.UrlReferrer.AbsoluteUri.IndexOf(Request.ApplicationPath);
      serviceUri = Request.UrlReferrer.AbsoluteUri.Substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL
   }

JSP/Java 版本如下:

   if(request.getParameter("IsPostBack").equals("true")){
      idx = request.getHeader("referer").indexOf(request.getContextPath());
      serviceUri = request.getHeader("referer").substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL
   }

如果你是直接在瀏覽器的網址列輸入 URL,或是點選書籤/我的最愛直接連結,那 referer 的值會是 null。因此,我們可以透過檢查這個網頁是否是 post back 網頁,來確保 referer 有值。注意一下,JSP 裡面並沒有預設的 IsPostBack 檢查機制,你要自己做。

另外,有些防火牆或防毒軟體也可能會改寫 Client 端發出的 referer,使得 Server 上取得的值為 null。若是這種情況,可以試著透過 client 端的 Javascript,取得 document.location.href 的值,將它指定給某個隱藏表單欄位,在 post 時傳給 Server 端來加以解決。

2008/11/11

Google Translate 非官方翻譯 API

之前為了將系統的多語化,其中一個議題就是要產生各種語系的訊息資源檔。繁簡中文的對譯,只要透過 Word 就行了,要是英日語,就得人工作業。靈機一動,想說何不借助既有的翻譯服務,為 resource 檔提供預設的簡中、英、日翻譯。

就這樣,找到了一些應用 Google Translate API 的討論。試用了之後,多少都有語系編碼的問題。我最後實作了一個版本,可以在輸入正體中文時,正常產出簡中、英、日語系的翻譯。

一開始我是採用 C# 語言測試,程式碼如下:

public static string TranslateText(string fromLang, string toLang, string msg)
{
  string target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&text={2}&langpair={0}|{1}";
  string url = String.Format(target, fromLang, toLang, msg);
  WebClient webClient = new WebClient();
  webClient.Encoding = System.Text.Encoding.UTF8;
  string result = webClient.DownloadString(url);
  string sign = "<div id=result_box dir=\"ltr\">";
  result = result.Substring(result.IndexOf(sign) + sign.Length);
  result = result.Substring(0, result.IndexOf("</div"));
  return result.Replace("<br>", "\n");;
}

後來又做了一個 Java 的版本,程式碼如下:

public static String translateText(String fromLang, String toLang, String msg) throws IOException{
  String target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&langpair=%s|%s&text=%s";
  String url = String.format(target, fromLang, toLang, URLEncoder.encode(msg, "utf-8"));
  String result = getUrlContent(url);
  String sign = "<div id=result_box dir=\"ltr\">";
  result = result.substring(result.indexOf(sign) + sign.length());
  result = result.substring(0, result.indexOf("</div"));
  return result.replaceAll("<br>", "\n");
}

public static String getUrlContent(String url) throws IOException {
  URL u = new URL(url);   
  HttpURLConnection conn = (HttpURLConnection)u.openConnection();   
  conn.setRequestProperty("User-agent","Mozilla/4.0");
  conn.setRequestProperty("Content-Language","UTF-8" );
  conn.connect();
  return slurp(conn.getInputStream());            
}

public static String slurp (InputStream in) throws IOException {
  StringBuffer out = new StringBuffer();
  byte[] b = new byte[4096];
  for (int n; (n = in.read(b)) != -1;) {
    out.append(new String(b, 0, n, "UTF-8"));
  }
  return out.toString();
}

測試程式長得像這樣:

public static void main(String []argv) throws IOException{
  System.out.println(GoogleTranslate.translateText("zh-TW", "zh-CN", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "en", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "ja", "書不同文、車不同軌、世界不大同!"));          
}

稍微說明一下我的測試心得。

首先,就是使用 Google Translate 時,最好是明確指定你所用的輸入編碼及輸出編碼,即上面 URL 連結中的 ie=UTF-8&oe=UTF-8。若不明確指定,Google 可是會依據你的輸入語言,而決定輸出編碼。例如,你輸入的語言是 "zh-CN",它預設會採用 gb2312 編碼回應。這會造成處理上的困擾。

其次,由於 .NET 與 Java 平台內建皆採用 UTF-8 編碼,因此在我們指定輸入編碼及輸出編碼時,當然是優先考量 UTF-8。

最後,如果你用 Eclipse 做測試,你可能會發現 console 裡有部分亂碼。請依照下圖,在 Run/Run Configurations... 裡面做設定,將 Console Encoding 指定為 UTF-8:

eclipse-console-utf8

最後就能產出正常的顯示了。

eclipse-console-utf8-2