XmlSerializer: ลบ xsi และ xsd namespaces ที่ไม่จำเป็น


132

มีวิธีกำหนดค่า XmlSerializer เพื่อไม่ให้เขียนเนมสเปซเริ่มต้นในองค์ประกอบรากหรือไม่

สิ่งที่ฉันได้รับคือ:

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

และฉันต้องการลบการประกาศ xmlns ทั้งสอง

ซ้ำกันของ : วิธีการทำให้วัตถุเป็นอนุกรมเป็น XML โดยไม่ได้รับ xmlns =” …”?

คำตอบ:


63

เนื่องจาก Dave ขอให้ฉันตอบซ้ำการละเว้นเนมสเปซ xsi และ xsd ทั้งหมดเมื่อทำให้เป็นอนุกรมวัตถุใน. NETฉันจึงอัปเดตโพสต์นี้และตอบซ้ำที่นี่จากลิงค์ที่กล่าวถึงข้างต้น ตัวอย่างที่ใช้ในคำตอบนี้เป็นตัวอย่างเดียวกับที่ใช้สำหรับคำถามอื่น สิ่งต่อไปนี้คือการคัดลอกคำต่อคำ


หลังจากอ่านเอกสารของ 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");

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

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

ฉันใช้วิธีนี้สำเร็จแล้วในโปรเจ็กต์ล่าสุดที่มีคลาสแบบลึกที่ต่ออนุกรมกับ XML สำหรับการเรียกใช้บริการเว็บ เอกสารของ Microsoft ไม่ชัดเจนว่าจะทำอย่างไรกับXmlSerializerNamespacesสมาชิกที่เข้าถึงได้แบบสาธารณะเมื่อคุณสร้างมันขึ้นมาและหลายคนคิดว่ามันไม่มีประโยชน์ แต่ต่อไปนี้เอกสารของพวกเขาและใช้มันในลักษณะที่แสดงข้างต้นคุณสามารถกำหนดวิธีการ XmlSerializer สร้าง XML สำหรับชั้นเรียนของคุณโดยไม่ต้อง resorting พฤติกรรมที่ไม่สนับสนุนหรือ "กลิ้งของคุณเอง" IXmlSerializableเป็นอันดับโดยการใช้

ฉันหวังเป็นอย่างยิ่งว่าคำตอบนี้จะนำไปสู่ความสงบในทันทีวิธีกำจัดมาตรฐานxsiและxsdเนมสเปซที่สร้างโดยไฟล์XmlSerializer.

UPDATE: ฉันแค่ต้องการให้แน่ใจว่าฉันตอบคำถามของ OP เกี่ยวกับการลบเนมสเปซทั้งหมด รหัสของฉันด้านบนจะใช้ได้กับสิ่งนี้ ให้ฉันแสดงวิธี ตอนนี้ในตัวอย่างข้างต้นคุณไม่สามารถกำจัดเนมสเปซทั้งหมดได้ (เนื่องจากมีการใช้งานเนมสเปซสองชื่อ) ที่ไหนสักแห่งในเอกสาร XML ของคุณคุณจะต้องมีบางอย่างเช่นxmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. ถ้าชั้นในตัวอย่างที่เป็นส่วนหนึ่งของเอกสารที่มีขนาดใหญ่แล้วที่ไหนสักแห่งข้างต้น namespace ต้องได้รับการประกาศอย่างใดอย่างหนึ่งอย่างใดอย่างหนึ่ง (หรือทั้งสอง) และAbracadbra Whoohooถ้าไม่เช่นนั้นองค์ประกอบในเนมสเปซหนึ่งหรือทั้งสองจะต้องได้รับการตกแต่งด้วยคำนำหน้าของการจัดเรียงบางประเภท (คุณไม่สามารถมีเนมสเปซเริ่มต้นสองรายการได้ใช่ไหม) ดังนั้นสำหรับตัวอย่างนี้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>อยู่ในเนมสเปซที่แตกต่างจากส่วนที่เหลือของเอกสารจึงต้อง "ตกแต่ง" ด้วยเนมสเปซในบางครั้ง สังเกตว่ายังไม่มีxsiและxsdเนมสเปซ


นี่เป็นการสิ้นสุดคำตอบของฉันสำหรับคำถามอื่น แต่ฉันต้องการให้แน่ใจว่าฉันตอบคำถามของ 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>

