1. ホーム
  2. .net

[解決済み] XmlSerializer: 不要な xsi と xsd 名前空間を削除する。

2022-05-14 07:34:07

質問

XmlSerializerがルート要素にデフォルトの名前空間を書き込まないように設定する方法はありますか?

私が得るものはこれです。

<?xml ...>
<rootelement xmlns:xsi="..." xmlns:xsd="...">
</rootelement>

で、両方のxmlns宣言を削除したい。

の重複 : xmlns="..." を取得せずにオブジェクトをXMLにシリアライズする方法は?

どのように解決するのですか?

Daveが私の答えを繰り返すよう要求してきたので、私は .NETでオブジェクトをシリアライズするとき、すべてのxsiとxsd名前空間を省略する。 に対する私の回答を繰り返すようにとのことでしたので、私はこの投稿を更新し、前述のリンクからここに私の回答を繰り返しました。この回答で使用される例は、他の質問で使用されたのと同じ例です。以下は、そのままコピーしたものです。


Microsoft のドキュメントとオンラインのいくつかのソリューションを読んだ後、私はこの問題の解決策を発見しました。それは、組み込みの XmlSerializer によるカスタム XML シリアライゼーションと IXmlSerialiazble .

白くするには、同じ MyTypeWithNamespaces XMLサンプルを使用します。

[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int epoch) : this( )
    {
        this._label = label;
        this._epoch = epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._epoch; }
        set { this._epoch = value; }
    }
    private int _epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

このクラスはこれで終わりです。さて、一部の人は XmlSerializerNamespaces オブジェクトがクラスのどこかにあることに異議を唱える人もいましたが、ご覧の通り、私はそれをデフォルトのコンストラクタにきちんとしまい込み、名前空間を返すパブリックプロパティを公開しました。

さて、クラスをシリアライズするときは、次のコードを使用します。

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/

/*****
  Per @dbc, since MyTypeWithNamespaces has a XmlRootAttribute decorating the class,
  You may be able to get away with NOT using this .ctor and use the simple
  XmlSerializer(Type) .ctor.
  Also, be careful not to use serializer creation in loops, as it could lead
  to extensive memory issues due to how serializers are cached (or not...).
  See @dbc's comment and link to SO Q&A for more details.

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });
****/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces));

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

これを実行すると、次のような出力が得られるはずです。

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

私は最近のプロジェクトで、Webサービス呼び出しのためにXMLにシリアライズされるクラスの深い階層で、この方法をうまく使いました。Microsoft のドキュメントでは、一般にアクセス可能な XmlSerializerNamespaces メンバを作成した後、それをどうするかについて Microsoft のドキュメントはあまり明確ではないので、多くの人はそれを無駄だと考えています。しかし、彼らのドキュメントに従い、上に示した方法でそれを使用することで、サポートされていない動作や、quot;rolling your own" のシリアライズに頼ることなく、XmlSerializer がクラスのために XML を生成する方法をカスタマイズすることができ、それを実装することによって IXmlSerializable .

この回答によって、標準的な xsixsd によって生成される名前空間は XmlSerializer .

UPDATE: 名前空間をすべて削除するというOPの質問に答えたことを確認したいのですが。上記の私のコードはこのために動作します。さて、上記の例では、実際にはすべての名前空間を削除することはできません (使用中の名前空間が 2 つあるため)。XML ドキュメントのどこかで、次のような記述をする必要があります。 xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo . 例のクラスがより大きな文書の一部である場合、上のどこかでどちらか一方(または両方)の名前空間を宣言しなければなりません。 AbracadbraWhoohoo . そうでない場合は、名前空間の一方または両方にある要素を何らかのプレフィックスで装飾する必要があります(デフォルトの名前空間を2つ持つことはできませんよね)。ですから、この例では Abracadabra がデフォルトの名前空間です。私は自分の MyTypeWithNamespaces クラスの中で、名前空間プレフィックスを Whoohoo のような名前空間プレフィックスを追加します。

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

さて、クラス定義の中で、私は <Label/> 要素が名前空間 "urn:Whoohoo" にあるので、これ以上何もする必要はありません。上記のシリアライズコードをそのまま使ってクラスをシリアライズすると、次のような出力が得られます。

<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

なぜなら <Label> はドキュメントの残りの部分とは異なる名前空間にあるため、何らかの方法で名前空間で "decorated"する必要があります。まだ xsixsd という名前空間があります。


これで他の質問に対する私の回答は終わりです。しかし、名前空間を使用しないことについての OP の質問には、まだ本当に対処していないと思うので、確実に回答したいと思います。次のように仮定します。 <Label> がドキュメントの残りの部分と同じ名前空間の一部であると仮定します(この場合 urn:Abracadabra :

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

あなたのコンストラクタは、私の最初のコード例と同じように、デフォルトの名前空間を取得するためのパブリックプロパティと一緒に表示されます。

// As noted below, per Microsoft's documentation, if the class exposes a public
// member of type XmlSerializerNamespaces decorated with the 
// XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
// namespaces during serialization.
public MyTypeWithNamespaces( )
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
    });
}

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
    get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;

次に、後で MyTypeWithNamespaces オブジェクトを使用してシリアライズするコードで、上記のように呼び出すことになります。

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

...

// Above, you'd setup your XmlTextWriter.

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

そして、その XmlSerializer は、出力に追加の名前空間がない、すぐ上に示されたのと同じXMLを吐き出します。

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>