我一直在网上进行研究,包括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和第一个子元素名称。相同的类需要在不同的情况下生成不同的XML元素名称。虽然这个示例只有两个字段,但序列化的实际类有大约100个字段。
那么,在直接序列化List<MyClass>
对象时,如何控制根元素和直接子元素的名称?
目标是让XML看起来像这样:
<ArrayOfPerson>
<Person>
<Name>John Doe</Name>
<Age>57</Age>
</Person>
<Person>
<Name>Doe, Jane</Name>
<Age/>
</Person>
</ArrayOfPerson>
通过更改覆盖值,我应该能够从同一个类生成这样的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
);
}
正在序列化的示例类。实际的班级有大约100个属性。
public class MyClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
更新1
如果我将List<MyClass>
封装在另一个类中并使用如下属性进行注释,我可以获得我想要的XML。但是我该如何动态地这样做,因为指定的元素名称必须在运行时变化?
[XmlRoot(ElementName = "ArrayOfPerson")]
public class MyCollection
{
[XmlElement(ElementName = "Person")]
public List<MyClass> Items { get; set; }
}
更新结束1
XmlResult
类型派生自内置ActionResult
并包含实际的序列化逻辑。该类旨在返回文件而不是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();
}
}
答案 0 :(得分:0)
好吧,我无法让事情像我想的那样动态,但这个解决方案似乎有效。
首先,我使我的类成为一个抽象基类:
public abstract class MyBaseClass
{
public string Name { get; set; }
public int? Age { get; set; }
}
然后我为我想要的每个XML元素名称创建了派生类:
public class Person : MyBaseClass { }
public class Employee : MyBaseClass { }
然后,确保我在控制器操作方法中使用了正确的派生类是一件简单的事情。
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"
);
}
当然,实际应用中的事情有点复杂...... 上面的技术允许我将所有属性和方法保留在基类中,只使用派生类来获取所需的XML元素名称。
真正的应用程序是在运行时根据action方法的输入参数选择Person
或Employee
类。此外,逻辑被隐藏在另一组类中,其中包含一个镜像类的继承结构。由于.Net 4.6还不支持与泛型类型(仅接口和委托)的协方差,因此在通过XmlResult类返回之前,我必须经历一些更多的回转以从内部逻辑中获取正确的返回值。
示例:
内部服务类:
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();
}
}
Controller的操作方法:
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
}
}