เพื่อความสมบูรณ์บางทีคุณควรใส่คำตอบที่ถูกต้องไว้ที่นี่แทนที่จะอ้างถึงเพียงอย่างเดียวและฉันสนใจที่จะทราบว่าคุณสรุปได้อย่างไรว่าเป็น 'พฤติกรรมที่ไม่รองรับ'
Dave Van den Eynde

1
มาที่นี่อีกครั้งเพื่อตรวจสอบสิ่งนี้เนื่องจากเป็นคำอธิบายที่ตรงไปตรงมาที่สุดที่ฉันพบ ขอบคุณ @fourpastmidnight
Andre Albuquerque

2
ฉันไม่เข้าใจสำหรับคำตอบสุดท้ายของ OP คุณยังคงใช้เนมสเปซระหว่างการทำให้เป็นอนุกรม "โกศ: Abracadabra" (ตัวสร้าง) เหตุใดจึงไม่รวมอยู่ในผลลัพธ์สุดท้าย ไม่ควรใช้ OP: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = XmlSerializerNamespaces ใหม่ (ใหม่ [] {XmlQualifiedName.Empty});
dparkar

2
นี่คือคำตอบที่ถูกต้องแม้ว่าจะไม่ได้รับการโหวตมากที่สุด สิ่ง onky ที่ไม่ทำงานสำหรับฉันคือฉันมีการแทนที่โดยXmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws); var xtw = XmlTextWriter.Create(memStm, xws);
Leonel Sanches da Silva

1
เป็นเวลานานแล้วที่ฉันเขียนคำตอบนี้ XmlTextWriter.Createส่งคืนXmlWriterอินสแตนซ์(นามธรรม?) ดังนั้น @ Preza8 ถูกต้องคุณจะสูญเสียความสามารถในการตั้งค่าอื่น ๆXmlTextWriterคุณสมบัติ -specific (อย่างน้อยไม่โดยไม่ต้องลงหล่อมัน) XmlTextWriterด้วยเหตุนี้นักแสดงที่เฉพาะเจาะจงเพื่อ
fourpastmidnight

257
//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

//Add an empty namespace and empty value
ns.Add("", "");

//Create the serializer
XmlSerializer slz = new XmlSerializer(someType);

//Serialize the object with our own namespaces (notice the overload)
slz.Serialize(myXmlTextWriter, someObject, ns)

24
อืม ... พวกคุณเป็นกบฏ มันบอกอย่างชัดเจนที่msdn.microsoft.com/en-us/library/…ว่าคุณทำอย่างนั้นไม่ได้
Ralph Lavelle

บูลย่ะ! (สำหรับการเอาชนะสิ่งที่คุณพูดว่าคุณทำไม่ได้)
granadaCoder

3
ฉันไม่แน่ใจว่าเหตุใดจึง "ไม่รองรับ" แต่สิ่งนี้ทำได้ตามที่ฉันต้องการ
Dan Bechard

8
คำตอบนี้สร้างเนมสเปซ "xmlns: d1p1" และ "xmlns: q1" นี่คืออะไร?
Leonel Sanches da Silva

2
รหัสนี้ใช้ได้กับการทำให้เป็นอนุกรมที่เรียบง่ายจริงๆโดยไม่มีคำจำกัดความของเนมสเปซอื่น ๆ สำหรับคำจำกัดความหลายเนมสเปซคำตอบที่ใช้ได้คือคำตอบที่ยอมรับ
Leonel Sanches da Silva

6

มีทางเลือกอื่น - คุณสามารถระบุสมาชิกของประเภทXmlSerializerNamespacesในประเภทที่จะทำให้เป็นอนุกรมได้ ตกแต่งด้วยแอตทริบิวต์XmlNamespaceDeclarations เพิ่มคำนำหน้าเนมสเปซและ URI ให้กับสมาชิกนั้น จากนั้นการทำให้เป็นอนุกรมใด ๆ ที่ไม่ได้ระบุ XmlSerializerNamespaces อย่างชัดเจนจะใช้คำนำหน้าเนมสเปซ + คู่ URI ที่คุณใส่ไว้ในประเภทของคุณ

ตัวอย่างรหัสสมมติว่าเป็นประเภทของคุณ:

