Tuần tự hóa XML của thuộc tính giao diện


83

Tôi muốn XML tuần tự hóa một đối tượng có (trong số các đối tượng khác) thuộc tính kiểu IModelObject (là một giao diện).

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

Khi tôi cố gắng tuần tự hóa một đối tượng của lớp này, tôi nhận được lỗi sau:
"Không thể tuần tự hóa thành viên Ví dụ.Model thuộc loại Ví dụ vì nó là một giao diện."

Tôi hiểu rằng vấn đề là một giao diện không thể được tuần tự hóa. Tuy nhiên, loại đối tượng Model cụ thể vẫn chưa được biết cho đến thời gian chạy.

Có thể thay thế giao diện IModelObject bằng một kiểu trừu tượng hoặc cụ thể và sử dụng kế thừa với XMLInclude, nhưng có vẻ như đây là một cách giải quyết xấu.

Bất kỳ đề xuất?

Câu trả lời:


116

Đây chỉ đơn giản là một hạn chế cố hữu của tuần tự hóa khai báo trong đó thông tin kiểu không được nhúng trong đầu ra.

Đang cố gắng chuyển đổi <Flibble Foo="10" />lại thành

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

Làm thế nào để trình tuần tự biết liệu nó phải là một int, một chuỗi, một kép (hoặc một cái gì đó khác) ...

Để thực hiện công việc này, bạn có một số tùy chọn nhưng nếu bạn thực sự không biết cho đến thời gian chạy, cách dễ nhất để làm điều này có thể là sử dụng XmlAttributeOverrides .

Đáng buồn là điều này sẽ chỉ hoạt động với các lớp cơ sở, không phải giao diện. Tốt nhất bạn có thể làm ở đó là bỏ qua tài sản không đủ cho nhu cầu của bạn.

Nếu bạn thực sự phải ở lại với các giao diện, bạn có ba lựa chọn thực sự:

Giấu nó và xử lý nó trong một tài sản khác

Tấm lò hơi xấu xí, khó chịu và lặp đi lặp lại nhiều nhưng hầu hết người tiêu dùng của tầng lớp này sẽ không phải đối mặt với vấn đề:

[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*/ } 
}

Điều này có thể trở thành một cơn ác mộng bảo trì ...

Triển khai IXmlSerializable

Tương tự như tùy chọn đầu tiên, bạn có toàn quyền kiểm soát mọi thứ nhưng

  • Ưu điểm
    • Bạn không có các thuộc tính 'giả' khó chịu quanh quẩn.
    • bạn có thể tương tác trực tiếp với cấu trúc xml thêm tính linh hoạt / lập phiên bản
  • Nhược điểm
    • bạn có thể phải triển khai lại bánh xe cho tất cả các thuộc tính khác trên lớp

Các vấn đề về nỗ lực trùng lặp cũng tương tự như lần đầu tiên.

Sửa đổi thuộc tính của bạn để sử dụng kiểu gói

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

Sử dụng điều này sẽ liên quan đến một cái gì đó như (trong dự án 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);
    }
}

cung cấp cho bạn:

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

Điều này rõ ràng là cồng kềnh hơn cho người dùng của lớp mặc dù tránh được nhiều tấm lò hơi.

Một phương tiện hài lòng có thể hợp nhất ý tưởng XmlAnything vào thuộc tính 'hỗ trợ' của kỹ thuật đầu tiên. Bằng cách này, hầu hết công việc càu nhàu được thực hiện cho bạn nhưng những người tiêu dùng thuộc tầng lớp này không bị ảnh hưởng gì ngoài sự nhầm lẫn với việc xem xét nội tâm.


