2017-02-17 55 views
15

System.Xml.Serialization.XmlCodeExporter从XSD模式生成代码(以CodeDom代码形式)。但它有一些怪癖。例如一个可选元素:XmlCodeExporter和可空类型

<xs:element name="Something" type="xs:decimal" minOccurs="0" maxOccurs="1"/> 

我希望它可以产生Nullable<decimal>类型的相应的编码元件,但它实际上创建decimal类型的成员,然后单独SomethingSpecified字段,它应当单独触发以表示一个空值。这可能是因为该库来自可引用类型的引入之前,但会导致非常不便的代码。

是否有可能调整此代码生成,或者是否有替代工具在这种情况下生成更好的代码?

编辑:我知道我可以修改架构并添加nillable='true',但我不想更改架构以解决代码生成的限制。

+0

尝试使用Xsd2Code工具。我广泛使用它而不是xsd.exe。不幸的是,我不能确定你提到的具体问题是否解决了。 – Evk

+0

您是否尝试过使用'nillable ='true''而不是最小/最大值? – tchrikch

+0

@Evk:Xsd2Code似乎在底层使用了XmlCodeExporter,似乎也有相同的问题。 – JacquesB

回答

13

该文章 Writing your own XSD.exe 作者Mike Hadlow给出了创建xsd.exe自己版本的基本框架。它有以下几个步骤:

  1. 导入与XmlSchema.Read()XmlSchemaImporter的模式。

  2. 使用XmlCodeExporter生成要创建的.Net类型和属性。

  3. 根据需要调整生成的类型和属性

    在这里,您可能希望删除生成的xxxSpecified属性,并将其相应的“真实”属性提升为空。

  4. 使用CSharpCodeProvider生成最终代码。

利用这个框架,并通过使用调试器实验确定由XmlCodeExporter究竟是生成的类型,我创建了以下CustomXsdCodeGenerator

public class CustomXsdCodeGenerator : CustomXsdCodeGeneratorBase 
{ 
    readonly bool promoteToNullable; 

    public CustomXsdCodeGenerator(string Namespace, bool promoteToNullable) : base(Namespace) 
    { 
     this.promoteToNullable = promoteToNullable; 
    } 

    protected override void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace) 
    { 
     RemoveSpecifiedProperties(codeNamespace, promoteToNullable); 
     base.ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace); 
    } 

    private static void RemoveSpecifiedProperties(CodeNamespace codeNamespace, bool promoteToNullable) 
    { 
     foreach (CodeTypeDeclaration codeType in codeNamespace.Types) 
     { 
      RemoveSpecifiedProperties(codeType, codeNamespace, promoteToNullable); 
     } 
    } 

    private static void RemoveSpecifiedProperties(CodeTypeDeclaration codeType, CodeNamespace codeNamespace, bool promoteToNullable) 
    { 
     var toRemove = new List<CodeTypeMember>(); 

     foreach (var property in codeType.Members.OfType<CodeMemberProperty>()) 
     { 
      CodeMemberField backingField; 
      CodeMemberProperty specifiedProperty; 
      if (!property.TryGetBackingFieldAndSpecifiedProperty(codeType, out backingField, out specifiedProperty)) 
       continue; 
      var specifiedField = specifiedProperty.GetBackingField(codeType); 
      if (specifiedField == null) 
       continue; 
      toRemove.Add(specifiedProperty); 
      toRemove.Add(specifiedField); 

      if (promoteToNullable) 
      { 
       // Do not do this for attributes 
       if (property.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlAttributeAttribute).FullName)) 
        continue; 
       var typeRef = property.Type; 
       if (typeRef.ArrayRank > 0) 
        // An array - not a reference type. 
        continue; 

       // OK, two possibilities here: 
       // 1) The property might reference some system type such as DateTime or decimal 
       // 2) The property might reference some type being defined such as an enum or struct. 

       var type = Type.GetType(typeRef.BaseType); 
       if (type != null) 
       { 
        if (!type.IsClass) 
        { 
         if (type == typeof(Nullable<>)) 
          // Already nullable 
          continue; 
         else if (!type.IsGenericTypeDefinition && (type.IsValueType || type.IsEnum) && Nullable.GetUnderlyingType(type) == null) 
         { 
          var nullableType = typeof(Nullable<>).MakeGenericType(type); 
          var newRefType = new CodeTypeReference(nullableType); 
          property.Type = newRefType; 
          backingField.Type = newRefType; 
         } 
        } 
       } 
       else 
       { 
        var generatedType = codeNamespace.FindCodeType(typeRef); 
        if (generatedType != null) 
        { 
         if (generatedType.IsStruct || generatedType.IsEnum) 
         { 
          var newRefType = new CodeTypeReference(typeof(Nullable<>).FullName, typeRef); 
          property.Type = newRefType; 
          backingField.Type = newRefType; 
         } 
        } 
       } 
      } 
     } 
     foreach (var member in toRemove) 
     { 
      codeType.Members.Remove(member); 
     } 
    } 
} 

