Reputation: 54117
I have a class that represents credit card details. To represent valid from and expiration months and years I am using four properties of type int
:
public int ValidFromMonth { get; set; }
public int ValidFromYear { get; set; }
public int ExpiresEndMonth { get; set; }
public int ExpiresEndYear { get; set; }
I am XML Serializing this class for consumption by a third party. That third party requires my month and year values to be prefixed with a leading zero if the value is less than 10
<validFromMonth>02</validFromMonth>
<validFromYear>09</validFromYear>
<expiresEndMonth>10</expiresEndMonth>
<expiresEndYear>14</expiresEndYear>
Does .NET support any attribution (or is it possible for me to create a custom attribute) that will enforce this rule, possibly using a format string (e.g. {0:00}
)?
Note: I know that I could add my own string
properties that do the formatting internally, and add an [XmlIgnore]
attribute to my int
properties, but this feels like a second-rate solution.
Edit: After some consideration I am wondering if this is actually just not feasible. Serialization would be no problem, but in order for deserialization to work you would need to un-format the serialized string. In the trivial example above this would be easy, but I am not sure that it could be made to work in the more general case.
Edit2: The XML Schema that defines the two-digit requirement is below.
Simple type definitions:
<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>
Credit card definition that uses these types:
<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>
Upvotes: 3
Views: 4440
Reputation: 36
Disadvatage using XmlEnum is that it can't be nullable
i will Recommand
[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); }
}
This will allow you to make attribute nullable
Upvotes: 1
Reputation: 10038
OK, ignore my previous code sample (I'll leave it up since it might help somebody else, though). I just remembered you can do this using XmlEnumAttribute:
public enum LeadingZeroMonth
{
[XmlEnum("01")]
January,
...
[XmlEnum("12")]
December
}
and then change your usage to the enum:
public LeadingZeroMonth ValidFromMonth { get; set; }
This is actually a very nice way since you now have an enum for the month (which is really what you should've done from the beginning).
Upvotes: 3
Reputation: 161773
This sort of requirement often comes from companies that don't understand XML. Rather than assume this is the case here, I'll ask: did they supply you with XML schema that describes the leading-zero day format? If so, could you post the part of it that defines the day?
EDIT based on edit
Thanks for posting the schema. It confirmed the other thing I was concerned about. Your integers are not integers. Note the <restriction base="xs:string"/>
. These are strings, not integers.
Upvotes: 1
Reputation: 10038
This is a lot of code, but it does what you want. The gist is that you can create a new class (LeadingZero
in this example) and implement IXmlSerializable
to control how you read/write from the XML stream. Hope this helps:
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();
}
}
}
This is the output that I got:
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!
Upvotes: 3