擴展 XSD 處理
為了自定義處理過(guò)程,我需要將信息傳遞給該工具,以便它知道要更改或處理的內容。此時(shí)有兩種主要選擇:
• 向 XSD 根 元素添加可被我的處理器理解的特性(可能添加很多),以便應用自定義,這種方法類(lèi)似于類(lèi)型化數據集方法。 單擊此處可獲得更多相關(guān)信息。
• 通過(guò)架構注釋使用內置的 XSD 可擴展性,以便任意進(jìn)行自定義。它只需向某種代碼生成管線(xiàn)中添加類(lèi)型,即可在基本生成發(fā)生后執行。
第一種方法最初可能很有吸引力,因為它非常簡(jiǎn)單。我只需添加一個(gè)特性,然后相應地修改處理器以檢查該特性:
架構:
<xs:schema elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:code="http://weblogs.asp.net/cazzu" code:fieldsToProperties="true">
代碼:
XmlSchema xsd;
// Load the XmlSchema.
...
foreach (XmlAttribute attr in xsd.UnhandledAttributes)
{
if (attr.NamespaceURI == "http://weblogs.asp.net/cazzu")
{
switch (attr.LocalName)
{
case "fieldsToProperties":
if (bool.Parse(attr.value)) ConvertFieldsToProperties(ns);
break;
...
}
}
}
這正是您通常會(huì )在其他從 xsd 到類(lèi)的生成器中看到的方法(您可以在 Code Generation Network 中找到大量類(lèi)似的生成器)。遺憾的是,該方法將導致長(cháng)長(cháng)的 switch 語(yǔ)句、無(wú)盡的特性,并最終導致代碼難以維護并缺乏可擴展性。
第二種方法更為健壯,因為它從一開(kāi)始就考慮了可擴展性。XSD 通過(guò) 元素提供此類(lèi)擴展工具,該元素可以是架構中幾乎所有項目的子元素。我將利用該元素及其 子元素,以便使開(kāi)發(fā)人員可以指定運行哪些(任意)擴展以及按什么順序運行。這樣的擴展架構將如下所示:
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:annotation> <xs:appinfo> <Code xmlns="http://weblogs.asp.net/cazzu"> <Extension Type="XsdGenerator.Extensions.FieldsToPropertiesExtension, XsdGenerator.CustomTool" /> </Code> </xs:appinfo> </xs:annotation>
當然,每個(gè)擴展都將需要實(shí)現一個(gè)公共接口,以便自定義工具可以輕松地執行各個(gè)擴展:
public interface ICodeExtension { void Process( System.CodeDom.CodeNamespace code, System.Xml.Schema.XmlSchema schema ); }
通過(guò)預先提供此類(lèi)可擴展性,當產(chǎn)生新的自定義需要時(shí),就可以很容易地進(jìn)行其他自定義。甚至還可以從一開(kāi)始就將最基本的代碼實(shí)現為擴展。
可擴展的代碼生成工具
我將修改 Processor 類(lèi)以添加這種新功能,并且將簡(jiǎn)單地從架構中檢索各個(gè) 元素。盡管如此,這里還需要提出一個(gè)警告:與那些為元素、特性、類(lèi)型等公開(kāi)的 Post Schema Compilation Infoset 屬性不同,在架構級別沒(méi)有針對注釋的類(lèi)型化屬性。也就是說(shuō),沒(méi)有 XmlSchema.Annotations 屬性。因此,需要對 XmlSchema.Items 的一般性預編譯屬性進(jìn)行迭代,以便查找注釋。而且,在檢測到 XmlSchemaAnnotation 項目之后,再次需要對其自己的 Items 一般性集合進(jìn)行迭代,這是因為除了 子元素以外,還可能有 子元素,而它也缺少類(lèi)型化屬性。當最終通過(guò) XmlSchemaAppInfo.Markup 屬性獲得 appinfo 的內容之后,我們所得到的全部?jì)热菔且粋€(gè) XmlNode 對象數組。您可以想像如何進(jìn)行后續處理:對節點(diǎn)進(jìn)行迭代,再對其子元素進(jìn)行迭代,等等。這將產(chǎn)生非常丑陋的代碼。
值得慶幸的是,XSD 文件只是一個(gè) XML 文件,因此可以使用 XPath 來(lái)對其進(jìn)行查詢(xún)。
為了提高執行速度,我將在 Processor 類(lèi)中保留 XPath 的靜態(tài)編譯表達式,它將在其靜態(tài)構造函數中進(jìn)行初始化:
public sealed class Processor
{
public const string ExtensionNamespace = "http://weblogs.asp.net/cazzu";
private static XPathExpression Extensions;
static Processor()
{
XPathNavigator nav = new XmlDocument().CreateNavigator();
// Select all extension types.
Extensions = nav.Compile
("/xs:schema/xs:annotation/xs:appinfo/kzu:Code/kzu:Extension/@Type");
// Create and set namespace resolution context.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("xs", XmlSchema.Namespace);
nsmgr.AddNamespace("kzu", ExtensionNamespace);
Extensions.SetContext(nsmgr);
}
注 有關(guān) XPath 預編譯和執行的優(yōu)點(diǎn)、細節和高級應用的更多信息,請參閱 Performant XML (I): Dynamic XPath expressions compilation 和 Performant XML (II): XPath execution tips。
Process() 方法需要在將 CodeNamespace 返回給調用方之前,執行該查詢(xún)并執行它找到的每個(gè) ICodeExtension 類(lèi)型:
XPathNavigator nav;
using ( FileStream fs = new FileStream( xsdFile, FileMode.Open ) )
{ nav = new XPathDocument( fs ).CreateNavigator(); }
XPathNodeIterator it = nav.Select( Extensions );
while ( it.MoveNext() )
{
Type t = Type.GetType( it.Current.value, true );
// Is the type an ICodeExtension?
Type iface = t.GetInterface( typeof( ICodeExtension ).Name );
if (iface == null)
throw new ArgumentException( "Invalid extension type ‘" +
it.Current.value + "‘." );
ICodeExtension ext = ( ICodeExtension ) Activator.CreateInstance( t );
// Run it!
ext.Process( ns, xsd );
}
return ns;
我使用 Type.GetInterface() 而不是 Type.IsAssignableFrom() 來(lái)測試接口實(shí)現情況,因為它能夠快速跳到非托管代碼,所以需要的開(kāi)銷(xiāo)較少。它們的效果是相同的,然而,使用后者將返回一個(gè)布爾值,而不是一個(gè)“類(lèi)型”(如果未找到接口,則返回空值)。
返回頁(yè)首
XmlSerializer 的內部原理
有了 CodeDom 以后,可以為追求自定義的開(kāi)發(fā)人員帶來(lái)大量能力和靈活性,但同時(shí)也帶來(lái)了更大的責任。以這種方式修改代碼會(huì )有危險,因為這會(huì )使代碼不再按與架構兼容的方式進(jìn)行序列化,或者 XmlSerializer 功能被完全破壞,并針對意外的節點(diǎn)和特性引發(fā)異常,從而無(wú)法檢索值,等等。
因此,在處理生成的代碼之前,絕對需要了解 XmlSerializer 的內部原理,當然也就需要一種了解其內部原理的方法。
當對象即將進(jìn)行 XML 序列化時(shí),將通過(guò)反射您傳遞給 XmlSerializer 構造函數的類(lèi)型來(lái)創(chuàng )建一個(gè)臨時(shí)程序集(這就是您需要那么做的原因)。請等一下!不要因為“反射”一詞而感到害怕!這對于每個(gè)類(lèi)型只執行一次,并且在 AppDomain 生命期內,將創(chuàng )建一對極為有效的 Reader 和 Writer 類(lèi)來(lái)處理序列化和反序列化。
這些類(lèi)繼承了 System.Xml.Serialization 命名空間中的 XmlSerializationReader 和 XmlSerializationWriter 公共類(lèi)。它們還是 [TheTopSecretClassName]。如果您希望看一下這些動(dòng)態(tài)生成的類(lèi),您只需向應用程序配置文件(對于 Web 應用程序,為 web.config)中添加以下設置:
<system.diagnostics> <switches> <add name="XmlSerialization.Compilation" value="4"/> </switches> </system.diagnostics>
現在,序列化程序將不會(huì )刪除在該過(guò)程中生成的臨時(shí)文件。對于 Web 應用程序,這些文件將位于 C:\Documents and Settings\[YourMachineName]\ASPNET\Local Settings\Temp 中;或者,它們將位于當前用戶(hù)的 Local Settings\Temp 文件夾中。
您將看到的代碼就是當您希望有效地加載 .NET 中的 XML 時(shí)需要編寫(xiě)的代碼:使用嵌套的 while 和 if 語(yǔ)句進(jìn)行讀取,使用 XmlReader 方法在數據流中向下移動(dòng),等等。使用這些丑陋代碼的目的就是使該處理過(guò)程真正地快起來(lái)。
還可以通過(guò)使用 Chris Sells 的 XmlSerializerPreCompiler 工具來(lái)診斷所生成的這些類(lèi)中的問(wèn)題。
我們可以查看此代碼,以便分析在序列化程序所生成的類(lèi)中進(jìn)行更改的效果。
返回頁(yè)首
通過(guò) CodeDom 自定義
某些自定義能夠立即產(chǎn)生吸引力,因為它們是人們經(jīng)常關(guān)心的與 xsd.exe 工具生成的類(lèi)有關(guān)的問(wèn)題。
將字段轉化為屬性
大多數開(kāi)發(fā)人員抱怨的問(wèn)題之一是,xsd.exe 工具生成的類(lèi)帶有公共字段,而不是由私有字段支持的屬性。XmlSerializer 生成的類(lèi)通過(guò)使用常規的 [object].[member] 注釋來(lái)讀寫(xiě)該類(lèi)的實(shí)例中的值。當然,從編譯和源代碼的角度來(lái)看,[member] 是字段還是屬性沒(méi)有什么區別。
因此借助于 CodeDom,可以更改 XSD 的默認類(lèi)。由于自定義 codegen 工具中內置的可擴展性,需要做的所有工作只是實(shí)現一個(gè)新的 ICodeExtension。該擴展將處理 CodeDom 樹(shù)中的每個(gè)類(lèi)型,而無(wú)論它是一個(gè)類(lèi)還是一個(gè)結構:
public class FieldsToPropertiesExtension : ICodeExtension
{
#region ICodeExtension Members
public void Process( System.CodeDom.CodeNamespace code,
System.Xml.Schema.XmlSchema schema )
{
foreach ( CodeTypeDeclaration type in code.Types )
{
if ( type.IsClass || type.IsStruct )
{
// Turn fields to props
現在,我需要對該類(lèi)型的每個(gè)成員(可能是字段、屬性、方法等等)進(jìn)行迭代,并且只處理 CodeMemberField 成員。不過(guò),我不能只對 type.Members 集合執行 foreach 操作,因為對于每個(gè)字段而言,我都需要向同一集合中添加屬性。這將導致發(fā)生異常,因為 foreach 結構所使用的基礎枚舉數可能會(huì )無(wú)效。因此,我需要將當前成員復制到某個(gè)數組中,然后改為對該數組進(jìn)行迭代:
CodeTypeMember[] members = new CodeTypeMember[type.Members.Count];
type.Members.CopyTo( members, 0 );
foreach ( CodeTypeMember member in members )
{
// Process fields only.
if ( member is CodeMemberField )
{
// Create property
Next, I create the new property:
CodeMemberProperty prop = new CodeMemberProperty();
prop.Name = member.Name;
prop.Attributes = member.Attributes;
prop.Type = ( ( CodeMemberField )member ).Type;
// Copy attributes from field to the property.
prop.CustomAttributes.AddRange( member.CustomAttributes );
member.CustomAttributes.Clear();
// Copy comments from field to the property.
prop.Comments.AddRange( member.Comments );
member.Comments.Clear();
// Modify the field.
member.Attributes = MemberAttributes.Private;
Char[] letters = member.Name.ToCharArray();
letters[0] = Char.ToLower( letters[0] );
member.Name = String.Concat( "_", new string( letters ) );
請注意,我向新的屬性中復制了字段名、它的成員特性以及類(lèi)型。我將注釋和自定義特性(XmlSerialization 特性)移出字段,然后移到屬性(AddRange() 和 Clear())中。最后,我將該字段變?yōu)樗接凶侄?,并將其首字母轉化為小寫(xiě),在它前面加上“_”字符,這對于由屬性支持的字段而言,是一種相當通用的命名規則。
但仍然缺少屬性中最重要的元素:屬性的 get 和 set 訪(fǎng)問(wèn)器的實(shí)現。因為它們只是對字段值進(jìn)行傳遞,所以都非常簡(jiǎn)單:
prop.HasGet = true;
prop.HasSet = true;
// Add get/set statements pointing to field. Generates:
// return this._fieldname;
prop.GetStatements.Add(
new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), member.Name ) ) );
// Generates:
// this._fieldname = value;
prop.SetStatements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), member.Name ),
new CodeArgumentReferenceExpression( "value" ) ) );
最后,我們只需向該類(lèi)型中添加新的屬性:
type.Members.Add( prop );
}
好了,先前的架構通過(guò)該工具生成以下代碼:
/// <remarks/> [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] public class Publisher { /// <remarks/> public string pub_id;
向該架構添加相應的擴展以后:
<xs:schema elementFormDefault="qualified" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:annotation> <xs:appinfo> <Code xmlns="http://weblogs.asp.net/cazzu"> <Extension Type="XsdGenerator.Extensions.FieldsToPropertiesExtension, XsdGenerator.CustomTool" /> </Code> </xs:appinfo> </xs:annotation> ...
該架構現在將生成:
///
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public class Publisher
{
private string _pub_id;
///
public string pub_id
{
get
{
return this._pub_id;
}
set
{
this._pub_id = value;
}
}
使用集合而不是數組
對于任何比較像樣的讀寫(xiě)(具有 get 和 set 屬性)對象模型而言,要使其可供程序員方便地使用,它的多值屬性都應該基于集合,而不是基于數組。這樣做會(huì )使修改值和操縱對象圖變得更為容易。通常的方法涉及到從 CollectionBase 派生一個(gè)新的類(lèi)型化集合類(lèi)。
在將更改提交給 CodeDom 之前,XmlSerializer 支持必須對集合進(jìn)行檢查。在分析和反射要序列化的類(lèi)型的類(lèi)的內部深處,有一個(gè)名為 TypeScope 的內部類(lèi)。TypeScope 負責確保生成序列化代碼。它包含一個(gè)有趣的方法,名為 ImportTypeDesc,該方法執行大多數檢查工作并且為支持的類(lèi)型生成信息。在這里,我們找到了對 IXmlSerializable(它檢查其成員中的安全特性)、數組(必須具有等于 1 的秩)、Enums、XmlNode、XmlAttribute 和 XmlElement 等的特殊支持。
尤其是對集合而言,導入方法檢查實(shí)現 ICollection 的類(lèi)型,該類(lèi)型必須滿(mǎn)足下列規則:
• 必須具有一個(gè) Add 方法,該方法不是由該接口定義的,因為它通常是為該集合將要容納的專(zhuān)用類(lèi)型而創(chuàng )建的。
• 不得通過(guò)該集合實(shí)現 IDictionary。
• 必須具有一個(gè)默認成員(即一個(gè)索引器)并且該成員具有一個(gè)類(lèi)型為 System.Int32 (C# int) 的參數。系統將在所有類(lèi)型層次結構中搜索這樣的成員。
• 在 Add、Count 和索引器中不能有任何安全特性。
在驗證上述信息以后,生成的派生自 XmlSerializationWriter 的專(zhuān)用類(lèi)在為我們的類(lèi)型編寫(xiě) XML 輸出時(shí),將使用 Count 屬性進(jìn)行迭代,而不使用基于數組的屬性的 Lenth:
MyAssembly.MyCollection a = (MyAssembly.MyCollection)o.@CollectionProperty;if (a != null) { for (int ia = 0; ia < a.Count; ia++) { Write10_MyCollectionItem(@"MyCollectionItem", @"http://weblogs.asp.net/cazzu/", ((MyAssembly.MyCollectionItem)a[ia]), false, false); }}
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。