【问题标题】:Formatting element/attribute values with XML Serialization使用 XML 序列化格式化元素/属性值
【发布时间】:2009-04-24 15:35:54
【问题描述】:

我有一个代表信用卡详细信息的类。为了表示有效期和到期月份和年份,我使用了 int 类型的四个属性:

public int ValidFromMonth { get; set; }
public int ValidFromYear { get; set; }
public int ExpiresEndMonth { get; set; }
public int ExpiresEndYear { get; set; }

我正在对此类进行 XML 序列化以供第三方使用。如果值小于 10,该第三方要求我的月份和年份值以前导零作为前缀

<validFromMonth>02</validFromMonth>
<validFromYear>09</validFromYear>
<expiresEndMonth>10</expiresEndMonth>
<expiresEndYear>14</expiresEndYear>

.NET 是否支持将强制执行此规则的任何属性(或者我是否可以创建自定义属性),可能使用格式字符串(例如{0:00})?

注意:我知道我可以添加自己的 string 属性以在内部进行格式化,并将 [XmlIgnore] 属性添加到我的 int 属性,但这感觉像是二流的解决方案。

编辑: 经过一番考虑,我想知道这是否真的不可行。序列化没有问题,但为了使反序列化工作,您需要取消格式化序列化字符串。在上面的简单示例中,这很容易,但我不确定它是否可以在更一般的情况下工作。

编辑2: 下面是定义两位数要求的 XML Schema。

简单类型定义:

<xs:simpleType name="CreditCardMonthType">
  <xs:annotation>
   <xs:documentation>Two digit month</xs:documentation>
  </xs:annotation>
  <xs:restriction base="xs:string">
   <xs:minLength value="2" />
   <xs:maxLength value="2" />
  </xs:restriction>
 </xs:simpleType>
<xs:simpleType name="CreditCardYearType">
  <xs:annotation>
   <xs:documentation>Two digit year</xs:documentation>
  </xs:annotation>
  <xs:restriction base="xs:string">
   <xs:minLength value="2" />
   <xs:maxLength value="2" />
  </xs:restriction>
</xs:simpleType>

使用这些类型的信用卡定义:

<xs:attribute name="ExpiryMonth" type="CreditCardMonthType" use="required">
 <xs:annotation>
  <xs:documentation>Credit/debt card's expiry month.</xs:documentation>
 </xs:annotation>
</xs:attribute>
<xs:attribute name="ExpiryYear" type="CreditCardYearType" use="required">
 <xs:annotation>
  <xs:documentation>Credit/debt card's expiry year.</xs:documentation>
 </xs:annotation>
</xs:attribute>
<xs:attribute name="StartMonth" type="CreditCardMonthType" use="optional">
 <xs:annotation>
  <xs:documentation>Switch card's start month.</xs:documentation>
 </xs:annotation>
</xs:attribute>
<xs:attribute name="StartYear" type="CreditCardYearType" use="optional">
 <xs:annotation>
  <xs:documentation>Switch card's start year.</xs:documentation>
 </xs:annotation>
</xs:attribute>

