自定义 XML 格式化程序仅添加必需的命名空间
Custom XML Formatter add only required namespaces
由于客户端应用程序使用带有硬编码前缀的 xpath(不要让我开始),我不得不向 XML 添加命名空间前缀,这些前缀由我的一些 api 端点生成。
我已经成功创建了一个自定义 XmlSerializerOutputFormatter,它覆盖了 Serialize
方法,如下所示:
public class CustomXmlOutputFormatter : XmlSerializerOutputFormatter
{
protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
{
var namespaces = new XmlQualifiedName[]
{
new XmlQualifiedName("xsi", "http://www.w3.org/2001/XMLSchema-instance"),
new XmlQualifiedName("t1", "my.namespace.one"),
new XmlQualifiedName("t2", "my.namespace.two"),
new XmlQualifiedName("t3", "my.namespace.three"),
};
var nsManager = new XmlSerializerNamespaces(namespaces);
xmlSerializer.Serialize(xmlWriter, value, nsManager);
}
}
只要 XML 是使用正确的命名空间前缀生成的,这就有效。
但是,我现在在生成的每个 XML 中都得到了我的所有命名空间前缀,即使它们没有被使用。
例如,下面的 xml 仅使用 "t1" 命名空间中的项,但由于我在命名空间管理器中声明了所有三个项,所以我在 xml 中获取了所有三个项:
<t1:MyRootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t1="my.namespace.one"
xmlns:t2="my.namespace.two"
xmlns:t3="my.namespace.three">
<t1:Hello>World</t1:Hello>
</t1:MyRootNode>
这显着增加了 XML 的大小,因为我们有超过 200 个不同的命名空间,总共 可以 使用 - 但每个端点实际上只使用最多两个或三个不同的名称空间
我正在寻找的是一种仅包含被序列化对象(或其任何子对象)实际使用的命名空间的方法。
由于我所有的对象都用相关 XML 序列化属性(例如 [XmlElement]
)进行了注释,并且它们在需要时使用了命名空间参数,我想我可以写一些可怕的反射来解决问题rabbithole 并找到任何正在使用的命名空间。
但是,我希望有一个我看不到的更优雅(并且可能更明显)的解决方案:)
有什么想法吗?
以下是我最终使用反射实现此功能的方式:
protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
{
//this is a List<XmlQualifiedName>() that contains all the namespaces and prefixes we could be using - its dynamically built during the constructor from a json file for ease of use.
var allNamespaces = AllPossibleNamespaces.ToList(); //create a temp copy we can modify
//remove surplus namespaces
var usedNamespaces = GetUsedNamespaces(value.GetType());
allNamespaces.RemoveAll(n => !usedNamespaces.Contains(n.Namespace));
//now add in the default ones we always want
allNamespaces.AddRange(requiredNamespaces);
//finally, serialize the object using the namespaces and prefixes
var nsManager = new XmlSerializerNamespaces(allNamespaces.ToArray());
xmlSerializer.Serialize(xmlWriter, value,nsManager);
}
private List<string> GetUsedNamespaces(Type baseType)
{
var usedNamespaces = new List<string>();
var attribs = baseType.GetXmlAttributes();
baseType.GetProperties().ToList().ForEach(p => attribs.AddRange(p.GetXmlAttributes()));
foreach (var a in attribs)
{
var nsProp = a.GetType().GetProperty("Namespace",typeof(string));
if (nsProp!=null)
{
var ns = (string)nsProp.GetValue(a);
if (!string.IsNullOrWhiteSpace(ns) && !usedNamespaces.Contains(ns))
usedNamespaces.Add(ns);
}
}
foreach(var child in baseType.GetProperties())
{
var propType = child.PropertyType;
var type = propType;
if (propType.IsClass)
{
//dont check inside strings :)
if (propType != typeof(string))
{
foreach (var iface in propType.GetInterfaces())
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IList<>))
{
type = propType.GetGenericArguments()[0];
}
}
GetUsedNamespaces(type).ForEach(n => {
if (!usedNamespaces.Contains(n))
usedNamespaces.Add(n);
});
}
}
}
return usedNamespaces;
}
如果出现不需要链接反射的更好解决方案,我会给出可接受的答案,因为我宁愿不必这样做:)
由于客户端应用程序使用带有硬编码前缀的 xpath(不要让我开始),我不得不向 XML 添加命名空间前缀,这些前缀由我的一些 api 端点生成。
我已经成功创建了一个自定义 XmlSerializerOutputFormatter,它覆盖了 Serialize
方法,如下所示:
public class CustomXmlOutputFormatter : XmlSerializerOutputFormatter
{
protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
{
var namespaces = new XmlQualifiedName[]
{
new XmlQualifiedName("xsi", "http://www.w3.org/2001/XMLSchema-instance"),
new XmlQualifiedName("t1", "my.namespace.one"),
new XmlQualifiedName("t2", "my.namespace.two"),
new XmlQualifiedName("t3", "my.namespace.three"),
};
var nsManager = new XmlSerializerNamespaces(namespaces);
xmlSerializer.Serialize(xmlWriter, value, nsManager);
}
}
只要 XML 是使用正确的命名空间前缀生成的,这就有效。
但是,我现在在生成的每个 XML 中都得到了我的所有命名空间前缀,即使它们没有被使用。 例如,下面的 xml 仅使用 "t1" 命名空间中的项,但由于我在命名空间管理器中声明了所有三个项,所以我在 xml 中获取了所有三个项:
<t1:MyRootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t1="my.namespace.one"
xmlns:t2="my.namespace.two"
xmlns:t3="my.namespace.three">
<t1:Hello>World</t1:Hello>
</t1:MyRootNode>
这显着增加了 XML 的大小,因为我们有超过 200 个不同的命名空间,总共 可以 使用 - 但每个端点实际上只使用最多两个或三个不同的名称空间
我正在寻找的是一种仅包含被序列化对象(或其任何子对象)实际使用的命名空间的方法。
由于我所有的对象都用相关 XML 序列化属性(例如 [XmlElement]
)进行了注释,并且它们在需要时使用了命名空间参数,我想我可以写一些可怕的反射来解决问题rabbithole 并找到任何正在使用的命名空间。
但是,我希望有一个我看不到的更优雅(并且可能更明显)的解决方案:)
有什么想法吗?
以下是我最终使用反射实现此功能的方式:
protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
{
//this is a List<XmlQualifiedName>() that contains all the namespaces and prefixes we could be using - its dynamically built during the constructor from a json file for ease of use.
var allNamespaces = AllPossibleNamespaces.ToList(); //create a temp copy we can modify
//remove surplus namespaces
var usedNamespaces = GetUsedNamespaces(value.GetType());
allNamespaces.RemoveAll(n => !usedNamespaces.Contains(n.Namespace));
//now add in the default ones we always want
allNamespaces.AddRange(requiredNamespaces);
//finally, serialize the object using the namespaces and prefixes
var nsManager = new XmlSerializerNamespaces(allNamespaces.ToArray());
xmlSerializer.Serialize(xmlWriter, value,nsManager);
}
private List<string> GetUsedNamespaces(Type baseType)
{
var usedNamespaces = new List<string>();
var attribs = baseType.GetXmlAttributes();
baseType.GetProperties().ToList().ForEach(p => attribs.AddRange(p.GetXmlAttributes()));
foreach (var a in attribs)
{
var nsProp = a.GetType().GetProperty("Namespace",typeof(string));
if (nsProp!=null)
{
var ns = (string)nsProp.GetValue(a);
if (!string.IsNullOrWhiteSpace(ns) && !usedNamespaces.Contains(ns))
usedNamespaces.Add(ns);
}
}
foreach(var child in baseType.GetProperties())
{
var propType = child.PropertyType;
var type = propType;
if (propType.IsClass)
{
//dont check inside strings :)
if (propType != typeof(string))
{
foreach (var iface in propType.GetInterfaces())
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IList<>))
{
type = propType.GetGenericArguments()[0];
}
}
GetUsedNamespaces(type).ForEach(n => {
if (!usedNamespaces.Contains(n))
usedNamespaces.Add(n);
});
}
}
}
return usedNamespaces;
}
如果出现不需要链接反射的更好解决方案,我会给出可接受的答案,因为我宁愿不必这样做:)