你的位置:首页 > ASP.net教程

[ASP.net教程]XsdGen:通过自定义Attribute与反射自动生成XSD


前言

        系统之间的数据交互往往需要事先定义一些契约,在WCF中我们需要先编写XSD文件,然后通过自动代码生成工具自动生成C#对象。对于刚刚接触契约的人来说,掌握

        于是我想能不能先用C#写好类型,然后自动生成标准格式的XSD呢。经过三天左右的设计和实现,目前实现了以下功能:

1. 支持Class和Enum类型的设计

2. 支持基元类型、自定义类型、泛型列表、自定义类型数组等属性

3. 支持自定义类型之间的依赖关系

4. 支持契约分组(指定Request/Response分到同一个xsd文件)

5. 支持契约汇总(对于自定义类型,最终体现在一个汇总xsd文件中,并自动引用其它xsd文件)

        开源地址:https://github.com/CreateChen/XsdGen。我刚接触SOA不久,目前只添加了xsd的minOccurs、maxOccurs等基本属性,对于其它属性并没有全部集成进去,集成方式也非常简单,在Attribute中添加相应的属性,并在XsdBuilder.cs中添加相应的处理。

效果

        先看一下实现的效果,例如我定义了以下类型:FoodOrderRequest、FoodOrderResponse、PayType

using System;using System.Collections.Generic;using XsdAttribute;[assembly: XsdSchema(  TargetNamespace = "http://xx.com/framework/soa/sample/v1",  = "http://xx.com/framework/soa/sample/v1",  Namespace = "http://xx.com/framework/soa/sample/v1",  Common = "http://xx.com/common/types/v1")][assembly: XsdImport(Id = "SOACommonTypes",  Namespace = "http://xx.com/common/types/v1",  SchemaLocation = "SOACommonTypes_V1.0.0.xsd")]namespace TestDLL{  [XsdComplexType(Annotation = "订餐申请", FileGroup = "FoodOrder")]  public class FoodOrderRequest  {    [XsdElement(MinOccurs = "1", Annotation = "餐馆编号")]    public int RestaurantId { get; set; }    [XsdElement(MinOccurs = "1", Annotation = "餐馆名称")]    public string RestaurantName { get; set; }    [XsdElement(Annotation = "订餐日期")]    public DateTime OrderDate { get; set; }    [XsdElement(MinOccurs = "0", MaxOccurs = "unbounded", Annotation = "食品编号")]    public List<int> FoodId { get; set; }    [XsdElement(MinOccurs = "1", Annotation = "业务类型")]    public PayType BusinessType { get; set; }  }  [XsdComplexType(Annotation = "订餐结果", FileGroup = "FoodOrder")]  public class FoodOrderResponse  {    [XsdElement(MinOccurs = "1", Annotation = "订单编号")]    public int OrderId { get; set; }    [XsdElement(Annotation = "预计送达时间")]    public DateTime DeliveryTime { get; set; }  }  [XsdSimpleType(Annotation = "付款类型", FileGroup = "PayType")]  public enum PayType  {    现金,    支付宝,    微信,    网银  }}

        工具自动为我生成了3个文件:

FoodOrder.xsd

