代码之家  ›  专栏  ›  技术社区  ›  code4life

如何从这种XML创建对象图?

  •  0
  • code4life  · 技术社区  · 14 年前

    我的soap服务器向我发送的XML看起来像这样:

    <?xml version='1.0' encoding='utf-8'?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <SOAP-ENV:Header xmlns:xxxxxx="...">
        <...bunch of soap header stuff.../>
      </SOAP-ENV:Header>
      <SOAP-ENV:Body>
        <queryResponse xmlns="...">
          <resultSet seqNo="0">
            <Types>
              <name>varchar</name>
              <value>varchar</value>
            </Types>
            <row>
              <name>field1</name>
              <value>0</value>
            </row>
            <row>
              <name>field2</name>
              <value>some string value</value>
            </row>
            <row>
              <name>field3</name>
              <value>false</value>
            </row>
            <... repeats for many more rows... />
          </resultSet>
        </queryResponse>
      </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    

    我怎么能拿 <row> 节点并填充以下类:

    public class SessionProperties
    {
      public int IntField {get;set;}
      public string StringField {get;set;}
      public bool BooleanField {get;set;}
    
      // more fields...
    }
    

    我想避免手动填充 SessionProperties 实例,例如。,

    var myProps = new SessionProperties();
    myProps.IntField = XElement("...").Value;  // I don't want to do this!
    

    另外,我不想编写一个IXmlSerializable实现,我认为这只会使代码更加复杂。但如果我错了就告诉我。

    最后,soap服务器可能会在将来向我发送额外的行节点,我只想更新 会话属性 如果可能的话,上课。是否有一些通用方法(可能使用自定义属性等)可以实现这一点?

    提前谢谢!

    3 回复  |  直到 14 年前
        1
  •  1
  •   Robert Rossney    14 年前

    我认为这应该不言而喻:

        class Program
        {
            // map names in the XML to the names of SessionInfo properties;
            // you'll need to update this when you add a new property to
            // the SessionInfo class:
            private static Dictionary<string, string> PropertyMap = 
                new Dictionary<string, string>
            {
                {"field1", "StringProperty1"},
                {"field2", "IntProperty1"},
                {"field3", "BoolProperty1"},
            };
    
            // map CLR types to XmlConvert methods; you'll need one entry in
            // this map for every CLR type SessionInfo uses
            private static Dictionary<Type, Func<string, object>> TypeConverterMap = 
                new Dictionary<Type, Func<string, object>>
            {
                { typeof(bool), x => XmlConvert.ToBoolean(x)},
                { typeof(int), x => XmlConvert.ToInt32(x)},
                { typeof(string), x => x},
            };
    
            static void Main(string[] args)
            {
                // map SessionInfo's property names to their PropertyInfo objects
                Dictionary<string, PropertyInfo> properties = AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(x => x.GetExportedTypes())
                    .Where(x => x.Name == "SessionInfo")
                    .SelectMany(x => x.GetMembers())
                    .Where(x => x.MemberType == MemberTypes.Property)
                    .Cast<PropertyInfo>()
                    .ToDictionary(x => x.Name);
    
                string xml =
                    @"<example>
    <row>
        <name>field1</name>
        <value>stringProperty</value>
    </row>
    <row>
        <name>field2</name>
        <value>123</value>
    </row>
    <row>
        <name>field3</name>
        <value>true</value>
    </row>
    </example>";
                XmlDocument d = new XmlDocument();
                d.LoadXml(xml);
    
                SessionInfo s = new SessionInfo();
    
                // populate the object's properties from the values in the XML
                foreach (XmlElement elm in d.SelectNodes("//row"))
                {
                    string name = elm.SelectSingleNode("name").InnerText;
                    string value = elm.SelectSingleNode("value").InnerText;
                    // look up the property for the name in the XML and get its
                    // PropertyInfo object
                    PropertyInfo pi = properties[PropertyMap[name]];
                    // set the property to the value in the XML, using the the converter for 
                    // the property's type
                    pi.SetValue(s, TypeConverterMap[pi.PropertyType](value), null);
                }
    
                // and the results:
                Console.WriteLine(s.StringProperty1);
                Console.WriteLine(s.IntProperty1);
                Console.WriteLine(s.BoolProperty1);
                Console.ReadKey();
            }
    
        2
  •  0
  •   user1228 user1228    14 年前

    嗯,那个XML有点不好。

    如果是我,我会为您的soap结果创建一个包装器类型,它实现 ICustomTypeDescriptor

    自定义类型描述符执行与普通反射相同的操作,但允许类型创建者为调用方处理反射过程。默认实现 TypeDescriptor

    这样做的真正好处是 .

    <TextBox Text="{Binding Session.UserName}" /> 
    

    即使是你真正喜欢的类型 没有用户名属性

    我不是说这会很容易,但一旦你得到了核心概念,这绝对不是那么难。

        3
  •  0
  •   code4life    14 年前

    这个解决方案演示了自定义属性的使用,正如Robert所建议的那样。

    // match the properties to the xml dynamically using this attribute...
    [AttributeUsage(AttributeTargets.Property)]
    public class DBSPropAttribute : Attribute
    {
        public string MappingField { get; set; }
        public DBSPropAttribute(string fieldName)
        {
            MappingField = fieldName;
        }
    }
    

    然后将自定义属性应用于SessionProperties类,如下所示:

    [DBSProp("archiveDays")]
    public int ArchiveDays { get; set; }
    

    在SessionProperties类中,我还将定义这个转换器字典,这是Robert的一个非常优雅的想法:

    // map CLR types to convert methods; you'll need one entry in
    // this map for every CLR type
    private static readonly Dictionary<Type, Func<string, object>> TypeConverterMap =
        new Dictionary<Type, Func<string, object>>
    {
        { typeof(bool), x => Convert.ToBoolean(x)},
        { typeof(int), x => Convert.ToInt32(x)},
        { typeof(string), x => x},
        { typeof(double), x => Convert.ToDouble(x)}
    };
    

    public void SetPropertyValues(IEnumerable<XElement> elements)
    {
        var propList = typeof(SessionProperties).GetProperties();
    
        foreach (var elm in elements)
        {
            var nm = elm.Element("name").Value;
            var val = elm.Element("value").Value;
    
            // MUST throw an exception if there are no matches...
            var pi = propList.First(c => c.GetCustomAttributes(true)
                       .OfType<DBSPropAttribute>()
                       .First()
                       .MappingField == nm);
    
            pi.SetValue(this, TypeConverterMap[pi.PropertyType](val), null);
        }
    }
    

    再次感谢罗伯特阐述了导致这个答案的关键概念。我认为这是一个很好的替代他同样有效的方法。