zoukankan      html  css  js  c++  java
  • XsdGen:通过自定义Attribute与反射自动生成XSD

    前言

            系统之间的数据交互往往需要事先定义一些契约,在WCF中我们需要先编写XSD文件,然后通过自动代码生成工具自动生成C#对象。对于刚刚接触契约的人来说,掌握XMLSpy之类的软件之后确实比手写XML效率要高,但还是有些学习成本的。此外XML的tag太多,如果设计的类型属性过多,手写XSD也不太现实,很难专注于设计。

            于是我想能不能先用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",
        XmlNamespace = "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

    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema id="FoodOrder" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://xx.com/framework/soa/sample/v1" xmlns:ns="http://xx.com/framework/soa/sample/v1" xmlns:common="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

    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema id="PayType" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://xx.com/framework/soa/sample/v1" xmlns:ns="http://xx.com/framework/soa/sample/v1" xmlns:common="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

    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema id="TestDLL" targetNamespace="http://xx.com/framework/soa/sample/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://xx.com/framework/soa/sample/v1" xmlns:ns="http://xx.com/framework/soa/sample/v1" xmlns:common="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 XmlNamespace { 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 ToXML()
        {
            foreach (var import in Imports)
            {
                Schema.Add(import);
            }
            foreach (var element in Elements)
            {
                Schema.Add(element);
            }
            return Schema;
        }
    }

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

    2. XsdBuilder

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Linq;
    using XsdAttribute;
    
    namespace XsdGen
    {
        public class XsdBuilder
        {
            private readonly XNamespace _xs = "http://www.w3.org/2001/XMLSchema";
            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.ToXML().Save(fileName);
                    Console.WriteLine("Generate {0}", fileName);
                }
                //生成汇总XSD文件
                var summaryXsdFile = BuildSummary(defaultSchema, defaultImports, directoryName);
                string summaryXsdFileName = string.Format("{0}/{1}.xsd", directoryName, directoryName);
                summaryXsdFile.ToXML().Save(summaryXsdFileName);
                Console.WriteLine("{0}Generate Summary{0}
    {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.Xmlns + "xs", _xs.ToString())
                    );
                if (!string.IsNullOrEmpty(xsdSchema.XmlNamespace))
                    schema.SetAttributeValue("xmlns", xsdSchema.XmlNamespace);
                if (!string.IsNullOrEmpty(xsdSchema.Namespace))
                    schema.SetAttributeValue(XNamespace.Xmlns + "ns", xsdSchema.Namespace);
                if (!string.IsNullOrEmpty(xsdSchema.Common))
                    schema.SetAttributeValue(XNamespace.Xmlns + "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

  • 相关阅读:
    筱玛的迷阵探险(折半搜索+01字典树)
    递推
    thin mission 2021 10 8
    4级 -- 阅读
    c++——小知识
    stl
    string
    ting mission 2021.9.20
    ting mission 2021.9.27
    欧拉函数
  • 原文地址:https://www.cnblogs.com/technology/p/XsdGen.html
Copyright © 2011-2022 走看看