<??><xs:schema id="FoodOrder" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" ="http://www.w3.org/2001/ ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/common/types/v1"> <xs:include schemaLocation="PayType.xsd" /> <xs:import id="SOACommonTypes" schemaLocation="SOACommonTypes_V1.0.0.xsd" namespace="http://xx.com/common/types/v1" /> <xs:complexType name="FoodOrderRequestType">  <xs:annotation>   <xs:documentation>订餐申请</xs:documentation>  </xs:annotation>  <xs:sequence>   <xs:element name="RestaurantId" type="xs:int" minOccurs="1">    <xs:annotation>     <xs:documentation>餐馆编号</xs:documentation>    </xs:annotation>   </xs:element>   <xs:element name="RestaurantName" type="xs:string" minOccurs="1">    <xs:annotation>     <xs:documentation>餐馆名称</xs:documentation>    </xs:annotation>   </xs:element>   <xs:element name="OrderDate" type="xs:dateTime">    <xs:annotation>     <xs:documentation>订餐日期</xs:documentation>    </xs:annotation>   </xs:element>   <xs:element name="FoodId" type="xs:int" minOccurs="0" maxOccurs="unbounded">    <xs:annotation>     <xs:documentation>食品编号</xs:documentation>    </xs:annotation>   </xs:element>   <xs:element name="BusinessType" type="PayType" minOccurs="1">    <xs:annotation>     <xs:documentation>业务类型</xs:documentation>    </xs:annotation>   </xs:element>  </xs:sequence> </xs:complexType> <xs:complexType name="FoodOrderResponseType">  <xs:annotation>   <xs:documentation>订餐结果</xs:documentation>  </xs:annotation>  <xs:sequence>   <xs:element name="OrderId" type="xs:int" minOccurs="1">    <xs:annotation>     <xs:documentation>订单编号</xs:documentation>    </xs:annotation>   </xs:element>   <xs:element name="DeliveryTime" type="xs:dateTime">    <xs:annotation>     <xs:documentation>预计送达时间</xs:documentation>    </xs:annotation>   </xs:element>  </xs:sequence> </xs:complexType></xs:schema>

PayType.xsd

<??><xs:schema id="PayType" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" ="http://www.w3.org/2001/ ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/common/types/v1"> <xs:import id="SOACommonTypes" schemaLocation="SOACommonTypes_V1.0.0.xsd" namespace="http://xx.com/common/types/v1" /> <xs:simpleType name="PayType" final="restriction">  <xs:restriction base="xs:string">   <xs:enumeration value="现金" />   <xs:enumeration value="支付宝" />   <xs:enumeration value="微信" />   <xs:enumeration value="网银" />  </xs:restriction> </xs:simpleType></xs:schema>

TestDLL.xsd

<??><xs:schema id="TestDLL" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" ="http://www.w3.org/2001/ ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/framework/soa/sample/v1" ="http://xx.com/common/types/v1"> <xs:import id="SOACommonTypes" schemaLocation="SOACommonTypes_V1.0.0.xsd" namespace="http://xx.com/common/types/v1" /> <xs:include schemaLocation="FoodOrder.xsd" /> <xs:include schemaLocation="PayType.xsd" /> <xs:element name="FoodOrderRequest" nillable="true" type="ns:FoodOrderRequestType" /> <xs:element name="FoodOrderResponse" nillable="true" type="ns:FoodOrderResponseType" /></xs:schema>

自定义Attribute

1. Assembly的Attribute

[AttributeUsage(AttributeTargets.Assembly)]public class XsdSchema : Attribute{  public string TargetNamespace { get; set; }  public string get; set; }  public string Namespace { get; set; }  public string Common { get; set; }  //汇总dll中的类型,生成总的xsd  private string _packageId;  /// <summary>  /// 生成XSD的文件名称  /// </summary>  public string PackageId  {    get { return _packageId; }    set    {      //去除文件名中非法字符      var regex = new Regex(@"\:|\;|\/|\\|\||\,|\*|\?|\""|\<|\>");      _packageId = regex.Replace(value, "");    }  }  public static XsdSchema Get(Assembly assembly)  {    return (XsdSchema) GetCustomAttribute(assembly, typeof (XsdSchema));  }}

        前几个是一些命名空间的定义,可以根据自己公司的业务规则进行设计。默认情况下,汇总文件的targetId、文件名称以及生成的文件夹名称都是PackageId决定,如果dll没有指定该值,默认则是dll的Name。

        除了定义Schema,每个xsd文件中可能还需要导入一些默认的公共类型,在这种情况下,可以为assembly添加如下Attribute:

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]public class XsdImport : Attribute{  public string Id { get; set; }  public string SchemaLocation { get; set; }  public string Namespace { get; set; }  public static IEnumerable<XsdImport> Get(Assembly assembly)  {    var attributes = GetCustomAttributes(assembly, typeof (XsdImport));    return attributes.Cast<XsdImport>();  }}

2. Class类型的Attribute

