XmlSerialization и xsi: схема расположения (xsd.exe) - PullRequest
12 голосов
/ 11 сентября 2009

Я использовал 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 ]

34 голосов
/ 11 сентября 2009

Добавьте это к вашему сгенерированному классу 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 не генерирует атрибут schemaLocation.

2 голосов
/ 21 июля 2013

Вместо того, чтобы изменять класс, сгенерированный 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, вам не нужно ничего менять.

2 голосов
/ 20 февраля 2012

Конечно, этот ответ слишком поздно! Но может быть полезно для других разработчиков ;-). Я использовал relfection для решения этой проблемы, потому что она должна была быть автоматизирована.

Статический метод CreateMessageType должен быть вызван. должен быть сериализованным классом, НЕ содержащим свойство schemaLocation. Этот метод возвращает новый тип, используя в качестве родительского элемента (с именем 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>
2 голосов
/ 11 сентября 2009

Вы должны будете сделать это самостоятельно. Сериализация 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";
}
...