【问题讨论】:

    标签: .net xml xml-serialization string-formatting


    【解决方案1】:

    好的,忽略我之前的代码示例(不过,我会保留它,因为它可能对其他人有帮助)。我只记得你可以使用XmlEnumAttribute

    public enum LeadingZeroMonth
    {
        [XmlEnum("01")]
        January,
    
        ...
    
        [XmlEnum("12")]
        December
    }
    

    然后将你的用法改为枚举:

    public LeadingZeroMonth ValidFromMonth { get; set; }
    

    这实际上是一个非常好的方法,因为您现在有一个月份的枚举(这确实是您从一开始就应该做的)。

    【讨论】:

    • 这很好 - 但我认为我仍需要使用您的 LeadingZero 类方法多年。
    • 可能。这就是我留下它的原因。我认为它们足够不同,足以保证将它们作为单独的答案(取决于您的需要)。我真的希望他们将年份字段扩展到 4 位数。我的意思是,来吧,他们已经在使用详细格式 (XML),他们认为一年只使用两位数可以节省多少?但我了解您是否因使用他们的架构而陷入困境。无论哪种方式,这两件事都应该有助于完成您所需要的。
    【解决方案2】:

    这是很多代码,但它可以满足您的需求。要点是您可以创建一个新类(在此示例中为LeadingZero)并实现IXmlSerializable 来控制您如何从XML 流中读取/写入。希望这会有所帮助:

        using System;
        using System.IO;
        using System.Xml.Serialization;
    
    namespace StackOverflow
    {
        [Serializable]
        public class LeadingZero : IXmlSerializable
        {
            public int Value { get; set; }
    
            public LeadingZero()
            {
                Value = 0;
            }
    
            public LeadingZero(int value)
            {
                this.Value = value;
            }
    
            public override string ToString()
            {
                return Value.ToString("00");
            }
    
            #region IXmlSerializable Members
    
            public System.Xml.Schema.XmlSchema GetSchema()
            {
                return null;
            }
    
            public void ReadXml(System.Xml.XmlReader reader)
            {
                string s = reader.ReadElementString();
                int i;
                if (int.TryParse(s, out i))
                {
                    Value = i;
                }
            }
    
            public void WriteXml(System.Xml.XmlWriter writer)
            {
                writer.WriteString(Value.ToString("00"));
            }
    
            #endregion
        }
    
        [Serializable]
        public class Complex
        {
            public LeadingZero ValidFromMonth { get; set; }
            public LeadingZero ValidFromYear { get; set; }
            public LeadingZero ExpiresEndMonth { get; set; }
            public LeadingZero ExpiresEndYear { get; set; }
        }
    
        class Program
        {
            static void Main()
            {
                var seven = new LeadingZero(7);
    
                XmlSerializer xml = new XmlSerializer(typeof(LeadingZero));
    
                StringWriter writer;
    
                writer = new StringWriter();
                xml.Serialize(writer, seven);
    
                string s = writer.ToString();
    
                Console.WriteLine(seven);
                Console.WriteLine();
                Console.WriteLine(s);
    
                Console.WriteLine();
                var newSeven = xml.Deserialize(new StringReader(s)) as LeadingZero;
                Console.WriteLine(newSeven ?? new LeadingZero(0));
    
                var complicated = new Complex()
                {
                    ValidFromMonth = new LeadingZero(7),
                    ValidFromYear = new LeadingZero(2009),
                    ExpiresEndMonth = new LeadingZero(6),
                    ExpiresEndYear = new LeadingZero(2010)
                };
    
                Console.WriteLine();
                writer = new StringWriter();
    
                xml = new XmlSerializer(typeof(Complex));
                xml.Serialize(writer, complicated);
                s = writer.ToString();
                Console.WriteLine(s);
    
                var newComplicated = xml.Deserialize(new StringReader(s)) as Complex;
                if (newComplicated != null)
                {
                    Console.WriteLine();
                    Console.WriteLine("Woo hoo!");
                }
    
                Console.ReadLine();
            }
        }
    }
    

    这是我得到的输出:

    07
    
    <?xml version="1.0" encoding="utf-16"?>
    <LeadingZero>07</LeadingZero>
    
    07
    
    <?xml version="1.0" encoding="utf-16"?>
    <Complex xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http:/
    /www.w3.org/2001/XMLSchema">
      <ValidFromMonth>07</ValidFromMonth>
      <ValidFromYear>2009</ValidFromYear>
      <ExpiresEndMonth>06</ExpiresEndMonth>
      <ExpiresEndYear>2010</ExpiresEndYear>
    </Complex>
    
    Woo hoo!
    

    【讨论】:

      【解决方案3】:

      这类需求通常来自不了解 XML 的公司。与其假设这里是这种情况,不如问:他们是否为您提供了描述前导零日格式的 XML 模式?如果是这样,您能否发布其中定义日期的部分?


      基于编辑的编辑

      感谢您发布架构。它证实了我担心的另一件事。你的整数不是整数。注意&lt;restriction base="xs:string"/&gt;。这些是字符串,而不是整数。

      【讨论】:

        【解决方案4】:

        使用 XmlEnum 的缺点是它不能为空

        我会推荐

            [XmlIgnore]
            private int? _startMonth;
        
            /// <remarks/>
            [XmlAttributeAttribute]
            public string StartMonth
            {
                get { return _startMonth == null ? null : _startMonth.ToString().PadLeft(2, '0'); }
                set { _startMonth = string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value); }
            }
        

        这将允许您使属性为空

        【讨论】:

        • 感谢您提供的信息——我们刚刚发现 XmlEnum 不能为空,这意味着这种方法不再适合我们的需求。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多