果然沒啥人知道的 code, bug 也會比較慢被抓出來 ... 兩個小時前我才貼了找到 XmlNodeWriter 的替代品,用了一下就被我挖到一個 BUG ... @_@
先來看看我的 Sample Code:
1: // test xml text writer, correct result
2: // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
3: {
4: Console.WriteLine("Using XmlTextWriter:");
5: XmlWriter writer = XmlWriter.Create(Console.Out);
6: writer.WriteStartElement("root");
7: writer.WriteRaw("<a/><a/><a/><a/><a/>");
8: writer.WriteEndElement();
9: writer.Flush();
10: Console.WriteLine();
11: Console.WriteLine();
12: }
13: // test xml node writer, wrong result
14: // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
15: {
16: Console.WriteLine("Using XmlWellFormedWriter:");
17: XmlDocument xmldoc = new XmlDocument();
18: XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
19: writer.WriteStartElement("root");
20: writer.WriteRaw("<a/><a/><a/><a/><a/>");
21: writer.WriteEndElement();
22: writer.Close();
23: xmldoc.Save(Console.Out);
24: Console.WriteLine();
25: Console.WriteLine();
26: }
而這是程式的輸出畫面:
兩段 code 除了拿到的 XmlWriter 來源不同之外,用它寫 XML DATA 的方式是一致的,不過寫出來的 XML 則完全不同。看來兩種 XmlWriter 對於 WriteRaw(...) 的實作不大相同。而照 MSDN 上的說明來說,XmlTextWriter的行為是對的,XmlWellFormedWriter 則太雞婆了,沒事多作一次編碼...
該說運氣好嘛? 哈哈... 繼上次撈到一個 SmtpMail 的 Bug 之後,這次又撈到一個... 要用的人注意一下,不過即使有這個 Bug, 也不會影響它的地位啦,這 Writer 解決了我很大的困擾,動搖國本也要用下去... (咳... 不過是避開一個 API ...)
最後我改了用法,一方面 API 有 BUG 是一回事,另一方面直接用這 API 也很危險,因為 MSDN 說它不會去做內容的驗證,也就是說透過 WriteRaw( ) 寫進不合法的資料,會讓你整份輸出都毀了... 第二個原因比較重要,因此我換了一個替代作法, 類似 Pipe 一樣,把 XmlReader 讀到的東西都寫到 XmlWriter:
1: /// <summary>
2: /// 從 XmlReader 複製到 XmlWriter
3: /// </summary>
4: /// <param name="reader"></param>
5: /// <param name="writer"></param>
6: private static void XmlCopyPipe(XmlReader reader, XmlWriter writer)
7: {
8: if (reader == null)
9: {
10: throw new ArgumentNullException("reader");
11: }
12: if (writer == null)
13: {
14: throw new ArgumentNullException("writer");
15: }
16: while (reader.Read() == true)
17: {
18: switch (reader.NodeType)
19: {
20: case XmlNodeType.Element:
21: writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
22: writer.WriteAttributes(reader, true);
23: if (reader.IsEmptyElement)
24: {
25: writer.WriteEndElement();
26: }
27: break;
28: case XmlNodeType.Text:
29: writer.WriteString(reader.Value);
30: break;
31: case XmlNodeType.Whitespace:
32: case XmlNodeType.SignificantWhitespace:
33: writer.WriteWhitespace(reader.Value);
34: break;
35: case XmlNodeType.CDATA:
36: writer.WriteCData(reader.Value);
37: break;
38: case XmlNodeType.EntityReference:
39: writer.WriteEntityRef(reader.Name);
40: break;
41: case XmlNodeType.XmlDeclaration:
42: case XmlNodeType.ProcessingInstruction:
43: writer.WriteProcessingInstruction(reader.Name, reader.Value);
44: break;
45: case XmlNodeType.DocumentType:
46: writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
47: break;
48: case XmlNodeType.Comment:
49: writer.WriteComment(reader.Value);
50: break;
51: case XmlNodeType.EndElement:
52: writer.WriteFullEndElement();
53: break;
54: }
55: }
56: }
很好用的作法,就像過去需要 COPY XML 資料,最常見的就是把來源跟目的都用 XmlDocument 載入,直接用 ImportNode( ) 把 XML 片段資料搬到另一個 XmlDocument 再儲存。跟上一篇的原因一樣,看起來很蠢... 就想到這個作法,透過 XmlReader, 拿到的是已經 parsing 過的資料,直接寫到 XmlWriter。而我用的 Writer 正好又可避開重複作 parsing 動作的優點,正好這樣效能跟可用性都兼顧了... 經過 parsing, 至少寫出來的東西會安心一點...
把最後我的程式搭配這個 XmlPipeCopy 改一改:
1: XmlDocument xmldoc = new XmlDocument();
2: XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
3: writer.WriteStartElement("root");
4: XmlReaderSettings settings = new XmlReaderSettings();
5: settings.ConformanceLevel = ConformanceLevel.Fragment;
6: XmlReader reader = XmlReader.Create(
7: new StringReader("<a/><a/><a/><a/><a/>"),
8: settings);
9: XmlCopyPipe(reader, writer);
10: writer.WriteEndElement();
11: writer.Close();
12: xmldoc.Save(Console.Out);
試了一下,果然如預期的執行了 :D,結果也沒錯,還好 XmlWellFormedWriter 的 Bug 只存在於 WriteRaw... 閃開就沒事了:
其中有個陷阱,就是如何用 XmlReader 讀取 XmlFragment (可以有多個 ROOT 的 XML DATA)。其實這個解法跟程式碼,大部份都是這篇看來的,只不過在裡面加了個 LOOP 跟改了名字,各位覺的好用的話記得去謝原作者 Mark Fussell, 別謝錯人了 :D
最近事情一堆,上班忙上班的事,下班還在忙著研究 Enterprise Library, Entity Framework, 還有一堆五四三的,文章寫的就少了... 先跟有訂閱我 BLOG 的朋友們說聲道歉...。 不過在寫新專案的過程中,意外的發現這東西,一定要提一下...
不知道有多少人用過 XmlNodeWriter ? 我用這東西用很久了,當年 Microsoft 推出 .NET Framework 時,強調有很強的 XML 處理能力,其中 XmlReader / XmlWriter 就是以效能為考量,讓你避開處理大型 XML 資料效能很糟糕的 XmlDocument, 也不用去碰很難寫的 SAX 的替代方案...
無奈 Microsoft 內建的 XmlWriter 少的可憐,只能寫到檔案或是 TextWriter ... 看看權威的 MSDN 告訴我們有那些 XmlWriter 可以用?
老實說除了 XmlTextWriter 之外,另外兩個很少用的到。XmlWriter 在輸出 XML 時很好用 (如果你只作輸出的話),複雜的 XML 輸出用 XmlWriter 比用 XmlDocument 簡單多了,不過最常碰到的情況是我還是想用 XmlDocument 來操作 XML,不過其中一部份的 NODE 想用 XmlWriter 來更新內容...
古早有位好心的 MVP 寫了 XmlNodeWriter, 就可以讓我這樣用:
1: XmlDocument xdoc = new XmlDocument();
2: xdoc.LoadXml("<root><node1><data/><data/><data/></node1><node2/></root>");
3: XmlNodeWriter xnw = new XmlNodeWriter(xdoc.DocumentElement, true);
4: xnw.WriteStartElement("newNode");
5: xnw.WriteAttributeString("newatt", "123");
6: xnw.WriteCData("1234567890");
7: xnw.WriteEndElement();
8: xnw.Close();
9: xdoc.Save(Console.Out);
第一個參數是 XmlNode, 第二個參數是要不要清掉原來 Node 下的內容。很棒,我可以直接拿 XmlNode 當作 XmlWriter 輸出的對象,透過 Writer 寫出去的東西就直接反映在 XmlNode 身上了,省掉輸出成 Text 然後再 PARSING 回 XML NODE 這種蠢事...
前面只是緬懷 XmlNodeWriter 到底有多好用,現在找不到有多難過而已... 接下來才是正題...
不過現在想再去找 XmlNodeWriter 官方網站已經找不到了 @_@,原本這 lib 是 hosting 在 gotdotnet.com 這網站上,不過 Microsoft 已經把它關了,改成 codeplex.com / MSDN Code Gallery 取代,只好求助 GOOGLE 大神,無意間又發現這 Microsoft XmlTeam 的 BLOG,COMMENTS 有這麼一段:
# re: XML Features in the February CTP of Visual Studio “Orcas”
Friday, February 02, 2007 8:27 PM by Stuart Ballard
Is there going to be an XmlNodeWriter in Orcas? It's a fairly glaring hole, especially if you've ever wanted to apply an XSL transformation to an in-memory XmlDocument and get the result as another in-memory XmlDocument. You can pass the input to the transform via XmlNodeReader, but to get it back out again you just have to feed your XmlWriter to a StringBuilder and then parse it...Fortunately my use case wasn't performance-critical, but it's still ugly...
# re: XML Features in the February CTP of Visual Studio “Orcas”
Saturday, February 03, 2007 3:22 AM by Oleg Tkachenko
Stuart, XmlNodeWriter in .NET 2.0 is hiding inxmlNode.createNavigator().AppendChild() method. It can be used to populate XmlNode via XmlWriter API and so you can
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild()) {
xslt.Transform(input, (XsltArgumentList)null, writer);
}
Mike, am I right that Orcas January CTP includes none of these coolness?
真是太機車了,這麼好用的東西藏在這種地方? @_@,枉我從 .NET 1.0 beta 就開始用 C# 處理 XML,連 XSLT Extension 都寫過一堆, Trace Code 也追到 XSLT 內抓過一堆問題... 竟然連這東西都沒發現? 可惡...
於是手又癢了,拿來試用看看,發現只要動手寫幾行 Code, 我就能把 XmlNodeWriter 變回來了,像這樣:
1: public class XmlNodeWriter : XmlWriter
2: {
3: private XmlWriter _inner_writer = null;
4: public XmlNodeWriter(XmlNode node, bool clean)
5: {
6: if (clean == true)
7: {
8: node.RemoveAll();
9: }
10: this._inner_writer = node.CreateNavigator().AppendChild();
11: }
12: #region 無聊的 "延長線" 程式碼...
13: // 略! 共一百多行,補上廿幾個 abstract method / property, 把它接到 _inner_writer 上
14: #endregion
15: }
這樣果真可以 WORK 了 :D 不過要真正變出一個新的 XmlNodeWriter 代價還不低,繼承 XmlWriter 的後果是有廿幾個 abstract method / property 得補上實作... 全都是很無聊的 code, 就是拿 _inner_writer 的直接套上去而已... 像這樣:
1: public override void Close()
2: {
3: this._inner_writer.Close();
4: }
5: public override void Flush()
6: {
7: this._inner_writer.Flush();
8: }
9: public override string LookupPrefix(string ns)
10: {
11: return this._inner_writer.LookupPrefix(ns);
12: }
這堆 Code 我就不貼了,總之可以 WORK :D
不過對 CODE 有點潔癖的我,越看越不是味道,就動起 Factory 的腦筋了。繼續改造一下... 原本 .NET 2.0 內建的 XmlWriter 就已經提供 Factory 的用法了,像這樣:
XmlWriter my_writer = XmlWriter.Create( ... );
不過沒辦法不改 .NET FX 原始碼的情況下 "加掛" 我自己的 Create(...) 實作,原本腦筋是動到 C# 3.0 開始支援的 Extension Method, 不過它只支援 instance method, 不支援 static method ... 只好改成這樣:
1: public abstract class XmlWriterFactory : XmlWriter
2: {
3: public static XmlWriter Create(XmlNode node)
4: {
5: return Create(node, false, null);
6: }
7: public static XmlWriter Create(XmlNode node, bool clearnContent)
8: {
9: return Create(node, clearnContent, null);
10: }
11: public static XmlWriter Create(XmlNode node, bool cleanContent, XmlWriterSettings settings)
12: {
13: if (node == null) throw new ArgumentNullException("node");
14: if (cleanContent == true)
15: {
16: node.RemoveAll();
17: }
18: XmlWriter xw = node.CreateNavigator().AppendChild();
19: if (settings != null)
20: {
21: xw = XmlWriter.Create(xw, settings);
22: }
23: return xw;
24: }
25: }
沒事還繼承原本的 XmlWriter 只有一個目的,就是要延用它原來的 10 種 Create method 啊... 貼張圖為証,繼承之後我就有 13 種不同的 Create method 可以用... 不用再兩頭跑 (只是不能加在原本的 XmlWriter 上真是殘念, C# 什麼時後會支援 static method extension ?):
當然,原程式只要改掉如何拿到 XmlWriter 那行而已,其它都照舊就可以執行了 :D
有需要的就拿去用吧,CODE 才十幾行,還包成 DLL 實在太麻煩了,需要的直接貼到你自己的 CODE 裡就好! 要散怖都隨便,沒有什麼授權問題,唯一的要求就是讓我知道我的 CODE 你有在用就好 :D,想讚助我的也很簡單,BLOG 上該多點幾下的東西,沒事就點一點... 哈哈
因為工作的關係,最近正在研究 Enterprise Library 裡整合的 Patterns & Practices 介紹的各式 Application Block... 撇開其它的發現,有個東西一定要提一下,就是 Policy Injection ...
介紹文章我就不多說了,一樣網路一大堆,有興趣的可以看 MSDN 官方的說明。比較特別的是它的用法。當年剛開始研究 .NET 內建的 Role Based Security Control,才在讚嘆它的 code 寫起來真漂亮,只要加個 attribute, 就可以在 runtime 自動檢查呼叫時的身份是否滿足 attribute 的宣告,如下:
1: [PrincipalPermissionAttribute(SecurityAction.Demand, Role="Supervisor")]
2: public void Foo() {
3: // ...
4: }
不管你的 code 在那裡,只要呼叫這個 Foo method, 當時的身份 ( principal ) 如果不屬於 "Supervisor" 這個角色的話,就會引發 Security Exception... 當初看到這真是太棒了,我可以用宣告的方式來作安全控制,不需要在主程式裡加一堆囉哩叭唆的 code 來查權限...
不過當我開始研究如何 "自定" 這個行為,除了加上自己的安全機制之外,想更進一步的加上 Log 或是其它的檢查... 我才發現跟本辦不到。因為... 這行為是直接在 CLR 裡支援的啊,我可以加上一堆自定的 Attribute 掛上去,但是呼叫時完全不會觸發我的 code ...
之後研究過 AOP,發現 AOP 正是解決我這類問題的 Solution, 無奈那些 solution 都不大實際,就沒深入研究了。之後找到篇 MSDN 的文章,裡面提到 .NET Remoting 時,遠方會產生 Proxy, 同時 Client / Server 之間的溝通會介著中間傳輸層傳遞 IMessage 介面封裝的 message, 到另一端才會由 Proxy 解讀,然後用 Reflection 還原呼叫的動作... 利用 Proxy 在還原呼叫動作時,你就有機會插入你要的邏輯 (IMessageSink),做到跟上面例子類似的功能。
還是很不實際啊啊啊啊,我沒事也不會去用 .NET Remoting 啊,用不到的話這招對我也沒啥用 (大錯特錯!! 當年的我真是太過自信了 :~~~~) ... 這事就一直擱著了,直到...
最近在研究 Policy Injection Application Block 時,讓我看到了似曾相識的 code:
1: [AuthorizationCallHandler("operation-name")]
2: public void Deposit(decimal depositAmount)
3: {
4: balance += depositAmount;
5: }
這段 CODE 跟前面 CAS 的範例作用差不多,一樣是在 method 被呼叫前作一次權限的檢查。不同的是 AuthorizationCallHandlerAttribute 是自定的 (由 Security Application Block 提供的),它的作用比 ROLE 更進一階,是直接檢查授權的。之間的差別就如同 windows 大家都知道把 USER 加入 Administrators 角色的話,"預設" 就可以做大部份的事,但是你要在某個有 ACL 的物件 (如 NTFS 的檔案) 拒絕 Administrators 的存取也是可行的。前面 CAS 的例子就只是判定你是不是某角色的人,而這例子則是判定某個授權的定義允不允許你執行。
扯遠了,重點不在安全,重點是自定的 Code / Attribute 也可以這樣用啊! 由於我多年心裡的疑惑,挖出這段作法比研究 Policy Injection 更積極一點 (老闆對不起...) 哈哈,沒想到答案就在前文...
它ㄨ的!! 原來只是在 Local 使用 .NET Remoting ...
說穿了不值錢,你用的物件標上 Attribute 後,要透過它的建立方式 ( Create or Wrap ) 取得加料過的物件,再呼叫它就會有你預期的效果了。這加料過的物件,就是 System.Runtime.Remoting.Proxies.RealProxy 下的某類別啊啊啊啊... 意思是我拿這加料過的物件,就會透過 .NET Remoting 的方式去呼叫到我真正的物件,而 Policy Injection Application Block 正好就替我把我要作的動作給補上去...。
雖然心裡有被擺了一道的感覺,不過它的 code 包裝的真漂亮啊... 除了 Create 的方式由原本的 new .ctor( ) 改成它的 Create( ... ) 之外,其它就通通一樣了。更猛的是它還提供了幾個真的很實用的 CallHandler (就是呼叫時會加料的動作啦):
大部份的 Handlers 都望文生義,像是 Logging 就是呼叫時替你加一段 LOG,而 Performance Counter 則是呼叫時就替你戳一下 windows 內建的 performance counter, 讓你可以透過 performance monitor 看相關統計 (如你的 method 被呼叫過幾次... ),更神奇的是 Caching, 如果你的 method 跑的很慢,加上去之後甚至是 cache 裡已經有了上次的結果,這次呼叫就直接 return 了... (你還記得你寫過多少次資料不在 cache 內就 insert 進去的 code 嗎?) @_@
如果你看這篇期望看到啥 Enterprise Library / Policy Injection Application Block 的深入介紹的話,很抱歉... 我還沒那本事,哈哈... 再過陣子研究出心得,可能會寫幾篇吧...。 這類文章如果你不介意看英文的,官方的說明還有 QuickStart 的範例就夠你看了,可以參考看看,我就不獻醜了...。 這篇純粹是為了這 AB 解除了我多年來的遺憾,特地留下篇記念用的... :D
我自己用了快六年的 ThinkPad x31 掛掉了,又沒潑到水,送去 LENOVO 修理,就回我 "液體入侵" ... 換主機板要 NTD 26500 ... 錢太多才會修,因此就跟我姊ㄠ了她已經沒在用的 ThinkPad x40 來用用...
X40 什麼都好,就是敗在它那顆 Hitachi 1.8" HDD 效能實在太爛... 拿到 X40 後就馬上重灌 XP,剛灌好後就用 HDTune 測一下這顆硬碟的鳥效能..
後來也很巧,經過一夜灌了堆必要的軟體跟工具之後,突然喀啦一聲,硬碟就再也不能用了 :~ 上網找找有無硬碟可以買? 還真慘... 都是拆機或是二手,個人保固七天或是一個月的那種。Hitachi也停產了,除了容量有 60GB 的之外也沒別的選擇了。效能很鳥的硬碟,相對的 $$ 也不算便宜,害我考慮了半天...
後來決定用 CF -> IDE 的轉卡,加上忍痛買了張 SanDisk Extreme IV 8GB CF 卡,也就是俗稱 "偽SSD" 的解決方案,裝好後好像完全換了台電腦似的,剛裝好的 XP PRO (原版光碟安裝的,沒有刪掉一堆內建的軟體跟服務),開機的 WINDOWS 光棒,跑不到一輪就進 WINDOWS 了 @_@,真是傻眼,效能的增進遠超過我的預期...
雖然容量小了點,不過效能跟原本的 1.8" HDD 實在差太多了,不足的容量就再補張 16GB SD 卡撐著用。只要不裝啥大型檔案,一般的 OFFICE 文件還不成問題,用起來也還不錯! 原本慢到想扔掉的 X40 就這樣又活了過來 :D
最後補上 SanDisk Extreme IV 8GB 的效能測試圖:
YA! 第四篇!! :D 還是一樣要先感謝一下編輯賞光,讓我有點空間寫些不一樣的東西。
基本的執行緒相關的程式設計跟函式庫,講的差不多了,其實這些也沒什麼好寫的。接下來打算寫一些應用的模式,來談談有那些方法,那些設計方式才能夠有效的發揮多執行緒的優點。看了 .NET Framework 4.0 / Visual Studio 2010 的 ROADMAP,有一大部份的重點擺在平行處理,INTEL年底也要發表四核 + HT 的 CPU ( WINDOWS 會認為有八個處理器 ),軟硬體都備齊了,剩下的就是程式設計師的巧思了。
其實之前貼過幾篇類似主題的文章,只是這次把它統合起來介紹一下。生產線模式,如果簡化後就是 [生產者消費者] 的模式,而把它徹底一點的應用,則是上回提到 [Stream Pipeline] ..
這篇也是第一次在雜誌上嘗試說明比較偏設計概念的文章,實作比較少,很怕不合讀者的口味... 應該不會貼了就沒續篇了吧? :P 有買雜誌的記得讀者回函填一下,哈哈,也算是點鼓勵。這次範例程式也是 Console application (我不會寫太炫的程式 :P ),需要的可以點 [這裡] 下載!