Tôi cố gắng để thực hiện gói tính cách tiếp cận phù thủy của mình nhưng tiếc là có một vấn đề :( bạn có thể có một cái nhìn vào bài đăng này, xin vui lòng: stackoverflow.com/questions/7584922/...
SOReader

Có bất kỳ tài sản FooSerialized giới thiệu artical nào không?
Gqqnbig,

42

Giải pháp cho điều này là sử dụng phản chiếu với DataContractSerializer. Bạn thậm chí không phải đánh dấu lớp của mình bằng [DataContract] hoặc [DataMember]. Nó sẽ tuần tự hóa bất kỳ đối tượng nào, bất kể nó có thuộc tính kiểu giao diện (bao gồm từ điển) thành xml hay không. Đây là một phương thức mở rộng đơn giản sẽ tuần tự hóa bất kỳ đối tượng nào thành XML ngay cả khi nó có giao diện (lưu ý rằng bạn cũng có thể điều chỉnh điều này để chạy đệ quy).

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

những gì biểu thức LINQ làm là nó liệt kê từng thuộc tính, trả về từng thuộc tính là một giao diện, nhận giá trị của thuộc tính đó (đối tượng cơ bản), nhận loại của đối tượng cụ thể đó đặt nó vào một mảng và thêm nó vào trình tuần tự danh sách các loại đã biết.

Bây giờ bộ tuần tự hóa biết làm thế nào về các loại mà nó đang tuần tự hóa để nó có thể thực hiện công việc của mình.


Giải pháp rất thanh lịch và dễ dàng cho vấn đề. Cảm ơn!
Ghlouw

2
Điều này dường như không hoạt động đối với một IList chung và giao diện. ví dụ: IList <IMyInterface>. Tuy nhiên, giá trị kết hợp cho IMyInterface cần được thêm vào knownTypes, thay vào đó, IList <IMyInterface> sẽ được thêm vào.
galford13x

6
@ galford13x Tôi đã cố gắng làm cho ví dụ này đơn giản nhất có thể trong khi vẫn chứng minh được quan điểm. Việc thêm vào từng trường hợp đơn lẻ, chẳng hạn như đệ quy hoặc các loại giao diện làm cho việc đọc trở nên kém rõ ràng hơn và làm mất đi điểm chính. Vui lòng thêm bất kỳ kiểm tra bổ sung nào để lấy các loại đã biết cần thiết. Thành thật mà nói, tôi không nghĩ rằng có bất cứ điều gì bạn không thể nhận được bằng cách sử dụng phản chiếu. Ví dụ: điều này sẽ nhận được loại thông số chung, stackoverflow.com/questions/557340/…
Despertar

Tôi hiểu, tôi chỉ đề cập đến điều này vì câu hỏi yêu cầu tuần tự hóa giao diện. Tôi đã nghĩ rằng tôi sẽ cho người khác biết lỗi sẽ xảy ra mà không cần sửa đổi để ngăn chặn việc đập đầu vào phần của họ. Tuy nhiên, tôi đánh giá cao mã của bạn vì tôi đã thêm thuộc tính [knownType ()] và mã của bạn dẫn tôi đến kết quả.
galford13x

1
Có cách nào để bỏ qua bảng tên khi tuần tự hóa không? Tôi cố gắng để xmlwriterSettings sử dụng sử dụng một XmlWriter thay vào đó, tôi sử dụng quá tải, nơi tôi có thể vượt qua các loại addtional, nhưng nó không làm việc ...
Legends

9

Nếu bạn biết những người triển khai giao diện của mình từ trước, bạn có thể sử dụng một cách hack khá đơn giản để loại giao diện của mình được tuần tự hóa mà không cần viết bất kỳ mã phân tích cú pháp nào:

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 kết quả sẽ trông giống như một cái gì đó dọc theo dòng

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

1
Rất hữu ích, cảm ơn. Trong hầu hết các tình huống, tôi biết các lớp triển khai giao diện. Câu trả lời này nên cao hơn imo.
Jonah

Đây là giải pháp dễ dàng nhất. Cảm ơn bạn!
mKay

8

Bạn có thể sử dụng ExtendedXmlSerializer . Bộ tuần tự hóa này hỗ trợ tuần tự hóa thuộc tính giao diện mà không cần bất kỳ thủ thuật nào.

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

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

var xml = serializer.Serialize(obj);

Xml của bạn sẽ giống như sau:

<?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 hỗ trợ .net 4.5 và .net Core.


3

Có thể thay thế giao diện IModelObject bằng một kiểu trừu tượng hoặc cụ thể và sử dụng kế thừa với XMLInclude, nhưng có vẻ như đây là một cách giải quyết không tốt.

Nếu có thể sử dụng một cơ sở trừu tượng, tôi sẽ giới thiệu tuyến đường đó. Nó vẫn sẽ sạch hơn so với việc sử dụng tuần tự hóa cuộn bằng tay. Vấn đề duy nhất tôi thấy với cơ sở trừu tượng là bạn vẫn cần loại bê tông? Ít nhất đó là cách tôi đã sử dụng nó trong quá khứ, đại loại như:

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

Thật không may, không có câu trả lời đơn giản, vì bộ tuần tự hóa không biết những gì để tuần tự hóa cho một giao diện. Tôi đã tìm thấy một giải thích đầy đủ hơn về cách giải quyết vấn đề này trên MSDN


1

Thật không may cho tôi, tôi đã gặp trường hợp lớp được tuần tự hóa có các thuộc tính cũng có giao diện là thuộc tính, vì vậy tôi cần phải xử lý đệ quy từng thuộc tính. Ngoài ra, một số thuộc tính giao diện được đánh dấu là [XmlIgnore], vì vậy tôi muốn bỏ qua những thuộc tính đó. Tôi lấy ý tưởng mà tôi tìm thấy trên chủ đề này và thêm một số thứ vào đó để làm cho nó đệ quy. Chỉ có mã deserialization được hiển thị ở đây:

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

Tôi đã tìm thấy một giải pháp đơn giản hơn (bạn không cần DataContractSerializer), nhờ blog này ở đây: XML tuần tự hóa các loại dẫn xuất khi loại cơ sở nằm trong một không gian tên khác hoặc DLL

Nhưng có 2 vấn đề có thể phát sinh trong quá trình triển khai này:

(1) Điều gì sẽ xảy ra nếu DerivedBase không có trong không gian tên của lớp Base, hoặc thậm chí tệ hơn trong một dự án phụ thuộc vào không gian tên Base, vì vậy Base không thể XMLInclude DerivedBase

(2) Điều gì sẽ xảy ra nếu chúng ta chỉ có lớp Base dưới dạng dll, vì vậy một lần nữa Base không thể XMLInclude DerivedBase

Cho đến bây giờ, ...

Vì vậy, giải pháp cho 2 vấn đề là sử dụng XmlSerializer Constructor (Loại, mảng []) :

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

Một ví dụ chi tiết được cung cấp tại đây trên MSDN: XmlSerializer Constructor (Loại, extraTypesArray [])

Với tôi, dường như đối với DataContracts hoặc Soap XMLs, bạn cần kiểm tra XmlRoot như đã đề cập ở đây trong câu hỏi SO này .

Một câu trả lời tương tự ở đây trên SO nhưng nó không được đánh dấu là một, vì dường như OP đã không xem xét nó.


0

trong dự án của tôi, tôi có một
Danh sách <IFormatStyle> FormatStyleTemplates;
chứa các loại khác nhau.

Sau đó, tôi sử dụng giải pháp 'XmlAnything' từ phía trên, để tuần tự hóa danh sách các loại khác nhau này. Xml được tạo ra rất đẹp.

    [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));
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.