XmlSerializer: gereksiz xsi ve xsd ad alanlarını kaldırın


Yanıtlar:


63

Dave benden .NET'te bir nesneyi serileştirirken tüm xsi ve xsd ad alanlarının çıkarılması konusundaki cevabımı tekrar etmemi istediğinden, bu yazıyı güncelledim ve cevabımı burada yukarıda belirtilen bağlantıdan tekrarladım. Bu cevapta kullanılan örnek, diğer soru için kullanılan aynı örnektir. Aşağıdakiler kelimesi kelimesine kopyalanır.


Microsoft'un belgelerini ve çeşitli çözümlerini çevrimiçi olarak okuduktan sonra, bu sorunun çözümünü keşfettim. Hem yerleşik hem de XmlSerializerözel XML serileştirme ile çalışır IXmlSerialiazble.

Whit için, MyTypeWithNamespacesşu ana kadar bu sorunun cevaplarında kullanılan XML örneğinin aynısını kullanacağım .

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

Hepsi bu sınıf için. Şimdi, bazıları birXmlSerializerNamespaces sınıflarının yerinde nesneye ; ancak görebileceğiniz gibi, onu varsayılan kurucuda özenle sakladım ve ad alanlarını döndürmek için bir genel özelliği açığa çıkardım.

Şimdi, sınıfı serileştirme zamanı geldiğinde, aşağıdaki kodu kullanırsınız:

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

Bunu yaptıktan sonra aşağıdaki çıktıyı almalısınız:

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

Bu yöntemi, web servis çağrıları için XML'e serileştirilmiş derin bir sınıf hiyerarşisiyle yeni bir projede başarıyla kullandım. Microsoft'un belgeleri, bir XmlSerializerNamespaceskez oluşturduktan sonra herkes tarafından erişilebilen üye ile ne yapılacağı konusunda çok net değil ve pek çoğu bunun faydasız olduğunu düşünüyor. Ancak belgelerini izleyerek ve yukarıda gösterilen şekilde kullanarak, XmlSerializer'ın desteklenmeyen davranışa başvurmadan veya uygulayarak "kendi serileştirmenizi yuvarlayarak" sınıflarınız için XML üretme şeklini özelleştirebilirsiniz IXmlSerializable.

Umarım bu yanıt, .tarafından oluşturulan standartlardan xsive xsdad alanlarından nasıl kurtulacağımızı bir kez ve sonsuza kadar dinlendirir XmlSerializer.

GÜNCELLEME: OP'nin tüm ad alanlarını kaldırmayla ilgili sorusunu yanıtladığımdan emin olmak istiyorum. Yukarıdaki kodum bunun için çalışacak; size nasıl olduğunu göstereyim. Şimdi, yukarıdaki örnekte, gerçekten tüm ad alanlarından kurtulamazsınız (çünkü kullanımda iki ad alanı vardır). XML belgenizin bir yerinde, buna benzer bir şeye ihtiyacınız olacak xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Örnekteki sınıf daha büyük bir belgenin parçasıysa, o zaman bir ad alanının üzerinde bir yerde, biri (veya her ikisi için) Abracadbrave Whoohoo. Değilse, ad alanlarının biri veya her ikisindeki öğe bir tür önekle dekore edilmelidir (iki varsayılan ad alanınız olamaz, değil mi?). Dolayısıyla, bu örnek Abracadabraiçin varsayılan ad alanıdır. MyTypeWithNamespacesSınıfımın içine ad alanı için şu şekilde bir ad alanı öneki ekleyebilirim Whoohoo:

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

Şimdi, sınıf tanımımda, <Label/>öğenin ad alanında olduğunu belirttim "urn:Whoohoo", bu nedenle daha fazla bir şey yapmam gerekmiyor. Şimdi yukarıdaki serileştirme kodumu değiştirmeden kullanarak sınıfı serileştirdiğimde, bu çıktı:

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

Çünkü <Label>belgenin geri kalanından farklı bir ad alanında, bu, someway, bir ad ile "dekore" olmalıdır. Hiçbir hala var olduğunu Bildirimi xsive xsdad.


Bu, diğer soruya verdiğim cevabı bitiriyor. Ancak OP'nin ad alanı kullanmama konusundaki sorusunu yanıtladığımdan emin olmak istedim, çünkü bunu henüz tam olarak ele almadığımı hissediyorum. Bunun <Label>belgenin geri kalanıyla aynı ad alanının parçası olduğunu varsayalım, bu durumda urn:Abracadabra:

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

Oluşturucunuz, varsayılan ad alanını almak için public özelliğiyle birlikte ilk kod örneğimde göründüğü gibi görünecektir:

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

Daha sonra, MyTypeWithNamespacesnesneyi serileştirmek için kullanan kodunuzda, yukarıda yaptığım gibi onu çağırırsınız:

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

Ve XmlSerializerçıktıda hiçbir ek ad alanı olmadan hemen yukarıda gösterildiği gibi aynı XML'i geri verir:

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