public static class CodeNamespaceExtensions 
{ 
    public static CodeTypeDeclaration FindCodeType(this CodeNamespace codeNamespace, CodeTypeReference reference) 
    { 
     if (codeNamespace == null) 
      throw new ArgumentNullException(); 
     if (reference == null) 
      return null; 
     CodeTypeDeclaration foundType = null; 
     foreach (CodeTypeDeclaration codeType in codeNamespace.Types) 
     { 
      if (codeType.Name == reference.BaseType) 
      { 
       if (foundType == null) 
        foundType = codeType; 
       else if (foundType != codeType) 
       { 
        foundType = null; 
        break; 
       } 
      } 
     } 
     return foundType; 
    } 
} 

public static class CodeMemberPropertyExtensions 
{ 
    public static bool TryGetBackingFieldAndSpecifiedProperty(this CodeMemberProperty property, CodeTypeDeclaration codeType, 
     out CodeMemberField backingField, out CodeMemberProperty specifiedProperty) 
    { 
     if (property == null) 
     { 
      backingField = null; 
      specifiedProperty = null; 
      return false; 
     } 

     if ((backingField = property.GetBackingField(codeType)) == null) 
     { 
      specifiedProperty = null; 
      return false; 
     } 

     specifiedProperty = null; 
     var specifiedName = property.Name + "Specified"; 
     foreach (var p in codeType.Members.OfType<CodeMemberProperty>()) 
     { 
      if (p.Name == specifiedName) 
      { 
       // Make sure the property is marked as XmlIgnore (there might be a legitimate, serializable property 
       // named xxxSpecified). 
       if (!p.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlIgnoreAttribute).FullName)) 
        continue; 
       if (specifiedProperty == null) 
        specifiedProperty = p; 
       else if (specifiedProperty != p) 
       { 
        specifiedProperty = null; 
        break; 
       } 
      } 
     } 
     if (specifiedProperty == null) 
      return false; 
     if (specifiedProperty.GetBackingField(codeType) == null) 
      return false; 
     return true; 
    } 

    public static CodeMemberField GetBackingField(this CodeMemberProperty property, CodeTypeDeclaration codeType) 
    { 
     if (property == null) 
      return null; 

     CodeMemberField returnedField = null; 
     foreach (var statement in property.GetStatements.OfType<CodeMethodReturnStatement>()) 
     { 
      var expression = statement.Expression as CodeFieldReferenceExpression; 
      if (expression == null) 
       return null; 
      if (!(expression.TargetObject is CodeThisReferenceExpression)) 
       return null; 
      var fieldName = expression.FieldName; 
      foreach (var field in codeType.Members.OfType<CodeMemberField>()) 
      { 
       if (field.Name == fieldName) 
       { 
        if (returnedField == null) 
         returnedField = field; 
        else if (returnedField != field) 
         return null; 
       } 
      } 
     } 

     return returnedField; 
    } 
} 

public abstract class CustomXsdCodeGeneratorBase 
{ 
    // This base class adapted from http://mikehadlow.blogspot.com/2007/01/writing-your-own-xsdexe.html 

    readonly string Namespace; 

    public CustomXsdCodeGeneratorBase(string Namespace) 
    { 
     this.Namespace = Namespace; 
    } 

    public void XsdToClassTest(IEnumerable<string> xsds, TextWriter codeWriter) 
    { 
     XsdToClassTest(xsds.Select(xsd => (Func<TextReader>)(() => new StringReader(xsd))), codeWriter); 
    } 

