XmlSerialization和xsi:SchemaLocation(xsd.exe)

时间:2009-09-10 23:51:24

标签: c# xsd xml-serialization xsd.exe gpx

我使用xsd.exe生成一个C#类来读取/写入GPX文件。如何获取生成的XML文件以包含xsi:schemaLocation属性 例如

我想要以下内容,但xsi:schemaLocation始终缺失

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

4 个答案:

答案 0 :(得分:34)

将此添加到生成的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";

显然是xsd.exe工具does not generate schemaLocation属性。

答案 1 :(得分:2)

你必须自己做这件事。在任何情况下,XML序列化都无法知道您希望架构的位置。

试试这个,虽然我还没有测试过:

[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 :(得分:2)

当然这个答案太迟了!但也许对其他开发者有用;-)。我使用relfection来解决这个问题,因为它必须是自动化的。

必须调用静态方法CreateMessageType。必须是不包含schemaLocation属性的序列化类。此方法使用as parent(名为Dynamic)返回一个新类型,但添加schemaLocation属性并将ElementName属性设置为XmlRootAttribute。

创建类型后,必须再次使用反射来创建对象并设置属性。

代码在xxx中看起来很痛苦,但它就像一个魅力!

请参阅以下编码:

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

以下代码我用来创建对象

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

最终是完美的输出:

<?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 :(得分:2)

您可以扩展类并将其添加到扩展类中,而不是修改xsd.exe生成的类来添加schemaLocation属性。

假设原始模式名为MySchema.xsd,生成的文件名为MySchema.cs,类名为MySchema。以下是生成的类的外观:

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

       ...
       ...
    }
}

(请注意,该类是部分的。这意味着我们可以扩展它。)

您需要做的是创建另一个文件,在本例中我们将其称为MySchemaExtender.cs。该文件将包含另一个具有相同类名MySchema的部分类定义:

[MySchemaExtender.cs]

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

    public partial class MySchema {        
    }
}

现在您需要做的就是将schemaLocation属性放在扩展类中。这是你的最终扩展课程的样子:

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

现在,如果使用xsd.exe重新生成类,则无需修改任何内容。