XmlSerialization and xsi:SchemaLocation (xsd.exe)
Asked Answered
T

4

15

I used xsd.exe to generate a C# class for reading/writing GPX files. How do I get the resultant XML file to include the xsi:schemaLocation attribute eg.

I want the following but xsi:schemaLocation is always missing

<?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>
Tachometer answered 10/9, 2009 at 23:51 Comment(0)
H
38

Add this to your generated C# class:

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

Apparently the xsd.exe tool does not generate the schemaLocation attribute.

Holism answered 11/9, 2009 at 0:12 Comment(4)
What would it set the schemaLocation to? The location from which XSD.EXE used it is not likely to be available on the web, which is where the user of xsi:schemaLocation would need to find it.Pyrophotometer
@John: Maybe there is some option to specify the value in the xsd file?Holism
a good idea would be to have this in a extra file and use partial classes to add the member this way you could generate new classes without loosing the xsiSchemaLocation.Terrill
Where does this code go in the C# class? Could you show it in context? Thank you.Dirk
A
3

Instead of modifying the class generated by xsd.exe to add the schemaLocation attribute you can extend the class and add it in your extended class.

Lets say the original schema is called MySchema.xsd and the generated file name is MySchema.cs and the class name is MySchema. Here is what the generated class might look like:

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

       ...
       ...
    }
}

(Note that the class is partial. This means we can extend it.)

What you need to do is create another file, in this example we will call it MySchemaExtender.cs. This file will contain another partial class definition with the same class name MySchema:

[MySchemaExtender.cs]

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

    public partial class MySchema {        
    }
}

Now all you need to do is put the schemaLocation attribute in the extended class. Here is what your final extended class will look like:

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

Now if you regenerate the class using xsd.exe you won't have to modify anything.

Alexalexa answered 21/7, 2013 at 18:4 Comment(1)
Exactly -- this should be the accepted answer. The approach may give you a warning if you use a tool like StyleCop, but you can easily get around this, for example by adding your own fake <autogenerated> comment tag.Gastroenterostomy
P
2

You'll have to do this on your own. There's no way for XML Serialization to know where you want your schema to go in any case.

Try this, though I haven't tested it yet:

[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";
}
Pyrophotometer answered 11/9, 2009 at 0:45 Comment(0)
O
2

Of course this answer is way too late! But maybe usefull for other developers ;-). I used relfection for solving this problem, because it had to be automated.

The static method CreateMessageType must be called. must be the serialized class NOT containing the schemaLocation property. This method return a new type using the as parent (named Dynamic), but add the schemaLocation properties and sets ElementName property to of the XmlRootAttribute.

After creating the type, reflection must again be used to create the object and set the properties.

The code looks like quite a pain in the xxx, but it works like a charm!

See the coding below:

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

The following code I used to create the object

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

And eventually the perfect output:

<?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>
Organism answered 20/2, 2012 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.