在XML序列化期间尝试动态控制元素名称时出现错误
本文关键字:错误 元素 动态控制 序列化 XML | 更新日期: 2023-09-27 18:08:51
我一直在网上研究,包括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]]
我在微软的网站上看到一个相当老的帖子,上面说这个消息应该是:Only XmlRoot and XmlType attributes may be specified...
。果然,如果删除除XmlRoot和XmlType之外的所有覆盖,错误就会被清除,但是呈现的XML不符合我的需要。
我使用XmlSerializer
Overrides构造函数,因为我必须动态设置根和第一个子元素的名称。相同的类需要在不同的情况下生成不同的XML元素名。虽然这个示例只有两个字段,但是实际被序列化的类有大约100个字段。
那么,当直接序列化List<MyClass>
对象时,我如何控制根元素和直接子元素的名称?
<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>
下面是一些简化的代码,演示了我的问题。我使用了一个基本的MVC。. Net应用程序从Visual Studio 2013的模板。
// 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; }
}
更新结束
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();
}
}
嗯,我不能使事情像我想要的那样动态,但这个解决方案似乎有效。
首先,我创建了一个抽象基类:
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();
}
}
控制器的动作方法:
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
}
}