J'ai compris que les types anonymes sont marqués comme privés par le compilateur et que les propriétés sont en lecture seule. Y a-t-il un moyen de les sérialiser en XML (sans désérialisation) ? Ça fonctionne avec JSON, comment puis-je le faire avec XML ?
Réponses
Trop de publicités?La réponse ci-dessous gère les IEnumerables de la manière dont j'avais besoin et transformera ceci :
new
{
Foo = new[]
{
new { Name = "One" },
new { Name = "Two" },
},
Bar = new[]
{
new { Name = "Three" },
new { Name = "Four" },
},
}
en ceci :
One
Two
Three
Four
Donc voilà, encore une autre variante de la réponse de Matthew :
public static class Tools
{
private static readonly Type[] WriteTypes = new[] {
typeof(string),
typeof(Enum),
typeof(DateTime), typeof(DateTime?),
typeof(DateTimeOffset), typeof(DateTimeOffset?),
typeof(int), typeof(int?),
typeof(decimal), typeof(decimal?),
typeof(Guid), typeof(Guid?),
};
public static bool IsSimpleType(this Type type)
{
return type.IsPrimitive || WriteTypes.Contains(type);
}
public static object ToXml(this object input)
{
return input.ToXml(null);
}
public static object ToXml(this object input, string element)
{
if (input == null)
return null;
if (string.IsNullOrEmpty(element))
element = "object";
element = XmlConvert.EncodeName(element);
var ret = new XElement(element);
if (input != null)
{
var type = input.GetType();
if (input is IEnumerable && !type.IsSimpleType())
{
var elements = (input as IEnumerable)
.Select(m => m.ToXml(element))
.ToArray();
return elements;
}
else
{
var props = type.GetProperties();
var elements = from prop in props
let name = XmlConvert.EncodeName(prop.Name)
let val = prop.GetValue(input, null)
let value = prop.PropertyType.IsSimpleType()
? new XElement(name, val)
: val.ToXml(name)
where value != null
select value;
ret.Add(elements);
}
}
return ret;
}
}
Un autre utilisateur Pavel a mentionné l'utilisation des méthodes Newtonsoft.Json pour une conversion rapide en 2 lignes
var jsonText = JsonConvert.SerializeObject(data);
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);
Je suggèrerais que cela pourrait en fait être une seule ligne de code :)
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
XmlDocument doc = JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(data));
Mon premier article pour contribuer à un site web qui aide toujours beaucoup Ci-dessous se trouve une solution complète qui peut être utilisée avec .net core (testée avec la version 3.1) appelez simplement ConvertAnonymousToType.Convert("MyNewType", target, out Type newType) Le retour sera un objet que vous pouvez utiliser pour renvoyer à l'intérieur d'un contrôleur ou pour le sérialiser manuellement (System.Xml.Serialization.XmlSerializer). Utilisation de l'objet comme retour à l'intérieur d'un contrôleur : 1. configurez le service (Startup.cs > ConfigureServices > services.AddMvcCore(options => options.OutputFormatters.Add(new XmlSerializerOutputFormatter());),
2. Action cible : utilisez [FormatFilter] et [Route(".......{format}")], 3. Renvoyez l'objet
Si un type anonyme supplémentaire doit composer la réponse (Metadata, Resultset, etc.), DynamicTypeBuilder.CreateNewObject peut être utilisé pour le service :)
Merci encore
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Configuration
{
public static class DynamicTypeBuilder
{
public static object CreateNewObject(string typeName, Dictionary properties, out Type newType)
{
newType = CompileResultType(typeName, properties);
return Activator.CreateInstance(newType);
}
public static Type CompileResultType(string typeName, Dictionary properties)
{
TypeBuilder tb = GetTypeBuilder(typeName);
ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
//Ajouter des propriétés
properties.ToList().ForEach(p => CreateProperty(tb, p.Key, p.Value));
//Type créé avec des propriétés
Type objectType = tb.CreateType();
return objectType;
}
//Créer un type avec une configuration standard
private static TypeBuilder GetTypeBuilder(string typeName)
{
string assemblyName = typeName + "InternalAssembly";
var an = new AssemblyName(assemblyName);
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType(typeName,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
return tb;
}
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
}
public static class ConvertAnonymousToType
{
public static object Convert(string typeName, object target, out Type newType)
{
var properties = GetProperties(target);
newType = DynamicTypeBuilder.CompileResultType(typeName, properties);
return Convert(newType, target);
}
public static object Convert(Type type, object target)
{
if (target.GetType().Name == typeof(List<>).Name)
{
var newListType = typeof(List<>).MakeGenericType(type);
var newList = Activator.CreateInstance(newListType);
MethodInfo addMethod = newList.GetType().GetMethod("Add");
((IList)target).ToList().ForEach(e =>
{
addMethod.Invoke(newList, new object[] { ConvertObject(type, e) });
});
return newList;
}
else
{
return ConvertObject(type, target);
}
}
private static object ConvertObject(Type type, object refObject)
{
Dictionary properties = new Dictionary();
object newObject = Activator.CreateInstance(type);
var propertiesOrg = refObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
var propertiesDes = newObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
propertiesOrg.ForEach(po => propertiesDes.First(pd => pd.Name == po.Name).SetValue(newObject, po.GetValue(refObject)));
return newObject;
}
private static Dictionary GetProperties(object target)
{
object objectRef = target;
if (target.GetType().Name == typeof(List<>).Name) objectRef = ((List)target).ToList()[0];
Dictionary properties = new Dictionary();
var lstProperties = objectRef.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
lstProperties.ForEach(p => properties.Add(p.Name, p.PropertyType));
return properties;
}
}
}
J'ai trouvé cette page extrêmement utile. Je passe des anciennes références web SOAP à quelque chose de pris en charge dans .net standard. J'ai modifié le code pour pouvoir le sérialiser dans un format qui fonctionnera avec les services web SOAP. C'est rudimentaire, mais cela peut convertir les éléments object[]
en éléments , les autres tableaux simples sont correctement sérialisés et vous pouvez spécifier un espace de noms ou utiliser l'attribut XmlTypeAttribute
sur vos modèles pour attribuer des espaces de noms.
public static class AnonymousTypeSerializer
{
private static readonly XNamespace _xmlInstanceNs = "http://www.w3.org/2001/XMLSchema-instance";
private static readonly Type[] WriteTypes = new[] {
typeof(string), typeof(DateTime), typeof(Enum),
typeof(decimal), typeof(Guid),
};
private static readonly Dictionary SerializedTypeNames = new Dictionary()
{
{ typeof(int), "int" },
{ typeof(long), "long" }
};
///
/// Convertit un type anonyme en XElement.
///
/// L'entrée.
/// Renvoie l'objet sous sa forme XML dans un XElement.
public static XElement? ToXml(this object? input) => input.ToXml(null);
///
/// Convertit un type anonyme en XElement.
///
/// L'entrée.
/// Le nom de l'élément.
/// Renvoie l'objet sous sa forme XML dans un XElement.
public static XElement? ToXml(this object? input, string? element, XNamespace? ns = null) => _ToXml(input, element, ns ?? XNamespace.None);
private static XElement? _ToXml(object? input, string? element, XNamespace? ns = null, int? arrayIndex = null, string? arrayName = null)
{
if (input == null)
return null;
if (string.IsNullOrEmpty(element))
{
string name = input.GetType().Name;
element = name.Contains("AnonymousType")
? "Object"
: arrayIndex != null
? $"{arrayName}"
: name;
}
element = XmlConvert.EncodeName(element);
var ret = new XElement(ns.GetName(element));
if (input != null)
{
var type = input.GetType();
var props = type.GetProperties();
var xmlType = type.GetCustomAttribute(true);
if (xmlType != null && xmlType.Namespace != null)
ns = xmlType.Namespace;
ret.Add(props.Select(p =>
{
var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
string name = XmlConvert.EncodeName(p.Name);
object val = pType.IsArray ? "array" : p.GetValue(input, null);
var value = pType.IsArray
? GetArrayElement(p, (Array)p.GetValue(input, null), ns)
: pType.IsSimpleType() || pType.IsEnum
? new XElement(ns.GetName(name), val)
: val.ToXml(name, ns);
return value;
}).Where(v => v != null));
}
return ret;
}
///
/// Obtient l'élément de tableau.
///
/// Les informations sur la propriété.
/// L'objet d'entrée.
/// Renvoie un XElement avec la collection du tableau en tant qu'éléments enfants.
private static XElement GetArrayElement(PropertyInfo info, Array? input, XNamespace ns)
{
string name = XmlConvert.EncodeName(info.Name);
if (input == null)
return null;
int arrayCount = input.GetLength(0);
var elementType = input.GetType().GetElementType();
bool isAnyType = elementType == typeof(object);
XElement rootElement;
if (isAnyType)
rootElement = new XElement(ns + name, new XAttribute(XNamespace.Xmlns + "xsi", _xmlInstanceNs));
else
rootElement = new XElement(ns + name);
for (int i = 0; i < arrayCount; i++)
{
object val = input.GetValue(i);
if (isAnyType)
{
var valType = val.GetType();
var xmlType = valType.GetCustomAttribute(true);
var valNs = ns;
if (xmlType != null && xmlType.Namespace != null)
valNs = xmlType.Namespace;
// Créer l'élément anyType
var childElement = new XElement(ns + "anyType", new XAttribute(XNamespace.Xmlns + $"p{rootElement.Elements().Count()}", valNs));
// Créer l'attribut de type
string nsPrefix = childElement.GetPrefixOfNamespace(valNs);
childElement.Add(new XAttribute(_xmlInstanceNs + "type",
!string.IsNullOrEmpty(nsPrefix)
? $"{nsPrefix}:{valType.Name}"
: valType.Name));
// Créer les éléments enfants
var inner = _ToXml(val, null, ns, i, "anyType");
childElement.Add(inner!.Elements());
// C'est terminé
rootElement.Add(childElement);
}
else
{
if (!SerializedTypeNames.TryGetValue(elementType, out string typeName))
typeName = elementType.Name;
rootElement.Add(elementType.IsSimpleType()
? new XElement(ns.GetName(typeName.ToLower()), val)
: _ToXml(val, null, ns, i, typeName));
}
}
return rootElement;
}
public static bool IsSimpleType(this Type type) =>
type.IsPrimitive || WriteTypes.Contains(type);
}
- Réponses précédentes
- Plus de réponses