XML 序列化期间尝试动态控制元素名称时出错
Errors when attempting to dynamically control element names during XML Serialization
我一直在网上进行研究,包括 Stack Overflow,但要么我遗漏了什么,要么我看到的例子并不适用于我的情况。
我在 XML 序列化期间尝试动态设置根和列表项元素名称时收到此错误。
XmlRoot and XmlType attributes may not be specified for the type
System.Collections.Generic.List`1[
[XmlSerializationFailureExample.Controllers.MyClass, XmlSerializationFailureExample,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
我在 Microsoft's site 上看到过一篇相当陈旧的帖子,其中指出消息应为:Only XmlRoot and XmlType attributes may be specified...
。果然,如果我删除除XmlRoot和XmlType之外的所有覆盖,错误被清除,但是呈现的XML不符合我的需要
我正在使用 XmlSerializer
Overrides 构造函数,因为我必须动态设置 Root 和第一个子元素名称。相同的 classes 在不同的情况下需要产生不同的 XML 元素名称。尽管此示例只有两个字段,但实际要序列化的 class 有大约 100 个字段。
那么,直接序列化一个List<MyClass>
对象时,如何控制根元素和直接子元素的名称呢?
objective 是让 XML 看起来像这样:
<ArrayOfPerson>
<Person>
<Name>John Doe</Name>
<Age>57</Age>
</Person>
<Person>
<Name>Doe, Jane</Name>
<Age/>
</Person>
</ArrayOfPerson>
通过更改覆盖值,我应该能够从相同的 class:
生成这样的 XML
<ArrayOfEmployee>
<Employee>
<Name>John Doe</Name>
<Age>57</Age>
</Employee>
<Employee>
<Name>Doe, Jane</Name>
<Age/>
</Employee>
</ArrayOfEmployee>
下面是一些演示我的问题的简化代码。在这个例子中,我使用了 Visual Studio 2013 年模板中的基本 MVC.Net 应用程序。
// GET api/SerializationTest
public ActionResult SerializationTest()
{
var list = new List<MyClass> {
new MyClass {Name = "John Doe", Age = 57},
new MyClass {Name = "Doe, Jane"}
};
XmlAttributes xmlPerson = new XmlAttributes {
XmlRoot = new XmlRootAttribute { ElementName = "Person" }
};
XmlAttributes xmlPersonList = new XmlAttributes {
XmlRoot = new XmlRootAttribute { ElementName = "ArrayOfPerson" },
XmlArrayItems = {
new XmlArrayItemAttribute("Person",typeof(MyClass))
},
};
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(MyClass), xmlPerson);
overrides.Add(typeof(List<MyClass>), xmlPersonList);
return new XmlResult(
list,
"TestFile.xml",
overrides
);
}
正在序列化示例 class。实际 class 有大约 100 个属性。
public class MyClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
更新 1
如果我把我的List<MyClass>
封装在另一个class中,然后用下面的属性注释,我就可以得到我想要的XML。但是我该如何动态地执行此操作,因为指定的元素名称必须在运行时发生变化?
[XmlRoot(ElementName = "ArrayOfPerson")]
public class MyCollection
{
[XmlElement(ElementName = "Person")]
public List<MyClass> Items { get; set; }
}
更新 1 结束
XmlResult
类型派生自内置 ActionResult
并包含实际的序列化逻辑。 class 用于 return 文件而不是 HTML 页面。
public class XmlResult : ActionResult
{
private string FileName { get; set; }
private object ObjectToSerialize { get; set; }
private XmlAttributeOverrides Overrides { get; set; }
public XmlResult(object objectToSerialize, string fileName, XmlAttributeOverrides overrides)
{
ObjectToSerialize = objectToSerialize;
FileName = fileName;
Overrides = overrides;
}
public override void ExecuteResult(ControllerContext context)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-control", "no-store, no-cache");
HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=" + FileName);
HttpContext.Current.Response.ContentType = "text/xml";
try
{
if (ObjectToSerialize != null)
{
var xs = new XmlSerializer(ObjectToSerialize.GetType(), Overrides);
xs.Serialize(HttpContext.Current.Response.Output, ObjectToSerialize);
}
}
catch (Exception ex)
{
HttpContext.Current.Response.Write("<error>" + ex + "</error>");
}
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
好吧,我没能像我想要的那样让事情变得动态,但这个解决方案似乎有效。
首先,我将 class 设为抽象基础 class:
public abstract class MyBaseClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
然后我为我想要的每个 XML 元素名称创建派生的 classes:
public class Person : MyBaseClass { }
public class Employee : MyBaseClass { }
然后确保我在我的控制器操作方法中使用正确的派生 classes 是一件简单的事情。
public ActionResult SerializePersonCollectionByXml()
{
var list = new List<Person> {
new Person {Name = "John Doe", Age = 57},
new Person {Name = "Doe, Jane"}
};
return new XmlResult(
list,
"PersonCollectionByXml.xml"
);
}
// GET api/SerializationTest
public ActionResult SerializeEmployeeCollectionByXml()
{
var list = new List<Employee> {
new Employee {Name = "John Doe", Age = 57},
new Employee {Name = "Doe, Jane"}
};
return new XmlResult(
list,
"EmployeeCollectionByXml.xml"
);
}
当然,实际应用中要复杂一些...
上面的技术允许我将所有属性和方法保留在基础 class 中,并仅使用派生的 class 来获取所需的 XML 元素名称。
真正的应用是在 运行 时根据操作方法的输入参数选择 Person
或 Employee
class。此外,逻辑被隐藏在另一组 classes 中,具有反映 classes 的继承结构。由于 .Net 4.6 还不支持与泛型类型的协变(仅接口和委托),我不得不再经历几次旋转才能从内部逻辑中获得正确的 return 值,然后才能 return他们通过 XmlResult class.
示例:
内部服务类:
public abstract class ServiceBase<TRecord> : IDisposable where TRecord : MyBaseClass
{
public abstract List<TRecord> Search(SearchParams p);
}
public class ServicePerson : ServiceBase<Person>
{
public override List<Person> Search(SearchParams p)
{
var result = base.Search(p);
// for example only, just used a simple cast; more complex operation may be required.
return result.Select(r => (Person)r).ToList();
}
}
public class ServiceEmployee : ServiceBase<Employee>
{
public override List<Employee> Search(SearchParams p)
{
var result = base.Search(p);
// for example only, just used a simple cast; more complex operation may be required.
return result.Select(r => (Employee)r).ToList();
}
}
控制器的动作方法:
public ActionResult Search(Guid apiKey, SearchParams p)
{
try
{
if (p.UsePerson)
{
using (var service = new ServicePerson())
{
List<Person> result = service.Search(p);
return XmlResult(result, "PersonList.xml");
}
}
using (var service = new ServiceEmployee())
{
List<Employee> result = service.Search(p);
return XmlResult(result, "EmployeeList.xml");
}
}
catch (Exception ex)
{
// log error
}
}
我一直在网上进行研究,包括 Stack Overflow,但要么我遗漏了什么,要么我看到的例子并不适用于我的情况。
我在 XML 序列化期间尝试动态设置根和列表项元素名称时收到此错误。
XmlRoot and XmlType attributes may not be specified for the type
System.Collections.Generic.List`1[
[XmlSerializationFailureExample.Controllers.MyClass, XmlSerializationFailureExample,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
我在 Microsoft's site 上看到过一篇相当陈旧的帖子,其中指出消息应为:Only XmlRoot and XmlType attributes may be specified...
。果然,如果我删除除XmlRoot和XmlType之外的所有覆盖,错误被清除,但是呈现的XML不符合我的需要
我正在使用 XmlSerializer
Overrides 构造函数,因为我必须动态设置 Root 和第一个子元素名称。相同的 classes 在不同的情况下需要产生不同的 XML 元素名称。尽管此示例只有两个字段,但实际要序列化的 class 有大约 100 个字段。
那么,直接序列化一个List<MyClass>
对象时,如何控制根元素和直接子元素的名称呢?
objective 是让 XML 看起来像这样:
<ArrayOfPerson>
<Person>
<Name>John Doe</Name>
<Age>57</Age>
</Person>
<Person>
<Name>Doe, Jane</Name>
<Age/>
</Person>
</ArrayOfPerson>
通过更改覆盖值,我应该能够从相同的 class:
生成这样的 XML<ArrayOfEmployee>
<Employee>
<Name>John Doe</Name>
<Age>57</Age>
</Employee>
<Employee>
<Name>Doe, Jane</Name>
<Age/>
</Employee>
</ArrayOfEmployee>
下面是一些演示我的问题的简化代码。在这个例子中,我使用了 Visual Studio 2013 年模板中的基本 MVC.Net 应用程序。
// GET api/SerializationTest
public ActionResult SerializationTest()
{
var list = new List<MyClass> {
new MyClass {Name = "John Doe", Age = 57},
new MyClass {Name = "Doe, Jane"}
};
XmlAttributes xmlPerson = new XmlAttributes {
XmlRoot = new XmlRootAttribute { ElementName = "Person" }
};
XmlAttributes xmlPersonList = new XmlAttributes {
XmlRoot = new XmlRootAttribute { ElementName = "ArrayOfPerson" },
XmlArrayItems = {
new XmlArrayItemAttribute("Person",typeof(MyClass))
},
};
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(MyClass), xmlPerson);
overrides.Add(typeof(List<MyClass>), xmlPersonList);
return new XmlResult(
list,
"TestFile.xml",
overrides
);
}
正在序列化示例 class。实际 class 有大约 100 个属性。
public class MyClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
更新 1
如果我把我的List<MyClass>
封装在另一个class中,然后用下面的属性注释,我就可以得到我想要的XML。但是我该如何动态地执行此操作,因为指定的元素名称必须在运行时发生变化?
[XmlRoot(ElementName = "ArrayOfPerson")]
public class MyCollection
{
[XmlElement(ElementName = "Person")]
public List<MyClass> Items { get; set; }
}
更新 1 结束
XmlResult
类型派生自内置 ActionResult
并包含实际的序列化逻辑。 class 用于 return 文件而不是 HTML 页面。
public class XmlResult : ActionResult
{
private string FileName { get; set; }
private object ObjectToSerialize { get; set; }
private XmlAttributeOverrides Overrides { get; set; }
public XmlResult(object objectToSerialize, string fileName, XmlAttributeOverrides overrides)
{
ObjectToSerialize = objectToSerialize;
FileName = fileName;
Overrides = overrides;
}
public override void ExecuteResult(ControllerContext context)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-control", "no-store, no-cache");
HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=" + FileName);
HttpContext.Current.Response.ContentType = "text/xml";
try
{
if (ObjectToSerialize != null)
{
var xs = new XmlSerializer(ObjectToSerialize.GetType(), Overrides);
xs.Serialize(HttpContext.Current.Response.Output, ObjectToSerialize);
}
}
catch (Exception ex)
{
HttpContext.Current.Response.Write("<error>" + ex + "</error>");
}
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
好吧,我没能像我想要的那样让事情变得动态,但这个解决方案似乎有效。
首先,我将 class 设为抽象基础 class:
public abstract class MyBaseClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
然后我为我想要的每个 XML 元素名称创建派生的 classes:
public class Person : MyBaseClass { }
public class Employee : MyBaseClass { }
然后确保我在我的控制器操作方法中使用正确的派生 classes 是一件简单的事情。
public ActionResult SerializePersonCollectionByXml()
{
var list = new List<Person> {
new Person {Name = "John Doe", Age = 57},
new Person {Name = "Doe, Jane"}
};
return new XmlResult(
list,
"PersonCollectionByXml.xml"
);
}
// GET api/SerializationTest
public ActionResult SerializeEmployeeCollectionByXml()
{
var list = new List<Employee> {
new Employee {Name = "John Doe", Age = 57},
new Employee {Name = "Doe, Jane"}
};
return new XmlResult(
list,
"EmployeeCollectionByXml.xml"
);
}
当然,实际应用中要复杂一些... 上面的技术允许我将所有属性和方法保留在基础 class 中,并仅使用派生的 class 来获取所需的 XML 元素名称。
真正的应用是在 运行 时根据操作方法的输入参数选择 Person
或 Employee
class。此外,逻辑被隐藏在另一组 classes 中,具有反映 classes 的继承结构。由于 .Net 4.6 还不支持与泛型类型的协变(仅接口和委托),我不得不再经历几次旋转才能从内部逻辑中获得正确的 return 值,然后才能 return他们通过 XmlResult class.
示例:
内部服务类:
public abstract class ServiceBase<TRecord> : IDisposable where TRecord : MyBaseClass
{
public abstract List<TRecord> Search(SearchParams p);
}
public class ServicePerson : ServiceBase<Person>
{
public override List<Person> Search(SearchParams p)
{
var result = base.Search(p);
// for example only, just used a simple cast; more complex operation may be required.
return result.Select(r => (Person)r).ToList();
}
}
public class ServiceEmployee : ServiceBase<Employee>
{
public override List<Employee> Search(SearchParams p)
{
var result = base.Search(p);
// for example only, just used a simple cast; more complex operation may be required.
return result.Select(r => (Employee)r).ToList();
}
}
控制器的动作方法:
public ActionResult Search(Guid apiKey, SearchParams p)
{
try
{
if (p.UsePerson)
{
using (var service = new ServicePerson())
{
List<Person> result = service.Search(p);
return XmlResult(result, "PersonList.xml");
}
}
using (var service = new ServiceEmployee())
{
List<Employee> result = service.Search(p);
return XmlResult(result, "EmployeeList.xml");
}
}
catch (Exception ex)
{
// log error
}
}