2009-09-10 16 views
12

J'ai utilisé xsd.exe pour générer une classe C# pour lire/écrire des fichiers GPX. Comment puis-je obtenir le fichier XML résultant pour inclure l'attribut xsi: schemaLocation par exemple.XmlSerialization et xsi: SchemaLocation (xsd.exe)

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> 

Répondre

33

Ajouter à votre produit classe C#:

[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.exedoes not generate l'attribut schemaLocation.

+0

Parfait merci! –

+0

À quoi définirait-il schemaLocation? L'emplacement à partir duquel XSD.EXE l'a utilisé n'est pas susceptible d'être disponible sur le web, où l'utilisateur de 'xsi: schemaLocation' aurait besoin de le trouver. –

+0

@John: Peut-être qu'il existe une option pour spécifier la valeur dans le fichier xsd? – dtb

2

Vous devrez le faire vous-même. Il n'y a aucun moyen pour que la sérialisation XML sache où vous voulez que votre schéma aille dans tous les cas.

Essayez ceci, bien que 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"; 
} 
2

Bien sûr, cette réponse est trop tard! Mais peut-être utile pour d'autres développeurs ;-). J'ai utilisé la relfection 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 ne contenant pas la propriété schemaLocation. Cette méthode renvoie un nouveau type en utilisant le parent as (nommé Dynamic), mais ajoute les propriétés schemaLocation et définit la propriété ElementName sur XmlRootAttribute. Après la création du type, la réflexion doit à nouveau être utilisée pour créer l'objet et définir les propriétés.

Le code ressemble à une douleur dans le xxx, mais cela fonctionne comme un charme!

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 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> 
3

Au lieu de modifier la classe généré par xsd.exe pour ajouter l'attribut schemaLocation vous pouvez étendre la classe et l'ajouter dans votre ex classe tendue. Supposons que le schéma d'origine s'appelle MySchema.xsd et que le nom de fichier généré est MySchema.cs et que le nom de la classe est MySchema. Voici ce que la classe générée pourrait ressembler à: (.. Notez que la classe est partielle Cela signifie que nous pouvons l'étendre)

[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; 

     ... 
     ... 
    } 
} 

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

[MySchemaExtender.cs]

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

    public partial class MySchema {   
    } 
} 

Maintenant tout ce que vous devez faire est de mettre l'attribut schemaLocation dans la classe étendue. Voici ce que votre classe finale étendu ressemblera:

[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 en utilisant xsd.exe vous ne devez rien modifier.

+0

Exactement - cela devrait être la réponse acceptée. L'approche peut vous donner un avertissement si vous utilisez un outil comme StyleCop, mais vous pouvez facilement contourner cela, par exemple en ajoutant votre propre balise de commentaire fausse . –