在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>对象时,我如何控制根元素和直接子元素的名称?

目标是使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>

下面是一些简化的代码,演示了我的问题。我使用了一个基本的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();
    }
}

在XML序列化期间尝试动态控制元素名称时出现错误

嗯,我不能使事情像我想要的那样动态,但这个解决方案似乎有效。

首先,我创建了一个抽象基类:

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方法的输入参数选择PersonEmployee类。此外,逻辑被隐藏在另一组类中,这些类具有镜像类的继承结构。由于。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
    }
}