    public void XsdToClassTest(IEnumerable<Func<TextReader>> xsds, TextWriter codeWriter) 
    { 
     var schemas = new XmlSchemas(); 

     foreach (var getReader in xsds) 
     { 
      using (var reader = getReader()) 
      { 
       var xsd = XmlSchema.Read(reader, null); 
       schemas.Add(xsd); 
      } 
     } 

     schemas.Compile(null, true); 
     var schemaImporter = new XmlSchemaImporter(schemas); 

     var maps = new List<XmlTypeMapping>(); 
     foreach (XmlSchema xsd in schemas) 
     { 
      foreach (XmlSchemaType schemaType in xsd.SchemaTypes.Values) 
      { 
       maps.Add(schemaImporter.ImportSchemaType(schemaType.QualifiedName)); 
      } 
      foreach (XmlSchemaElement schemaElement in xsd.Elements.Values) 
      { 
       maps.Add(schemaImporter.ImportTypeMapping(schemaElement.QualifiedName)); 
      } 
     } 

     // create the codedom 
     var codeNamespace = new CodeNamespace(this.Namespace); 
     var codeExporter = new XmlCodeExporter(codeNamespace); 
     foreach (XmlTypeMapping map in maps) 
     { 
      codeExporter.ExportTypeMapping(map); 
     } 

     ModifyGeneratedNamespace(codeNamespace); 

     // Check for invalid characters in identifiers 
     CodeGenerator.ValidateIdentifiers(codeNamespace); 

     // output the C# code 
     var codeProvider = new CSharpCodeProvider(); 
     codeProvider.GenerateCodeFromNamespace(codeNamespace, codeWriter, new CodeGeneratorOptions()); 
    } 

    protected virtual void ModifyGeneratedNamespace(CodeNamespace codeNamespace) 
    { 
     foreach (CodeTypeDeclaration codeType in codeNamespace.Types) 
     { 
      ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace); 
     } 
    } 

    protected virtual void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace) 
    { 
    } 
} 

为了测试它,我创建了以下几种类型:

namespace SampleClasses 
{ 
    public class SimleSampleClass 
    { 
     [XmlElement] 
     public decimal Something { get; set; } 

     [XmlIgnore] 
     public bool SomethingSpecified { get; set; } 
    } 

    [XmlRoot("RootClass")] 
    public class RootClass 
    { 
     [XmlArray] 
     [XmlArrayItem("SampleClass")] 
     public List<SampleClass> SampleClasses { get; set; } 
    } 

    [XmlRoot("SampleClass")] 
    public class SampleClass 
    { 
     [XmlAttribute] 
     public long Id { get; set; } 

     public decimal Something { get; set; } 

     [XmlIgnore] 
     public bool SomethingSpecified { get; set; } 

     public SomeEnum SomeEnum { get; set; } 

     [XmlIgnore] 
     public bool SomeEnumSpecified { get; set; } 

     public string SomeString { get; set; } 

     [XmlIgnore] 
     public bool SomeStringSpecified { get; set; } 

     public decimal? SomeNullable { get; set; } 

     [XmlIgnore] 
     public bool SomeNullableSpecified { get; set; } 

     public DateTime SomeDateTime { get; set; } 

     [XmlIgnore] 
     public bool SomeDateTimeSpecified { get; set; } 

     // https://stackoverflow.com/questions/3280362/most-elegant-xml-serialization-of-color-structure 

     [XmlElement(Type = typeof(XmlColor))] 
     public Color MyColor { get; set; } 

     [XmlIgnore] 
     public bool MyColorSpecified { get; set; } 
    } 

    public enum SomeEnum 
    { 
     DefaultValue, 
     FirstValue, 
     SecondValue, 
     ThirdValue, 
    } 

    // https://stackoverflow.com/questions/3280362/most-elegant-xml-serialization-of-color-structure 
    public struct XmlColor 
    { 
     private Color? color_; 

     private Color Color 
     { 
      get 
      { 
       return color_ ?? Color.Black; 
      } 
      set 
      { 
       color_ = value; 
      } 
     } 

     public XmlColor(Color c) { color_ = c; } 

     public Color ToColor() 
     { 
      return Color; 
     } 

     public void FromColor(Color c) 
     { 
      Color = c; 
     } 

     public static implicit operator Color(XmlColor x) 
     { 
      return x.ToColor(); 
     } 

     public static implicit operator XmlColor(Color c) 
     { 
      return new XmlColor(c); 
     } 

