इंटरफ़ेस संपत्ति का XML क्रमांकन


83

मैं XML को किसी ऐसी वस्तु को क्रमबद्ध करना चाहूंगा, जिसमें IModelObject (जो एक इंटरफ़ेस है ) की एक संपत्ति है (अन्य के अलावा )।

public class Example
{
    public IModelObject Model { get; set; }
}

जब मैं इस वर्ग की किसी वस्तु को क्रमबद्ध करने का प्रयास करता हूं, तो मुझे निम्न त्रुटि प्राप्त होती है:
"सदस्य उदाहरण को क्रमबद्ध नहीं कर सकता। उदाहरण के लिए मॉडल क्योंकि यह एक इंटरफ़ेस है।"

मैं समझता हूं कि समस्या यह है कि एक इंटरफ़ेस को क्रमबद्ध नहीं किया जा सकता है। हालाँकि, ठोस मॉडल ऑब्जेक्ट प्रकार रनटाइम तक अज्ञात है।

एक अमूर्त या ठोस प्रकार के साथ IModelObject इंटरफ़ेस को बदलना और XMLInclude के साथ विरासत का उपयोग संभव है, लेकिन एक बदसूरत वर्कअराउंड की तरह लगता है।

कोई सुझाव?

जवाबों:


116

यह केवल घोषणात्मक क्रमांकन की एक अंतर्निहित सीमा है जहां प्रकार की जानकारी आउटपुट के भीतर एम्बेडेड नहीं होती है।

<Flibble Foo="10" />वापस में बदलने की कोशिश कर रहा है

public class Flibble { public object Foo { get; set; } }

धारावाहिक कैसे जानता है कि क्या यह एक इंट, एक स्ट्रिंग, एक डबल (या कुछ और) होना चाहिए ...

इस काम को करने के लिए आपके पास कई विकल्प हैं, लेकिन अगर आप वास्तव में रनटाइम तक नहीं जानते हैं तो ऐसा करने का सबसे आसान तरीका XmlAttributeOverrides का उपयोग करना है ।

अफसोस की बात है कि यह केवल आधार वर्गों के साथ काम करेगा, इंटरफेस नहीं। आप जो सबसे अच्छा कर सकते हैं, वह संपत्ति की अनदेखी करना है जो आपकी आवश्यकताओं के लिए पर्याप्त नहीं है।

यदि आपको वास्तव में इंटरफेस के साथ रहना चाहिए तो आपके पास तीन वास्तविक विकल्प होंगे:

इसे छिपाएं और किसी अन्य संपत्ति में इससे निपटें

बदसूरत, अप्रिय बॉयलर प्लेट और बहुत दोहराव लेकिन वर्ग के अधिकांश उपभोक्ताओं को समस्या से नहीं जूझना पड़ेगा:

[XmlIgnore()]
public object Foo { get; set; }

[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
}

यह एक रखरखाव दुःस्वप्न बनने की संभावना है ...

IXmlSerializable लागू करें

इसमें पहले विकल्प की तरह ही आप चीजों पर पूर्ण नियंत्रण रखते हैं लेकिन

  • पेशेवरों
    • आपके आस-पास लटकने वाले 'नकली' गुण नहीं हैं।
    • आप लचीलेपन / संस्करण को जोड़कर सीधे xml संरचना के साथ बातचीत कर सकते हैं
  • विपक्ष
    • आप वर्ग के अन्य सभी गुणों के लिए पहिया को फिर से लागू करने के लिए समाप्त हो सकते हैं

प्रयास के दोहराव के मुद्दे पहले के समान हैं।

रैपिंग प्रकार का उपयोग करने के लिए अपनी संपत्ति को संशोधित करें

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
        reader.ReadEndElement();
    }

    public XmlSchema GetSchema() { return(null); }
}

इसके इस्तेमाल से कुछ ऐसा होगा (प्रोजेक्ट P में):

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

जो आपको देता है:

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

यह स्पष्ट रूप से वर्ग के उपयोगकर्ताओं के लिए अधिक बोझिल है, हालांकि बहुत बॉयलर प्लेट से बचा जाता है।

