Question

J'ai utilisé xsd.exe pour générer une classe C # permettant de lire / écrire des fichiers GPX. Comment obtenir le fichier XML résultant pour inclure l'attribut xsi: schemaLocation par exemple.

Je veux ce qui suit mais xsi: schemaLocation est toujours manquant

<?xml version="1.0"?>
<gpx 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    version="1.1" 
    xmlns="http://www.topografix.com/GPX/1/1"
    creator="ExpertGPS 1.1 - http://www.topografix.com"
    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
</gpx>
Était-ce utile?

La solution

Ajoutez ceci à votre classe C # générée:

[XmlAttribute("schemaLocation", Namespace = XmlSchema.InstanceNamespace)]
public string xsiSchemaLocation = "http://www.topografix.com/GPX/1/1 " +
                                  "http://www.topografix.com/GPX/1/1/gpx.xsd";

Apparemment, l'outil xsd.exe ne génère pas le schemaLocation attribut.

Autres conseils

Vous devrez le faire vous-même. La sérialisation XML n'a aucun moyen de savoir où vous voulez que votre schéma aille dans tous les cas.

Essayez ceci, même si je ne l'ai pas encore testé:

[XmlRoot(ElementName = "gpx", Namespace = GPX_NAMESPACE)]
public class WhateverAGpxIs
{
    private const string GPX_NAMESPACE = "http://www.topografix.com/GPX/1/1";

    private const string XSI_NAMESPACE =
        "http://www.w3.org/2001/XMLSchema-instance";

    [XmlAttribute(AttributeName = "creator")]
    public string Creator = "ExpertGPS 1.1 - http://www.topografix.com";

    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces =
        new XmlSerializerNamespaces(
            new[]
                {
                    new XmlQualifiedName("xsi", XSI_NAMESPACE),
                    new XmlQualifiedName(string.Empty, GPX_NAMESPACE)
                });

    [XmlAttribute(AttributeName = "schemaLocation",
        Namespace = XSI_NAMESPACE)]
    public string SchemaLocation = GPX_NAMESPACE + " " +
                                   "http://www.topografix.com/GPX/1/1/gpx.xsd";

    [XmlAttribute(AttributeName = "version")]
    public string Version = "1.1";
}

Bien sûr, cette réponse est bien trop tardive! Mais peut-être utile pour d'autres développeurs ;-). J'ai utilisé la réinfection pour résoudre ce problème, car il devait être automatisé.

La méthode statique CreateMessageType doit être appelée. doit être la classe sérialisée PAS contenant la propriété schemaLocation. Cette méthode renvoie un nouveau type en utilisant comme parent (nommé Dynamic), mais ajoute les propriétés schemaLocation et définit la propriété ElementName sur XmlRootAttribute.

Après avoir créé le type, vous devez à nouveau utiliser la réflexion pour créer l'objet et définir les propriétés.

Le code ressemble à une douleur dans le xxx, mais il fonctionne à merveille!

Voir le codage ci-dessous:

/// <summary>Copying the attributes of a type to a new type</summary>
private static void copyAttributes<TMessage>(TypeBuilder dynamictype)
{
    try
    {
        //Iterate over all attributes of the TMessage class and copy these to the new type
        IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(typeof(TMessage));
        if (attributes != null)
        {
          foreach (CustomAttributeData attribute in attributes)
          {
              List<object> constructorarguments = new List<object>();
              if (attribute.ConstructorArguments != null)
              {
                  foreach (CustomAttributeTypedArgument argument in attribute.ConstructorArguments)
                  {
                      constructorarguments.Add(argument.Value);
                  }
              }

              List<FieldInfo> namedfields = new List<FieldInfo>();
              List<object> namedfieldarguments = new List<object>();

              List<PropertyInfo> namedproperties = new List<PropertyInfo>();
              List<object> namedpropertyarguments = new List<object>();

              if (attribute.NamedArguments != null)
              {
                  //Iterate over all named arguments
                  foreach (CustomAttributeNamedArgument argument in attribute.NamedArguments)
                  {
                      //Check which type of argument is found
                      if (argument.MemberInfo is FieldInfo)
                      {
                          FieldInfo field = argument.MemberInfo as FieldInfo;
                          namedfields.Add(field);
                          namedfieldarguments.Add(argument.TypedValue.Value);
                      }
                      else if (argument.MemberInfo is PropertyInfo)
                      {
                          PropertyInfo property = argument.MemberInfo as PropertyInfo;
                          namedproperties.Add(property);
                          namedpropertyarguments.Add(argument.TypedValue.Value);
                      }
                  }
              }

              //Check if the current attribute is of type XmlRoot.
              //In this case the ElementName or TypeName property must also be set
              if (attribute.Constructor.DeclaringType.Equals(typeof(XmlRootAttribute)))
              {
                  namedproperties.Add(typeof(XmlRootAttribute).GetProperty("ElementName"));
                  namedpropertyarguments.Add(typeof(TMessage).Name);
              }

              //Build the copy of the parent attribute
              CustomAttributeBuilder copyattributebuilder = new CustomAttributeBuilder(
                  attribute.Constructor,
                  constructorarguments.ToArray(),
                  namedproperties.ToArray(),
                  namedpropertyarguments.ToArray(),
                  namedfields.ToArray(),
                  namedfieldarguments.ToArray());

              //Add the attribute to the dynamic type
              dynamictype.SetCustomAttribute(copyattributebuilder);
          }
      }
  }
  catch (Exception exception)
  {
      throw new ApplicationException("Unable to copy attribute from parent type", exception);
  }
}