[XmlRoot(Namespace = "urn:mycompany.2009")]
public class Person {
  [XmlAttribute] 
  public bool Known;
  [XmlElement]
  public string Name;
  [XmlNamespaceDeclarations]
  public XmlSerializerNamespaces xmlns;
}

คุณสามารถทำได้:

var p = new Person
  { 
      Name = "Charley",
      Known = false, 
      xmlns = new XmlSerializerNamespaces()
  }
p.xmlns.Add("",""); // default namespace is emoty
p.xmlns.Add("c", "urn:mycompany.2009");

และนั่นหมายความว่าการทำให้เป็นอนุกรมของอินสแตนซ์นั้นที่ไม่ได้ระบุชุดคำนำหน้า + URI ของตัวเองจะใช้คำนำหน้า "p" สำหรับเนมสเปซ "urn: mycompany.2009" นอกจากนี้ยังจะละเว้น xsi และ xsd namespaces

ความแตกต่างตรงนี้คือคุณกำลังเพิ่ม XmlSerializerNamespaces ให้กับประเภทนั้น ๆ แทนที่จะใช้อย่างชัดเจนในการเรียกใช้ XmlSerializer.Serialize () ซึ่งหมายความว่าหากอินสแตนซ์ประเภทของคุณถูกทำให้เป็นอนุกรมโดยรหัสที่คุณไม่ได้เป็นเจ้าของ (ตัวอย่างเช่นในกองบริการเว็บ) และรหัสนั้นไม่ได้ระบุ XmlSerializerNamespaces อย่างชัดเจนซีเรียลไลเซอร์นั้นจะใช้เนมสเปซที่ให้ไว้ในอินสแตนซ์


1. ไม่เห็นความแตกต่าง คุณยังคงเพิ่มเนมสเปซเริ่มต้นในอินสแตนซ์ของ XmlSerializerNamespaces
Dave Van den Eynde

3
2. สิ่งนี้ก่อให้เกิดมลพิษในชั้นเรียนมากขึ้น วัตถุประสงค์ของฉันคือไม่ใช้เนมสเปซบางอย่างวัตถุประสงค์ของฉันคือไม่ใช้เนมสเปซเลย
Dave Van den Eynde

ฉันได้เพิ่มหมายเหตุเกี่ยวกับความแตกต่างระหว่างแนวทางนี้และการระบุ XmlSerializerNamespaces ระหว่างการทำให้เป็นอนุกรมเท่านั้น
Cheeso

0

ฉันกำลังใช้:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        const string DEFAULT_NAMESPACE = "http://www.something.org/schema";
        var serializer = new XmlSerializer(typeof(Person), DEFAULT_NAMESPACE);
        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("", DEFAULT_NAMESPACE);

        using (var stream = new MemoryStream())
        {
            var someone = new Person
            {
                FirstName = "Donald",
                LastName = "Duck"
            };
            serializer.Serialize(stream, someone, namespaces);
            stream.Position = 0;
            using (var reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

เพื่อรับ XML ต่อไปนี้:

<?xml version="1.0"?>
<Person xmlns="http://www.something.org/schema">
  <FirstName>Donald</FirstName>
  <LastName>Duck</LastName>
</Person>

หากคุณไม่ต้องการเนมสเปซเพียงตั้งค่า DEFAULT_NAMESPACE เป็น ""


แม้ว่าคำถามนี้จะมีอายุมากกว่า 10 ปี แต่ประเด็นในตอนนั้นคือการมีเนื้อหา XML ที่ไม่มีการประกาศเนมสเปซใด ๆ เลย
Dave Van den Eynde

1
ถ้าฉันเพิ่มคำตอบของตัวเองสำหรับคำถามที่มีอายุ 10 ปีเป็นเพราะคำตอบที่ได้รับการยอมรับนั้นอ่านได้นานกว่าพระคัมภีร์ในฉบับสมบูรณ์
Maxence

และคำตอบที่ได้รับการโหวตมากที่สุดส่งเสริมแนวทาง (เนมสเปซว่าง) ซึ่งไม่แนะนำ
Maxence

ฉันไม่สามารถช่วยได้ ฉันสามารถทำให้คำตอบที่ยอมรับได้เป็นคำตอบที่ฉันเชื่อว่าถูกต้องที่สุดเท่านั้น
Dave Van den Eynde
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.