एक खुशहाल माध्यम हो सकता है कि XmlAnything के विचार को पहली तकनीक की 'बैकिंग' संपत्ति में मिला दिया जाए। इस तरह से अधिकांश ग्रंट कार्य आपके लिए किए जाते हैं लेकिन वर्ग के उपभोक्ताओं को आत्मनिरीक्षण के साथ भ्रम से परे कोई प्रभाव नहीं पड़ता है।


मैंने आपके दृष्टिकोण डायन रैपिंग गुणों को लागू करने की कोशिश की, लेकिन दुर्भाग्य से एक समस्या है :( क्या आप इस पोस्ट पर एक नज़र डाल सकते हैं, कृपया: stackoverflow.com/questions/7584922/…
SOReader

क्या कोई कृत्रिम परिचय FooSerialized संपत्ति है?
Gqqnbig

42

इसका समाधान DataContractSerializer के साथ प्रतिबिंब का उपयोग कर रहा है। आपको अपनी कक्षा को [DataContract] या [DataMember] के साथ चिह्नित करने की आवश्यकता नहीं है। यह किसी भी वस्तु को क्रमबद्ध करेगा, भले ही इसमें इंटरफ़ेस प्रकार के गुण हों (शब्दकोशों सहित)। यहाँ एक सरल विस्तार विधि है जो XML में किसी भी वस्तु को अनुक्रमित करेगी भले ही उसमें इंटरफेस हो (ध्यान दें कि आप इसे पुनरावर्ती रूप से चलाने के लिए भी ट्वीक कर सकते हैं)।

    public static XElement ToXML(this object o)
    {
        Type t = o.GetType();

        Type[] extraTypes = t.GetProperties()
            .Where(p => p.PropertyType.IsInterface)
            .Select(p => p.GetValue(o, null).GetType())
            .ToArray();

        DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
        StringWriter sw = new StringWriter();
        XmlTextWriter xw = new XmlTextWriter(sw);
        serializer.WriteObject(xw, o);
        return XElement.Parse(sw.ToString());
    }

LINQ एक्सप्रेशन क्या करता है, यह प्रत्येक प्रॉपर्टी को एन्यूमरेट करता है, प्रत्येक प्रॉपर्टी को लौटाता है जो कि एक इंटरफ़ेस है, उस प्रॉपर्टी की वैल्यू (अंतर्निहित वस्तु) प्राप्त करता है, उस ठोस ऑब्जेक्ट के प्रकार को एक अरै में रखता है, और जो कि serializer को जोड़ता है ज्ञात प्रकारों की सूची।

अब धारावाहिककर्ता जानता है कि यह किस प्रकार से क्रमबद्ध है, इसलिए यह अपना काम कर सकता है।


समस्या का बहुत ही सुरुचिपूर्ण और आसान समाधान। धन्यवाद!
गुलालव

2
यह एक सामान्य IList के लिए और इंटरफ़ेस के लिए काम नहीं करता है। उदाहरण के लिए IList <IMyInterface>। IMyInterface के लिए संक्षिप्त मूल्य को KnownTypes में जोड़ने की आवश्यकता है, इसके बजाय IList <IMyInterface> जोड़ा जाएगा।
गलफोर्ड

6
@ galford13x मैंने इस उदाहरण को यथासंभव सरल बनाने की कोशिश की, जबकि अभी भी बिंदु का प्रदर्शन किया जा रहा है। कभी-कभी एकल मामले में जोड़ना, जैसे पुनरावृत्ति या इंटरफ़ेस प्रकारों को पढ़ना कम स्पष्ट होता है और मुख्य बिंदु से दूर ले जाता है। कृपया आवश्यक ज्ञात प्रकारों को खींचने के लिए किसी भी अतिरिक्त जांच को जोड़ने के लिए स्वतंत्र महसूस करें। सच कहूं तो मुझे नहीं लगता कि कुछ भी है जो आप प्रतिबिंब का उपयोग नहीं कर सकते हैं। उदाहरण के लिए यह जेनेरिक पैरामीटर, stackoverflow.com/questions/557340/…
Despertar

मैं समझता हूं, मैंने केवल इस बात का उल्लेख किया है क्योंकि इंटरफ़ेस क्रमांकन के लिए पूछा गया था। मुझे लगा कि मैं दूसरों को यह बता दूंगा कि सिर पर चोट लगने से रोकने के लिए संशोधन के बिना त्रुटि की उम्मीद की जाएगी। हालाँकि, मैंने आपके कोड की सराहना की, क्योंकि मैंने [KnownType ()] विशेषता जोड़ी और आपके कोड ने मुझे परिणाम तक पहुँचाया।
galford13x

1
क्या क्रमबद्ध करते समय नामपद को अलग करने का कोई तरीका है? मैंने xmlwriterSettings का उपयोग करने की कोशिश की बजाय एक xmlwriter का उपयोग करके, मैं अधिभार का उपयोग करता हूं जहां मैं नशे के प्रकारों को पारित कर सकता हूं, लेकिन यह काम नहीं कर रहा है ...
महापुरूष

9

यदि आपको पता है कि आपका इंटरफ़ेस कार्यान्वयनकर्ता अप-फ्रंट है तो एक बहुत ही सरल हैक है जिसका उपयोग आप किसी भी पार्स कोड को लिखे बिना अपने इंटरफ़ेस प्रकार को प्राप्त करने के लिए कर सकते हैं:

public interface IInterface {}
public class KnownImplementor01 : IInterface {}
public class KnownImplementor02 : IInterface {}
public class KnownImplementor03 : IInterface {}
public class ToSerialize {
  [XmlIgnore]
  public IInterface InterfaceProperty { get; set; }
  [XmlArray("interface")]
  [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))]
  [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))]
  [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))]
  public object[] InterfacePropertySerialization {
    get { return new[] { InterfaceProperty }; ; }
    set { InterfaceProperty = (IInterface)value.Single(); }
  }
}