/// <summary>Create dynamic type for an operation message which includes the types for serialization</summary>
/// <returns>Returns dynamic type</returns>
public static Type CreateMessageType<TMessage>()
{
    try
    {
        AssemblyBuilder assemblybuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run);
            ModuleBuilder modulebuilder = assemblybuilder.DefineDynamicModule(Guid.NewGuid().ToString(), false);

            //Create type based on an unique so that it does not conflict with the OperationMessage classname
            TypeBuilder typebuilder = modulebuilder.DefineType(typeof(TMessage).Name + "Dynamic", TypeAttributes.Public | TypeAttributes.Class);

            //Set original message type as parent of the new dynamic type
            typebuilder.SetParent(typeof(TMessage));

            //Copy attributes from TMessage paren type to the dynamic type
            WMQXMLMessageTypeFactory.copyAttributes<TMessage>(typebuilder);

            //Create the xsi:schemaLocation property
            CustomAttributeBuilder attributebuilder = new CustomAttributeBuilder(
                typeof(XmlAttributeAttribute).GetConstructor(new Type[] { typeof(string) }),
                new object[] { "schemaLocation" },
                new PropertyInfo[] { typeof(XmlAttributeAttribute).GetProperty("Namespace") },
                new object[] { XmlSchema.InstanceNamespace });

            FieldBuilder schemalocationfieldbuilder = typebuilder.DefineField("SchemaLocation", typeof(string), FieldAttributes.Public);
            schemalocationfieldbuilder.SetCustomAttribute(attributebuilder);

            return typebuilder.CreateType();
        }
        catch (Exception exception)
        {
            throw new ApplicationException("Unable to create XML message type", exception);
        }
    }

Le code suivant que j'ai utilisé pour créer l'objet

Type type = WMQXMLMessageTypeFactory.CreateMessageType<TenantRequest>();

MetaData metadata = new MetaData();
metadata.ID = Guid.NewGuid().ToString();
metadata.Created = DateTime.Now;
metadata.Application = new schemasdev.local.tenant.Application();
metadata.Application.Name = "Publish Tenant";
metadata.Application.Core = ApplicationCore.PropertySystem;
NewOperation newoperation = new NewOperation();
newoperation.Tenant = new Tenant();
newoperation.Tenant.Code = "001";
newoperation.Tenant.Name = "Mister X";

object request = type.GetConstructor(new Type[0]).Invoke(new object[0]);

(request as TenantRequest).MetaData = metadata;
(request as TenantRequest).New = newoperation;

//Setting the schema location property
type.InvokeMember("SchemaLocation", System.Reflection.BindingFlags.SetField, null, request, new object[] { "http://schemasdev.local/2012-01/Tenant/1.0/Tenant.xsd" });

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(type);
stream = new System.IO.MemoryStream();
serializer.Serialize(stream, request);

Console.WriteLine(UTF8Encoding.UTF8.GetString(stream.ToArray()));

Et finalement, la sortie parfaite:

<?xml version="1.0"?>
<TenantRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:xsd="http://www.w3.org/2001/XMLSchema"   xsi:schemaLocation="http://schemasdev.local/2012-01/Tenant/1.0/Tenant.xsd" xmlns="http://schemasdev.local/2012-01/Tenant/1.0">
    <MetaData xmlns="http://schemasdev.local/2012-01/Messaging/1.0">
        <ID>b59938fd-8e68-4927-87da-6d92c609f159</ID>
        <Application>
            <Name>Publish Tenant</Name>
            <Core>PropertySystem</Core>
        </Application>
        <Created>2012-02-20T10:07:54.645424+01:00</Created>
    </MetaData>
    <New>
        <Tenant>
            <Code>001</Code>
            <Name>Mister X</Name>
        </Tenant>
    </New>
</TenantRequest>

Au lieu de modifier la classe générée par xsd.exe pour ajouter l'attribut schemaLocation, vous pouvez étendre la classe et l'ajouter à votre classe étendue.

Disons que le schéma d'origine s'appelle MySchema.xsd et que le nom du fichier généré est MySchema.cs et que le nom de la classe est MySchema. Voici à quoi pourrait ressembler la classe générée:

[MySchema.cs]

namespace MyProgram.MySchemas {
    using System.Xml.Serialization;


    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    ...
    public partial class MySchema {

       private string someField;

       ...
       ...
    }
}

(Notez que la classe est partielle. Cela signifie que nous pouvons l'étendre.)

Ce que vous devez faire, c'est créer un autre fichier. Dans cet exemple, nous l'appellerons MySchemaExtender.cs. Ce fichier contiendra une autre définition de classe partielle portant le même nom de classe MySchema:

[MySchemaExtender.cs]

namespace MyProgram.MySchemas {
    using System.Xml.Serialization;

    public partial class MySchema {        
    }
}

Il ne vous reste plus qu'à placer l'attribut schemaLocation dans la classe étendue. Voici à quoi ressemblera votre classe étendue finale:

[MySchemaExtender.cs]

namespace MyProgram.MySchemas {
    using System.Xml.Serialization;

    public partial class MySchema {
        [XmlAttribute("schemaLocation", Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)]
        public string xsiSchemaLocation = @"http://someurl/myprogram http://someurl/myprogram/MySchema.xsd";
    }
}

Maintenant, si vous régénérez la classe à l'aide de xsd.exe, vous ne devez rien modifier.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top