2009-09-10 16 views
12

Użyłem xsd.exe do wygenerowania klasy C# do odczytu/zapisu plików GPX. Jak uzyskać wynikowy plik XML zawierający atrybut xsi: schemaLocation np.XmlSerialization i xsi: SchemaLocation (xsd.exe)

Chcę następujący ale xsi: schemaLocation zawsze brakuje

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

Odpowiedz

33

Dodaj to do swojej generowanego C# klasy:

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

najwyraźniej narzędzie xsd.exedoes not generate atrybut schemaLocation.

+0

Doskonałe dzięki! –

+0

Co by to ustawić w schemaLocation? Lokalizacja, z której XSD.EXE go użył, prawdopodobnie nie będzie dostępna w Internecie, gdzie użytkownik 'xsi: schemaLocation' musiałby go znaleźć. –

+0

@John: Może istnieje opcja określenia wartości w pliku xsd? – dtb

2

Będziesz musiał to zrobić na własną rękę. Serializacja XML nie jest w stanie wiedzieć, gdzie w danym przypadku ma się znaleźć twój schemat.

Spróbuj tego, choć nie testowałem go jeszcze:

[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

Oczywiście ta odpowiedź jest zbyt późno! Ale może przydatne dla innych programistów ;-). Użyłem relaksu do rozwiązania tego problemu, ponieważ musiał on być zautomatyzowany.

Należy wywołać metodę statyczną CreateMessageType. musi być serializowaną klasą NIE zawierającą właściwości schemaLocation. Ta metoda zwraca nowy typ za pomocą elementu nadrzędnego (o nazwie Dynamiczny), ale dodaje właściwości schemaLocation i ustawia właściwość ElementName na wartość XmlRootAttribute.

Po utworzeniu typu należy ponownie użyć odbicia, aby utworzyć obiekt i ustawić jego właściwości.

Kod wygląda na dość bolesny w xxx, ale działa jak urok!

Zobacz kodowanie poniżej:

/// <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); 
     } 
    } 

Poniższy kod użyłem do utworzenia obiektu

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())); 

i ostatecznie idealnym wyjściowego:

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

Zamiast modyfikowania klasę wygenerowany przez xsd.exe, aby dodać atrybut schemaLocation, możesz rozszerzyć klasę i dodać ją do swojego ex zajęta klasa.

Pozwala powiedzieć, że oryginalny schemat nazywa się MySchema.xsd, a wygenerowana nazwa pliku to MySchema.cs, ​​a nazwa klasy to MySchema. Oto co wygenerowany klasa może wyglądać tak: (.. Zauważ, że klasa jest częściowe Oznacza to, że można go przedłużyć)

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

     ... 
     ... 
    } 
} 

Co trzeba do to jest utworzenie innego pliku, w tym przykładzie nazwiemy go MySchemaExtender.cs. Ten plik będzie zawierał inną definicję częściowej klasy o tej samej nazwie klasy MySchema:

[MySchemaExtender.cs]

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

    public partial class MySchema {   
    } 
} 

Teraz wystarczy ustawić atrybut schemaLocation w rozszerzonej klasie. Oto, co się ostateczna rozszerzona klasa będzie wyglądać następująco:

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

Teraz, jeśli zregenerować klasę używając xsd.exe nie trzeba nic przerabiać.

+0

Dokładnie - to powinna być zaakceptowana odpowiedź. Podejście to może dać ci ostrzeżenie, jeśli użyjesz narzędzia takiego jak StyleCop, ale możesz łatwo obejść ten problem, na przykład dodając swój fałszywy tag . –

Powiązane problemy