Tamlık için, belki de sadece ona atıfta bulunmak yerine doğru cevabı buraya eklemelisiniz ve ayrıca bunun 'desteklenmeyen davranış' olduğu sonucuna nasıl vardığınızı bilmek istiyorum.
Dave Van den Eynde

1
Bunu kontrol etmek için tekrar geldim, çünkü bulduğum en basit açıklama buydu. Teşekkürler @fourpastmidnight
Andre Albuquerque

2
Anlayamıyorum, son OP'nin cevabı için, serileştirme sırasında hala bir ad alanı kullanıyorsunuz "urn: Abracadabra" (kurucu), bu neden son çıktıya dahil edilmiyor? OP şunları kullanmamalıdır: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar

2
En çok oylanan olmasa da bu doğru cevaptır. Benim için işe yaramayan onky şey, XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);yerine geçmek zorunda kalmamdı var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva

1
Bu cevabı yazmayalı epey oldu. XmlTextWriter.Create(soyut?) bir XmlWriterörnek döndürür . Öyleyse @ Preza8 doğru, diğer XmlTextWriter-özel özellikleri (en azından alt- çevirmeden değil), dolayısıyla özel atama yapma yeteneğini kaybedersiniz 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
Hmmm ... sizler asisiniz. Msdn.microsoft.com/en-us/library/… adresinde bunu yapamayacağınızı açıkça söylüyor .
Ralph Lavelle

Bool Yah! (
MS'nin

3
Neden "desteklenmiyor" olduğundan emin değilim, ama bu tam olarak istediğimi yapıyor.
Dan Bechard

8
Bu cevap "xmlns: d1p1" ve "xmlns: q1" ad alanlarını oluşturur. Bu nedir?
Leonel Sanches da Silva

2
Bu kod, diğer ad alanı tanımları olmadan gerçekten çok basit serileştirmeler için çalışır. Birden çok ad alanı tanımı için, çalışma yanıtı kabul edilen yanıttır.
Leonel Sanches da Silva

6

Bir alternatif var - serileştirilecek türde XmlSerializerNamespaces türünde bir üye sağlayabilirsiniz . Bunu XmlNamespaceDeclarations özniteliğiyle süsleyin . Ad alanı öneklerini ve URI'leri bu üyeye ekleyin. Ardından, bir XmlSerializerNamespaces'i açıkça sağlamayan herhangi bir serileştirme, türünüze koyduğunuz ad alanı öneki + URI çiftlerini kullanır.

Örnek kod, bunun türünüz olduğunu varsayalım:

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

Bunu yapabilirsiniz:

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

Ve bu, o örneğin kendi önek + URI çiftleri kümesini belirtmeyen herhangi bir serileştirmenin "urn: sirketim.2009" ad alanı için "p" önekini kullanacağı anlamına gelir. Ayrıca xsi ve xsd ad alanlarını da atlar.

Buradaki fark, XmlSerializer.Serialize () çağrısında açıkça kullanmak yerine, XmlSerializerNamespaces'i türün kendisine eklemenizdir. Bu, türünüzün bir örneğinin sahip olmadığınız kod tarafından serileştirilmesi (örneğin bir web hizmetleri yığınında) ve bu kodun açıkça bir XmlSerializerNamespaces sağlamaması durumunda, bu serileştiricinin örnekte sağlanan ad alanlarını kullanacağı anlamına gelir.


1. Farkı görmüyorum. Hala bir XmlSerializerNamespaces örneğine varsayılan ad alanını ekliyorsunuz.
Dave Van den Eynde

3
2. Bu sınıfları daha fazla kirletmektedir. Amacım belirli isim alanlarını kullanmak değil, amacım isim alanlarını hiç kullanmamak.
Dave Van den Eynde

Bu yaklaşım ile XmlSerializerNamespaces'i yalnızca serileştirme sırasında belirtme arasındaki fark hakkında bir not ekledim.
Cheeso

0

Kullanıyorum:

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

Aşağıdaki XML'yi almak için:

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

Ad alanını istemiyorsanız, sadece DEFAULT_NAMESPACE'i "" olarak ayarlayın.


Bu soru 10 yıldan daha eski olmasına rağmen, o zamanlar asıl mesele, herhangi bir ad alanı bildirimi içermeyen bir XML gövdesine sahip olmaktı.
Dave Van den Eynde

1
10 yıllık bir soruya kendi cevabımı eklersem, bunun nedeni, kabul edilen cevabın okunması için Kutsal Kitabın tam baskısından daha uzun olmasıdır.
Maxence

Ve en çok oylanan cevap, tavsiye edilmeyen bir yaklaşımı (boş ad alanı) teşvik eder.
Maxence

Ben yardım edemem. Kabul edilen yanıtı yalnızca en doğru olduğuna inandığım yanıt verebilirim.
Dave Van den Eynde
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.