XmlSerializationおよびxsi:SchemaLocation(xsd.exe)
-
05-07-2019 - |
質問
xsd.exeを使用して、GPXファイルの読み取り/書き込み用のC#クラスを生成しました。結果の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>
解決
これを生成された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
>属性。
他のヒント
これは自分で行う必要があります。 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";
}
もちろん、この答えは手遅れです!ただし、他の開発者にとっては便利かもしれません;-)。この問題を解決するには、自動化する必要があるため、リレーションを使用しました。
静的メソッドCreateMessageTypeを呼び出す必要があります。 schemaLocationプロパティを含まないシリアル化されたクラスである必要があります。このメソッドは、as as(ダイナミックという名前)を使用して新しいタイプを返しますが、schemaLocationプロパティを追加し、XmlRootAttributeのElementNameプロパティを設定します。
タイプの作成後、オブジェクトを作成してプロパティを設定するには、リフレクションを再度使用する必要があります。
このコードは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>
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を使用してクラスを再生成する場合、何も変更する必要はありません。