परिणामी xml की तर्ज पर कुछ दिखना चाहिए

 <interface><ofTypeKnownImplementor01><!-- etc... -->

1
बहुत उपयोगी है, धन्यवाद। ज्यादातर स्थितियों में मैं उन वर्गों को जानता हूं जो इंटरफ़ेस को लागू करते हैं। यह उत्तर उच्च कोटि का होना चाहिए।
जोनाह

यह सबसे आसान उपाय है। धन्यवाद!
mKay

8

आप ExtendedXmlSerializer का उपयोग कर सकते हैं । यह धारावाहिक किसी भी चाल के बिना इंटरफ़ेस संपत्ति के क्रमांकन का समर्थन करता है।

var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();

var obj = new Example
                {
                    Model = new Model { Name = "name" }
                };

var xml = serializer.Serialize(obj);

आपका xml दिखेगा:

<?xml version="1.0" encoding="utf-8"?>
<Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples">
    <Model exs:type="Model">
        <Name>name</Name>
    </Model>
</Example>

ExtendedXmlSerializer समर्थन .net 4.5 और .net Core।


3

एक अमूर्त या ठोस प्रकार के साथ IModelObject इंटरफ़ेस को बदलना और XMLInclude के साथ विरासत का उपयोग संभव है, लेकिन एक बदसूरत वर्कअराउंड की तरह लगता है।

यदि एक सार आधार का उपयोग करना संभव है तो मैं उस मार्ग की सिफारिश करूंगा। यह अभी भी हाथ से लुढ़का क्रमांकन का उपयोग करने की तुलना में क्लीनर होगा। एकमात्र समस्या जो मुझे सार आधार के साथ दिखाई देती है, वह यह है कि आपके अभी भी ठोस प्रकार की आवश्यकता है? कम से कम यह है कि मैंने इसे अतीत में कैसे उपयोग किया है, कुछ इस तरह:

public abstract class IHaveSomething
{
    public abstract string Something { get; set; }
}

public class MySomething : IHaveSomething
{
    string _sometext;
    public override string Something 
    { get { return _sometext; } set { _sometext = value; } }
}

[XmlRoot("abc")]
public class seriaized
{
    [XmlElement("item", typeof(MySomething))]
    public IHaveSomething data;
}

2

दुर्भाग्य से कोई सरल उत्तर नहीं है, क्योंकि धारावाहिक को पता नहीं है कि इंटरफ़ेस के लिए क्या करना है। मुझे MSDN पर इसे कैसे हल करना है, इस पर अधिक पूर्ण स्पष्टीकरण मिला


1