     [XmlAttribute] 
     public string Web 
     { 
      get { return ColorTranslator.ToHtml(Color); } 
      set 
      { 
       try 
       { 
        if (Alpha == 0xFF) // preserve named color value if possible 
         Color = ColorTranslator.FromHtml(value); 
        else 
         Color = Color.FromArgb(Alpha, ColorTranslator.FromHtml(value)); 
       } 
       catch (Exception) 
       { 
        Color = Color.Black; 
       } 
      } 
     } 

     [XmlAttribute] 
     public byte Alpha 
     { 
      get { return Color.A; } 
      set 
      { 
       if (value != Color.A) // avoid hammering named color if no alpha change 
        Color = Color.FromArgb(value, Color); 
      } 
     } 

     public bool ShouldSerializeAlpha() { return Alpha < 0xFF; } 
    } 
} 

使用泛型xsd.exe我从它们生成了以下模式:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> 
    <xs:element name="SimleSampleClass" nillable="true" type="SimleSampleClass" /> 
    <xs:complexType name="SimleSampleClass"> 
    <xs:sequence> 
     <xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" /> 
    </xs:sequence> 
    </xs:complexType> 
    <xs:element name="RootClass" nillable="true" type="RootClass" /> 
    <xs:complexType name="RootClass"> 
    <xs:sequence> 
     <xs:element minOccurs="0" maxOccurs="1" name="SampleClasses" type="ArrayOfSampleClass" /> 
    </xs:sequence> 
    </xs:complexType> 
    <xs:complexType name="ArrayOfSampleClass"> 
    <xs:sequence> 
     <xs:element minOccurs="0" maxOccurs="unbounded" name="SampleClass" nillable="true" type="SampleClass" /> 
    </xs:sequence> 
    </xs:complexType> 
    <xs:complexType name="SampleClass"> 
    <xs:sequence> 
     <xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" /> 
     <xs:element minOccurs="0" maxOccurs="1" name="SomeEnum" type="SomeEnum" /> 
     <xs:element minOccurs="0" maxOccurs="1" name="SomeString" type="xs:string" /> 
     <xs:element minOccurs="0" maxOccurs="1" name="SomeNullable" nillable="true" type="xs:decimal" /> 
     <xs:element minOccurs="0" maxOccurs="1" name="SomeDateTime" type="xs:dateTime" /> 
     <xs:element minOccurs="0" maxOccurs="1" name="MyColor" type="XmlColor" /> 
    </xs:sequence> 
    <xs:attribute name="Id" type="xs:long" use="required" /> 
    </xs:complexType> 
    <xs:simpleType name="SomeEnum"> 
    <xs:restriction base="xs:string"> 
     <xs:enumeration value="DefaultValue" /> 
     <xs:enumeration value="FirstValue" /> 
     <xs:enumeration value="SecondValue" /> 
     <xs:enumeration value="ThirdValue" /> 
    </xs:restriction> 
    </xs:simpleType> 
    <xs:complexType name="XmlColor"> 
    <xs:attribute name="Web" type="xs:string" /> 
    <xs:attribute name="Alpha" type="xs:unsignedByte" /> 
    </xs:complexType> 
    <xs:element name="SampleClass" nillable="true" type="SampleClass" /> 
    <xs:element name="SomeEnum" type="SomeEnum" /> 
    <xs:element name="XmlColor" type="XmlColor" /> 
</xs:schema> 

而且,采用这种模式,我再生利用CustomXsdCodeGenerator下面的C#类与promoteToNullable = trueNamespace = "Question42295155"