[AttributeUsage((AttributeTargets.Class))]public class XsdComplexType : Attribute{  private string _fileGroup;  /// <summary>  /// 生成XSD的文件名称  /// </summary>  public string FileGroup  {    get { return _fileGroup; }    set    {      //去除文件名中非法字符      var regex = new Regex(@"\:|\;|\/|\\|\||\,|\*|\?|\""|\<|\>");      _fileGroup = regex.Replace(value, "");    }  }  public string Annotation { get; set; }  public static XsdComplexType Get(Type type)  {    return (XsdComplexType) GetCustomAttribute(type, typeof (XsdComplexType));  }}

        其中必须指定FileGroup值,相同的FileGroup值,最后会生成到同一个xsd文件中。

2. Property的Attribute

[AttributeUsage((AttributeTargets.Property))]public class XsdElement : Attribute{  public string MinOccurs { get; set; }  public string MaxOccurs { get; set; }  public string Annotation { get; set; }  public static XsdElement Get(PropertyInfo propertyInfo)  {    return (XsdElement) GetCustomAttribute(propertyInfo, typeof (XsdElement));  }}

        如果自定义类型的属性是List或者Array,要将MaxOccurs设为大于0的数或者unbounded。

3. Enum类型的Attribute

[AttributeUsage((AttributeTargets.Enum))]public class XsdSimpleType : Attribute{  private string _fileGroup;  /// <summary>  /// 生成XSD的文件名称  /// </summary>  public string FileGroup  {    get { return _fileGroup; }    set    {      //去除文件名中非法字符      var regex = new Regex(@"\:|\;|\/|\\|\||\,|\*|\?|\""|\<|\>");      _fileGroup = regex.Replace(value, "");    }  }  public string Annotation { get; set; }  public static XsdSimpleType Get(Type type)  {    return (XsdSimpleType) GetCustomAttribute(type, typeof (XsdSimpleType));  }}

        与XsdComplexType类似,相同的FileGroup类型,最后都归到同一个xsd文件中。

反射DLL并生成XSD

1. XsdFile结构定义

public class XsdFile{  public XsdFile()  {    Imports = new List<XElement>();    Elements = new List<XElement>();  }  public XElement Schema { get; set; }  /// <summary>  ///   <para>Assembly级别的Import</para>  ///   <para>自定义复合对象的Import</para>  /// </summary>  public List<XElement> Imports { get; set; }  /// <summary>  ///   <para>自定义Class类型</para>  ///   <para>自定义枚举类型</para>  /// </summary>  public List<XElement> Elements { get; set; }  public XElement Toforeach (var import in Imports)    {      Schema.Add(import);    }    foreach (var element in Elements)    {      Schema.Add(element);    }    return Schema;  }}

        基本思路是Schema添加所有的Import和Element,最后生成

2. XsdBuilder

using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Reflection;using System.using XsdAttribute;namespace XsdGen{  public class XsdBuilder  {    private readonly XNamespace _xs = "http://www.w3.org/2001/";    private Assembly _assembly;    //Key:FileGroup, Value:对应的XsdFile    private Dictionary<string, XsdFile> _xsdFiles;    //自定义Class对象的声明,用于生成汇总xsd    private List<XElement> _elements;    public void Build(Assembly assembly)    {      _assembly = assembly;      _xsdFiles = new Dictionary<string, XsdFile>();      _elements = new List<XElement>();      XElement[] defaultImports = GetDefaultImports(_assembly).ToArray();      XsdSchema defaultSchema = XsdSchema.Get(_assembly);      string directoryName = defaultSchema.PackageId ?? _assembly.GetName().Name;      if (!Directory.Exists(directoryName))      {        Directory.CreateDirectory(directoryName);      }      BuildTypes(_assembly);      foreach (var item in _xsdFiles)      {        item.Value.Schema = GetSchema(defaultSchema, item.Key);        foreach (var import in defaultImports)        {          item.Value.Imports.Add(import);        }        //生成XSD文件        string fileName = string.Format("{0}/{1}.xsd", directoryName, item.Key);        item.Value.To"Generate {0}", fileName);      }      //生成汇总XSD文件      var summaryXsdFile = BuildSummary(defaultSchema, defaultImports, directoryName);      string summaryXsdFileName = string.Format("{0}/{1}.xsd", directoryName, directoryName);      summaryXsdFile.To"{0}Generate Summary{0}\n{1}", new String('=', 10), summaryXsdFileName);    }    private XElement GetSchema(XsdSchema xsdSchema, string id)    {      var schema = new XElement(        _xs + "schema",        new XAttribute("id", id),        new XAttribute("targetNamespace", xsdSchema.TargetNamespace),        new XAttribute("elementFormDefault", "qualified"),        new XAttribute("attributeFormDefault", "unqualified"),        new XAttribute(XNamespace."xs", _xs.ToString())        );      if (!string.IsNullOrEmpty(xsdSchema."", xsdSchema.if (!string.IsNullOrEmpty(xsdSchema.Namespace))        schema.SetAttributeValue(XNamespace.+ "ns", xsdSchema.Namespace);      if (!string.IsNullOrEmpty(xsdSchema.Common))        schema.SetAttributeValue(XNamespace.+ "common", xsdSchema.Common);      return schema;    }    private IEnumerable<XElement> GetDefaultImports(Assembly assembly)    {      var xsdImports = XsdImport.Get(assembly);      return xsdImports.Select(xsdImport => new XElement(        _xs + "import",        new XAttribute("id", xsdImport.Id),        new XAttribute("schemaLocation", xsdImport.SchemaLocation),        new XAttribute("namespace", xsdImport.Namespace)        ));    }    private void BuildTypes(Assembly assembly)    {      var types = assembly.GetTypes();      foreach (var type in types)      {        string fileGroup;        if (type.IsClass)        {          var element = BuildElement(type);          _elements.Add(element);          //_xsdFiles[fileGroup].Elements.Add(element);          var complexTypeElement = BuildComplexType(type, out fileGroup);          _xsdFiles[fileGroup].Elements.Add(complexTypeElement);        }        else if (type.IsEnum)        {          var simpleTypeElement = BuildSimpleType(type, out fileGroup);          _xsdFiles[fileGroup].Elements.Add(simpleTypeElement);        }      }    }    public XElement BuildElement(Type type)    {      //只有Request或者Response对象类型,末尾自动添加Type      string name = (type.Name.EndsWith("Request") || type.Name.EndsWith("Response"))        ? type.Name + "Type"        : type.Name;      return new XElement(        _xs + "element",        new XAttribute("name", type.Name),          new XAttribute("nillable", true),        new XAttribute("type", "ns:" + name)        );    }    private XElement BuildComplexType(Type type, out string fileGroup)    {      var xsdComplexType = XsdComplexType.Get(type);      //添加XSD文件      fileGroup = xsdComplexType.FileGroup;      SetDefaultFile(fileGroup);      //只有Request或者Response对象类型,末尾自动添加Type      string name = (type.Name.EndsWith("Request") || type.Name.EndsWith("Response"))        ? type.Name + "Type"        : type.Name;      var complexTypeElement = new XElement(        _xs + "complexType",        new XAttribute("name", name)        );      if (!string.IsNullOrEmpty(xsdComplexType.Annotation))      {        complexTypeElement.Add(new XElement(          _xs + "annotation",          new XElement(_xs + "documentation", xsdComplexType.Annotation)          ));      }      var sequenceElement = BuildSequence(type);      AddProperties(type, sequenceElement);      complexTypeElement.Add(sequenceElement);      return complexTypeElement;    }    private XElement BuildSequence(Type type)    {      var sequence = new XElement(_xs + "sequence");return sequence;    }    private XsdFile BuildSummary(XsdSchema defaultSchema, XElement[] defaultImports, string packageId)    {      XsdFile xsdFile = new XsdFile();      xsdFile.Schema = GetSchema(defaultSchema, packageId);      foreach (var import in defaultImports)      {        xsdFile.Imports.Add(import);      }      //include所有其它自动生成的xsd文件      foreach (var item in _xsdFiles)      {        xsdFile.Imports.Add(new XElement(          _xs + "include",          new XAttribute("schemaLocation", item.Key + ".xsd")          ));      }      //添加dll中所有定义的element      foreach (var item in _elements)      {        xsdFile.Elements.Add(item);      }      return xsdFile;    }    private void AddProperties(Type type, XElement sequenceElement)    {      var properties = type.GetProperties();      foreach (var propertyInfo in properties)      {        var typeName = Common.GetXsdTypeName(propertyInfo.PropertyType);        var propertyElement = new XElement(          _xs + "element",          new XAttribute("name", propertyInfo.Name),          new XAttribute("type", typeName)          );        var xsdElement = XsdElement.Get(propertyInfo);        if (xsdElement != null)        {          if (!string.IsNullOrEmpty(xsdElement.MinOccurs))            propertyElement.SetAttributeValue("minOccurs", xsdElement.MinOccurs);          if (!string.IsNullOrEmpty(xsdElement.MaxOccurs))            propertyElement.SetAttributeValue("maxOccurs", xsdElement.MaxOccurs);          if (!string.IsNullOrEmpty(xsdElement.Annotation))            propertyElement.Add(new XElement(              _xs + "annotation",              new XElement(                _xs + "documentation", xsdElement.Annotation                )              ));        }        //判断是否自定义类型, 添加Import        if (!typeName.StartsWith("xs:"))        {          var parentClassFileGroup = XsdComplexType.Get(type).FileGroup;          var propertyClassFileGroup = Common.GetFileGroup(propertyInfo.PropertyType);          if (parentClassFileGroup != propertyClassFileGroup)          {            string importXsd = propertyClassFileGroup + ".xsd";            //判断是否已经存在该Import            if (_xsdFiles[parentClassFileGroup].Imports.All(item => item.Attribute("schemaLocation").Value != importXsd))            {              _xsdFiles[parentClassFileGroup].Imports.Add(                new XElement(                  _xs + "include",                  new XAttribute("schemaLocation", importXsd)                  )                );            }          }        }        sequenceElement.Add(propertyElement);      }    }    private XElement BuildSimpleType(Type type, out string fileGroup)    {      var xsdSimpleType = XsdSimpleType.Get(type);      //添加XSD文件      fileGroup = xsdSimpleType.FileGroup;      SetDefaultFile(fileGroup);      var simpleTypeElement = new XElement(        _xs + "simpleType",        new XAttribute("name", type.Name),        new XAttribute("final", "restriction")        );      var restrictionElement = new XElement(        _xs + "restriction",        new XAttribute("base", "xs:string")        );      foreach (var val in Enum.GetNames(type))      {        restrictionElement.Add(          new XElement(            _xs + "enumeration",            new XAttribute("value", val)            )          );      }      simpleTypeElement.Add(restrictionElement);      return simpleTypeElement;    }    private void SetDefaultFile(string fileGroup)    {      if (!_xsdFiles.ContainsKey(fileGroup))      {        var xsdFile = new XsdFile();        _xsdFiles[fileGroup] = xsdFile;      }    }  }}

        XsdBuilder依次反射出Assembly、Class、Enum、Property的自定义Attribute,根据XSD的规则构造XElement。主要步骤在Builder函数中都有所体现。

应用

        现在写接口契约就很爽了,只要定义好类生成DLL,通过XsdGen.exe就可轻松生成接口契约微笑

 

参考

http://www.codeproject.com/Articles/2933/Attributes-in-C

https://msdn.microsoft.com/en-us/library/84c42s56%28v=vs.110%29.aspx

https://msdn.microsoft.com/en-us/library/aa719879(v=vs.71).aspx

https://msdn.microsoft.com/en-us/library/y1375e30%28v=vs.110%29.aspx

http://stackoverflow.com/questions/3353699/using-reflection-to-get-all-classes-of-certain-base-type-in-dll

http://stackoverflow.com/questions/1936953/custom-assembly-attributes

http://stackoverflow.com/questions/1168532/xsd-definition-for-enumerated-value

https://msdn.microsoft.com/en-us/library/bb387075.aspx