【问题标题】:How can I XML Serialize a DateTimeOffset Property?如何 XML 序列化 DateTimeOffset 属性?
【发布时间】:2010-07-31 06:04:50
【问题描述】:

当数据表示为 Xml 时,我在此类中拥有的 DateTimeOffset 属性不会被呈现。我需要做什么来告诉 Xml 序列化以将其正确呈现为 DateTimeDateTimeOffset

[XmlRoot("playersConnected")]
public class PlayersConnectedViewData
{
    [XmlElement("playerConnected")]
    public PlayersConnectedItem[] playersConnected { get; set; }
}

[XmlRoot("playersConnected")]
public class PlayersConnectedItem
{
    public string name { get; set; }
    public DateTimeOffset connectedOn { get; set; }  // <-- This property fails.
    public string server { get; set; }
    public string gameType { get; set; }
}

还有一些示例数据...

<?xml version="1.0" encoding="utf-8"?>
<playersConnected 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <playerConnected>
    <name>jollyroger1000</name>
    <connectedOn />
    <server>log1</server>
    <gameType>Battlefield 2</gameType>
  </playerConnected>
</playersConnected>

更新

我希望可能有一种方法可以通过我可以在属性上装饰的属性...

奖金问题

有什么方法可以摆脱根节点中声明的这两个命名空间?我应该吗?

【问题讨论】:

  • 为什么还要玩命名空间?

标签: .net xml-serialization


【解决方案1】:

这已经晚了几年,但这里是使用 ISO 8601完全序列化 DateTimeOffset 的快速简便的方法:

[XmlElement("lastUpdatedTime")]
public string lastUpdatedTimeForXml // format: 2011-11-11T15:05:46.4733406+01:00
{
   get { return lastUpdatedTime.ToString("o"); } // o = yyyy-MM-ddTHH:mm:ss.fffffffzzz
   set { lastUpdatedTime = DateTimeOffset.Parse(value); } 
}
[XmlIgnore] 
public DateTimeOffset lastUpdatedTime;

【讨论】:

  • 与 XmlConvert.ToString(DateTimeOffset) 相同,而 XmlConvert.ToDateTimeOffset(String) 更清晰。但是一段时间后,为您想要使用的每个全球化日期类型添加如此多的属性变得乏味。因此,对于大量使用,我觉得可能创建一个实现 IXmlSerializable 和 IConvertable 的新结构,并且在这些情况下,赋值运算符会更好。
  • 你可以做return lastUpdatedTime.ToString("o")(它发出ISO 8601。)见docs.microsoft.com/en-us/dotnet/standard/base-types/…
【解决方案2】:

我想出了这个基于 ISO 8601 格式(例如2011-11-11T15:05:46.4733406+01:00)实现 XML 序列化的结构。提示:尝试解析 DateTime 值(例如 2011-11-11T15:05:46)会按预期失败。

欢迎反馈。我没有在这里包含单元测试,因为那会是太多的文本。

/// <remarks>
/// The default value is <c>DateTimeOffset.MinValue</c>. This is a value
/// type and has the same hash code as <c>DateTimeOffset</c>! Implicit
/// assignment from <c>DateTime</c> is neither implemented nor desirable!
/// </remarks>
public struct Iso8601SerializableDateTimeOffset : IXmlSerializable
{
    private DateTimeOffset value;

    public Iso8601SerializableDateTimeOffset(DateTimeOffset value)
    {
        this.value = value;
    }

    public static implicit operator Iso8601SerializableDateTimeOffset(DateTimeOffset value)
    {
        return new Iso8601SerializableDateTimeOffset(value);
    }

    public static implicit operator DateTimeOffset(Iso8601SerializableDateTimeOffset instance)
    {
        return instance.value;
    }

    public static bool operator ==(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
    {
        return a.value == b.value;
    }

    public static bool operator !=(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
    {
        return a.value != b.value;
    }

    public static bool operator <(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
    {
        return a.value < b.value;
    }

    public static bool operator >(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
    {
        return a.value > b.value;
    }

    public override bool Equals(object o)
    {
        if(o is Iso8601SerializableDateTimeOffset)
            return value.Equals(((Iso8601SerializableDateTimeOffset)o).value);
        else if(o is DateTimeOffset)
            return value.Equals((DateTimeOffset)o);
        else
            return false;
    }

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        var text = reader.ReadElementString();
        value = DateTimeOffset.ParseExact(text, format: "o", formatProvider: null);
    }

    public override string ToString()
    {
        return value.ToString(format: "o");
    }

    public string ToString(string format)
    {
        return value.ToString(format);
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteString(value.ToString(format: "o"));
    }
}

【讨论】:

    【解决方案3】:

    我也不确定最好的方法,但这是我所做的:

    [XmlElement("lastUpdatedTime")]
    public string lastUpdatedTimeForXml
    {
      get { return lastUpdatedTime.ToString(); }
      set { lastUpdatedTime = DateTimeOffset.Parse(value); }
    }
    [XmlIgnore] 
    public DateTimeOffset lastUpdatedTime;
    

    【讨论】:

    • 我认为你的getter应该是:get { return lastUpdatedTime.ToString("o"); }。这将返回 XML 日期/时间格式的字符串。
    • 如果 xml 被写入然后在具有不同区域设置的工作站上读取,则此 中断。 DateTimeOffset.ToString() 函数用于显示,而不是用于序列化。您需要指定一个不依赖于区域设置的格式化程序,例如"yyyy-MM-ddTHH\:mm\:ss.fffffffzzz"->2011-11-11T15:05:46.4733406+01:00
    • 这不起作用。快速测试显示它var now = DateTimeOffset.UtcNow;var nowSerialized = now.ToString();var nowDeserialized = DateTimeOffset.Parse(nowSerialized);Assert.AreNotEqual(now, nowDeserialized);
    【解决方案4】:

    我在这里找到了解决方案:http://tneustaedter.blogspot.com/2012/02/proper-way-to-serialize-and-deserialize.html

    用 DataContractSerializer 替换 XmlSerializer 效果很好。 请参阅下面的示例代码:

        public static string XmlSerialize(this object input)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                DataContractSerializer serializer = new DataContractSerializer(input.GetType());
                serializer.WriteObject(stream, input);
                return new UTF8Encoding().GetString(stream.ToArray());
            }
        }
    