namespace Question42295155 { 


    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    [System.ComponentModel.DesignerCategoryAttribute("code")] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] 
    public partial class SimleSampleClass { 

     private System.Nullable<decimal> somethingField; 

     /// <remarks/> 
     public System.Nullable<decimal> Something { 
      get { 
       return this.somethingField; 
      } 
      set { 
       this.somethingField = value; 
      } 
     } 
    } 

    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    [System.ComponentModel.DesignerCategoryAttribute("code")] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] 
    public partial class SampleClass { 

     private System.Nullable<decimal> somethingField; 

     private System.Nullable<SomeEnum> someEnumField; 

     private string someStringField; 

     private System.Nullable<decimal> someNullableField; 

     private System.Nullable<System.DateTime> someDateTimeField; 

     private XmlColor myColorField; 

     private long idField; 

     /// <remarks/> 
     public System.Nullable<decimal> Something { 
      get { 
       return this.somethingField; 
      } 
      set { 
       this.somethingField = value; 
      } 
     } 

     /// <remarks/> 
     public System.Nullable<SomeEnum> SomeEnum { 
      get { 
       return this.someEnumField; 
      } 
      set { 
       this.someEnumField = value; 
      } 
     } 

     /// <remarks/> 
     public string SomeString { 
      get { 
       return this.someStringField; 
      } 
      set { 
       this.someStringField = value; 
      } 
     } 

     /// <remarks/> 
     [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)] 
     public System.Nullable<decimal> SomeNullable { 
      get { 
       return this.someNullableField; 
      } 
      set { 
       this.someNullableField = value; 
      } 
     } 

     /// <remarks/> 
     public System.Nullable<System.DateTime> SomeDateTime { 
      get { 
       return this.someDateTimeField; 
      } 
      set { 
       this.someDateTimeField = value; 
      } 
     } 

     /// <remarks/> 
     public XmlColor MyColor { 
      get { 
       return this.myColorField; 
      } 
      set { 
       this.myColorField = value; 
      } 
     } 

     /// <remarks/> 
     [System.Xml.Serialization.XmlAttributeAttribute()] 
     public long Id { 
      get { 
       return this.idField; 
      } 
      set { 
       this.idField = value; 
      } 
     } 
    } 

    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] 
    public enum SomeEnum { 

     /// <remarks/> 
     DefaultValue, 

     /// <remarks/> 
     FirstValue, 

     /// <remarks/> 
     SecondValue, 

     /// <remarks/> 
     ThirdValue, 
    } 

    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    [System.ComponentModel.DesignerCategoryAttribute("code")] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] 
    public partial class XmlColor { 

     private string webField; 

     private byte alphaField; 

     /// <remarks/> 
     [System.Xml.Serialization.XmlAttributeAttribute()] 
     public string Web { 
      get { 
       return this.webField; 
      } 
      set { 
       this.webField = value; 
      } 
     } 

     /// <remarks/> 
     [System.Xml.Serialization.XmlAttributeAttribute()] 
     public byte Alpha { 
      get { 
       return this.alphaField; 
      } 
      set { 
       this.alphaField = value; 
      } 
     } 
    } 

    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    [System.ComponentModel.DesignerCategoryAttribute("code")] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] 
    public partial class RootClass { 

     private SampleClass[] sampleClassesField; 

     /// <remarks/> 
     public SampleClass[] SampleClasses { 
      get { 
       return this.sampleClassesField; 
      } 
      set { 
       this.sampleClassesField = value; 
      } 
     } 
    } 

    /// <remarks/> 
    [System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")] 
    [System.SerializableAttribute()] 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    [System.ComponentModel.DesignerCategoryAttribute("code")] 
    [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] 
    public partial class ArrayOfSampleClass { 

     private SampleClass[] sampleClassField; 

     /// <remarks/> 
     [System.Xml.Serialization.XmlElementAttribute("SampleClass", IsNullable=true)] 
     public SampleClass[] SampleClass { 
      get { 
       return this.sampleClassField; 
      } 
      set { 
       this.sampleClassField = value; 
      } 
     } 
    } 
} 

注意:

  • 有在名称结尾没有属性Specified

  • 属性Something,SomeEnumSomeDateTime已成为空值。

  • 已经为空的public decimal? SomeNullable { get; set; }往返public System.Nullable<decimal> SomeNullable,而不是成为一些可怕的双可空System.Nullable<System.Nullable<decimal>>的失败。

我然后产生从最初RootClass以下XML:

<RootClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <SampleClasses> 
    <SampleClass Id="10101"> 
     <Something>2.718</Something> 
     <SomeEnum>ThirdValue</SomeEnum> 
     <SomeString>hello</SomeString> 
     <SomeNullable>3.14</SomeNullable> 
     <SomeDateTime>2017-02-28T00:00:00-05:00</SomeDateTime> 
     <MyColor Web="Maroon" /> 
    </SampleClass> 
    </SampleClasses> 
</RootClass> 

,并能够成功地反序列化它的产生Question42295155.RootClass课,而不会丢失数据。

注 - 此代码是轻微测试。如果您想提供一个样本模式,我可以重新测试一下。请参阅Code Generation in the .NET Framework Using XML Schema

+0

令人惊叹的工作,谢谢! – JacquesB

+0

很好的解释!!!!! – csharpbd