मेरे लिए दुर्भाग्य से, मेरे पास एक ऐसा मामला था जहां श्रेणीबद्ध होने के लिए गुण थे जो गुणों के साथ-साथ इंटरफेस भी थे, इसलिए मुझे प्रत्येक संपत्ति को पुन: संसाधित करने की आवश्यकता थी। इसके अलावा, कुछ इंटरफ़ेस गुणों को [XmlIgnore] के रूप में चिह्नित किया गया था, इसलिए मैं उन पर छोड़ना चाहता था। मैंने इस थ्रेड पर पाए गए विचारों को लिया और इसे पुनरावर्ती बनाने के लिए इसमें कुछ चीजें जोड़ीं। केवल डिसेरिएलाइज़ेशन कोड यहाँ दिखाया गया है:

void main()
{
    var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>();
    using (FileStream stream = new FileStream(xmlPath, FileMode.Open))
    {
        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
        var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader);

        // your code here
    }
}

DataContractSerializer GetDataContractSerializer<T>() where T : new()
{
    Type[] types = GetTypesForInterfaces<T>();

    // Filter out duplicates
    Type[] result = types.ToList().Distinct().ToList().ToArray();

    var obj = new T();
    return new DataContractSerializer(obj.GetType(), types);
}

Type[] GetTypesForInterfaces<T>() where T : new()
{
    return GetTypesForInterfaces(typeof(T));
}

Type[] GetTypesForInterfaces(Type T)
{
    Type[] result = new Type[0];
    var obj = Activator.CreateInstance(T);

    // get the type for all interface properties that are not marked as "XmlIgnore"
    Type[] types = T.GetProperties()
        .Where(p => p.PropertyType.IsInterface && 
            !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any())
        .Select(p => p.GetValue(obj, null).GetType())
        .ToArray();

    result = result.ToList().Concat(types.ToList()).ToArray();

    // do the same for each of the types identified
    foreach (Type t in types)
    {
        Type[] embeddedTypes = GetTypesForInterfaces(t);
        result = result.ToList().Concat(embeddedTypes.ToList()).ToArray();
    }
    return result;
}

1

मुझे एक सरल समाधान मिला है (आपको DataContractSerializer की आवश्यकता नहीं है), इस ब्लॉग के लिए यहां धन्यवाद: XML प्रकार व्युत्पन्न प्रकार जब बेस प्रकार किसी अन्य नामस्थान या DLL में होता है

लेकिन इस कार्यान्वयन में 2 समस्याएं बढ़ सकती हैं:

(1) क्या है अगर DerivedBase क्लास बेस के नेमस्पेस में नहीं है, या बेस नेमस्पेस पर निर्भर एक प्रोजेक्ट में और भी बुरा है, तो बेस XMLInclude DerivedBase नहीं कर सकता

(२) क्या होगा यदि हमारे पास केवल एक डीएसएल के रूप में क्लास बेस है, इसलिए फिर से बेस XMLInclude DerivedBase नहीं कर सकता है

अब तक, ...

तो 2 समस्याओं का हल XmlSerializer कंस्ट्रक्टर (प्रकार, सरणी []) का उपयोग करके है :

XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});

MSDN पर एक विस्तृत उदाहरण यहाँ दिया गया है: XmlSerializer कंस्ट्रक्टर (प्रकार, extraTypesArray [])

यह मुझे लगता है कि DataContracts या साबुन XMLs के लिए, आपको XmlRoot की जाँच करने की आवश्यकता है जैसा कि इस SO प्रश्न में यहाँ बताया गया है

एक समान जवाब इतने पर यहाँ है , लेकिन यह एक के रूप में के रूप में यह नहीं ओपी यह पहले से ही माना जाता है लगता है चिह्नित नहीं है,।


0

मेरी परियोजना में, मेरे पास एक
सूची है <IFormatStyle> FormatStyleTemplates;
विभिन्न प्रकारों से युक्त।

मैं तो ऊपर से 'XmlAnything' समाधान का उपयोग करता हूं, विभिन्न प्रकार की इस सूची को क्रमबद्ध करने के लिए। उत्पन्न xml सुंदर है।

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlArray("FormatStyleTemplates")]
    [XmlArrayItem("FormatStyle")]
    public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML
    {
        get
        {
            return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray();
        }
        set
        {
            // read the values back into some new object or whatever
            m_FormatStyleTemplates = new FormatStyleProvider(null, true);
            value.ForEach(t => m_FormatStyleTemplates.Add(t.Value));
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.