        public static T XmlDeserialize<T>(this string input)
        {
            using (MemoryStream memoryStream = new MemoryStream(new UTF8Encoding().GetBytes(input)))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(T));
                return (T)serializer.ReadObject(memoryStream);
            }
        }
    

    【讨论】:

      【解决方案5】:

      我最终只是这样做了......

      添加了两个扩展方法...

      public static double ToUnixEpoch(this DateTimeOffset value)
      {
          // Create Timespan by subtracting the value provided from 
          //the Unix Epoch then return the total seconds (which is a UNIX timestamp)
          return (double)((value - new DateTime(1970, 1, 1, 0, 0, 0, 0)
              .ToLocalTime())).TotalSeconds;
      }
      
      public static string ToJsonString(this DateTimeOffset value)
      {
          return string.Format("\\/Date({0})\\/", value.ToUnixEpoch());
      }
      

      修改了 ViewData 类...

      [XmlRoot("playersConnected")]
      public class PlayersConnectedItem
      {
          public string name { get; set; }
          public string connectedOn { get; set; }
          public string server { get; set; }
          public string gameType { get; set; }
      }
      

      更改了我设置 viewdata 属性的方式...

      var data = (from q in connectedPlayerLogEntries
                  select new PlayersConnectedItem
                             {
                                 name = q.ClientName,
                                 connectedOn =  q.CreatedOn.ToJsonString(),
                                 server = q.GameFile.UniqueName,
                                 gameType = q.GameFile.GameType.Description()
                              });
      

      完成。不确定这是否是最好的方法.. 但现在 viewdata 属性对于 Json 或 Xml 具有相同的值。

      【讨论】:

        【解决方案6】:

        解决这个问题的一种方法是让你的类实现接口IXmlSerializable。 实现此接口强制序列化程序调用“覆盖”WriteXmlReadXml 数学。

        类似的东西:

        public void WriteXml(XmlWriter w)
        {
            wr.WriteStartElement("playersConnected"); 
            w.WriteElementString("name", Name);
            w.WriteElementString("connected-on" , ConnectedOn.ToString("dd.MM.yyyy HH:mm:ss"));
            //etc...
        }
        

        当你阅读它时:

        DateTimeOffset offset;
        
        if(DateTimeoffset.TryParse(reader.Value, out offset))
        {
            connectedOn = offset;
        }
        

        这很麻烦,但我不能以任何其他方式解决问题。 此解决方案还让您可以完全控制序列化过程(这是优势)

        如果您喜欢这个解决方案并想要完整的解决方案,请发表评论,我会写下来

        关于命名空间 - 我认为您无法摆脱它(我可能不会获得奖励分数)。

        【讨论】:

          【解决方案7】:

          为了补充@Peter 的answer,如果您使用的是 ADO.NET 实体模型 (.edmx),因此所有访问修饰符都是在部分类中自动生成的,您可以编辑 T4 模板(展开 .在解决方案资源管理器中打开.edmx 文件并打开 YourEdmxFilename.tt) 以使其生成带有 internal 修饰符而不是默认值的 DateTimeOffset 类型。

          只需将Property(EdmProperty) 方法替换为以下方法:

          public string Property(EdmProperty edmProperty)
          {
              string typeName = _typeMapper.GetTypeName(edmProperty.TypeUsage);
              return string.Format(
                  CultureInfo.InvariantCulture,
                  "{0} {1} {2} {{ {3}get; {4}set; }}",
                  typeName == "System.DateTimeOffset" || typeName == "Nullable<System.DateTimeOffset>" ? "internal" : Accessibility.ForProperty(edmProperty),
                  typeName,
                  _code.Escape(edmProperty),
                  _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-06-22
            • 1970-01-01
            • 1970-01-01
            • 2011-06-19
            • 1970-01-01
            • 1970-01-01
            • 2015-04-17
            相关资源
            最近更新 更多