简体   繁体   English

通常接受的方法来避免每个派生类的KnownType属性

[英]Generally accepted way to avoid KnownType attribute for every derived class

Is there a generally accepted way to avoid having to use KnownType attributes on WCF services? 是否有一种普遍接受的方法来避免在WCF服务上使用KnownType属性? I've been doing some research, and it looks like there are two options: 我一直在做一些研究,看起来有两种选择:

  1. Data contract resolver 数据合同解析器
  2. NetDataContractSerializer NetDataContractSerializer

I'm not a big fan of having to statically add KnownType attributes every time I add a new type, hence wanting to avoid it. 我不是每次添加新类型时都必须静态添加KnownType属性的忠实粉丝,因此我想避免它。

Is there a third option that should be used? 是否应该使用第三种选择? If so, what is it? 如果是这样,它是什么? If not, which of the above two options are the right way to go? 如果没有,上述两种选择中的哪一种是正确的选择?

Edit - use a method 编辑 - 使用方法

A third option would be to use reflection 第三种选择是使用反射

[DataContract]
[KnownType("DerivedTypes")]
public abstract class FooBase
{
    private static Type[] DerivedTypes()
    {
        return typeof(FooBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
    }
}

I wanted to post what seems to be the simplest, most elegant solution that I can think of so far. 我想发布迄今为止我能想到的最简单,最优雅的解决方案。 If another answer comes along that's better, I'll go with that. 如果另一个答案更好,我会继续这样做。 But for now, this worked well. 但就目前而言,这很有效。

The base class, with only one KnownType attribute, pointing to a method called DerivedTypes() : 只有一个 KnownType属性的基类指向名为DerivedTypes()的方法:

[KnownType("DerivedTypes")]
[DataContract]
public abstract class TaskBase : EntityBase
{
    // other class members here

    private static Type[] DerivedTypes()
    {
        return typeof(TaskBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
    }
}

The GetDerivedTypes() method, in a separate ReflectionUtility class: GetDerivedTypes()方法,在单独的ReflectionUtility类中:

public static IEnumerable<Type> GetDerivedTypes(this Type baseType, Assembly assembly)
{
    var types = from t in assembly.GetTypes()
                where t.IsSubclassOf(baseType)
                select t;

    return types;
}

The method mentioned by Bob will work as long as all involved classes are in the same assembly. 只要所有涉及的类都在同一个程序集中,Bob提到的方法就可以工作。

The following method will work across assemblies: 以下方法适用于程序集:

[DataContract]
[KnownType("GetDerivedTypes")]
public class BaseClass
{
  public static List<Type> DerivedTypes = new List<Type>();

  private static IEnumerable<Type> GetDerivedTypes()
  {
    return DerivedTypes;
  }
}


[DataContract]
public class DerivedClass : BaseClass
{
  //static constructor
  static DerivedClass()
  {
    BaseClass.DerivedTypes.Add(typeof(DerivedClass)); 
  }
}

If you don't like attributes everywhere then you can use configuration file. 如果您不喜欢各处的属性,那么您可以使用配置文件。

<system.runtime.serialization>
   <dataContractSerializer>
      <declaredTypes>
         <add type = "Contact,Host,Version=1.0.0.0,Culture=neutral,
                                                              PublicKeyToken=null">
            <knownType type = "Customer,MyClassLibrary,Version=1.0.0.0,
                                             Culture=neutral,PublicKeyToken=null"/>
         </add>
      </declaredTypes>
   </dataContractSerializer>
</system.runtime.serialization>

You can implement IXmlSerializable in your custom types and handle its complexity manually. 您可以在自定义类型中实现IXmlSerializable并手动处理其复杂性。 Following you can find a sample code: 您可以在下面找到示例代码:

[XmlRoot("ComplexTypeA")]
public class ComplexTypeA : IXmlSerializable
{
    public int Value { get; set; }

    public void WriteXml (XmlWriter writer)
    {
        writer.WriteAttributeString("Type", this.GetType().FullName);
        writer.WriteValue(this.Value.ToString());
    }

    public void ReadXml (XmlReader reader)
    {
        reader.MoveToContent();
        if (reader.HasAttributes) {
            if (reader.GetAttribute("Type") == this.GetType().FullName) {
                this.Value = int.Parse(reader.ReadString());
            }
        }
    }

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

[XmlRoot("ComplexTypeB")]
public class ComplexTypeB : IXmlSerializable
{
    public string Value { get; set; }

    public void WriteXml (XmlWriter writer)
    {
        writer.WriteAttributeString("Type", this.GetType().FullName);
        writer.WriteValue(this.Value);
    }

    public void ReadXml (XmlReader reader)
    {
        reader.MoveToContent();
        if (reader.HasAttributes) {
            if (reader.GetAttribute("Type") == this.GetType().FullName) {
                this.Value = reader.ReadString();
            }
        }
    }

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


[XmlRoot("ComplexTypeC")]
public class ComplexTypeC : IXmlSerializable
{
    public Object ComplexObj { get; set; }

    public void WriteXml (XmlWriter writer)
    {
        writer.WriteAttributeString("Type", this.GetType().FullName);
        if (this.ComplexObj != null)
        {
            writer.WriteAttributeString("IsNull", "False");
            writer.WriteAttributeString("SubType", this.ComplexObj.GetType().FullName);
            if (this.ComplexObj is ComplexTypeA)
            {
                writer.WriteAttributeString("HasValue", "True");
                XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA));
                serializer.Serialize(writer, this.ComplexObj as ComplexTypeA);
            }
            else if (tthis.ComplexObj is ComplexTypeB)
            {
                writer.WriteAttributeString("HasValue", "True");
                XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB));
                serializer.Serialize(writer, this.ComplexObj as ComplexTypeB);
            }
            else
            {
                writer.WriteAttributeString("HasValue", "False");
            }
        }
        else
        {
            writer.WriteAttributeString("IsNull", "True");
        }
    }

    public void ReadXml (XmlReader reader)
    {
        reader.MoveToContent();
        if (reader.HasAttributes) {
            if (reader.GetAttribute("Type") == this.GetType().FullName) {
                if ((reader.GetAttribute("IsNull") == "False") && (reader.GetAttribute("HasValue") == "True")) {
                    if (reader.GetAttribute("SubType") == typeof(ComplexTypeA).FullName)
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA));
                        this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeA;
                    }
                    else if (reader.GetAttribute("SubType") == typeof(ComplexTypeB).FullName)
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB));
                        this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeB;
                    }
                }
            }
        }
    }

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

Hope it helps. 希望能帮助到你。

Here's my variant on the accepted answer: 这是我接受的答案的变体:

    private static IEnumerable<Type> GetKnownTypes() {
        Type baseType = typeof(MyBaseType);
        return AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(x => x.DefinedTypes)
            .Where(x => x.IsClass && !x.IsAbstract && x.GetCustomAttribute<DataContractAttribute>() != null && baseType.IsAssignableFrom(x));
    }

The differences are: 不同之处是:

  1. Looks at all loaded assemblies. 查看所有已加载的程序集。
  2. Checks some bits we are interested in (DataContract I think is required if you're using DataContractJsonSerializer) such as being a concrete class. 检查我们感兴趣的一些位(如果你使用的是DataContractJsonSerializer,我认为是必需的DataContract),比如是一个具体的类。
  3. You can use isSubclassOf here, I tend to prefer IsAssignableFrom in general to catch all overridden variants. 你可以在这里使用isSubclassOf,我倾向于更喜欢IsAssignableFrom来捕获所有被覆盖的变体。 In particular I think it works with generics. 特别是我认为它适用于泛型。
  4. Take advantage of KnownTypes accepting an IEnumerable (if it matters in this case, probably not) instead of converting to an array. 利用KnownTypes接受IEnumerable(如果在这种情况下很重要,可能不是),而不是转换为数组。

I'd rather extract my custom types all at once and use it during serialization/deserialization. 我宁愿一次性提取我的自定义类型,并在序列化/反序列化期间使用它。 After reading this post, it took me a while to understand where to inject this list of types to be useful for serializer object. 阅读这篇文章之后,我花了一些时间来了解在哪里注入这个类型列表对序列化程序对象有用。 The answer was quite easy: this list is to be used as one of the input arguments of constructor of serializer object. 答案很简单:此列表将用作序列化程序对象的构造函数的输入参数之一。

1- I'm using two static generic methods for serialization and deserialization, this may be more or less the way others also do the job, or at least it is very clear for making comparison with your code: 1-我使用两种静态通用方法进行序列化和反序列化,这可能或多或少与其他人一样,或者至少它与你的代码进行比较非常清楚:

    public static byte[] Serialize<T>(T obj)
    {
        var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes());
        var stream = new MemoryStream();
        using (var writer =
            XmlDictionaryWriter.CreateBinaryWriter(stream))
        {
            serializer.WriteObject(writer, obj);
        }
        return stream.ToArray();
    }
    public static T Deserialize<T>(byte[] data)
    {
        var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes());
        using (var stream = new MemoryStream(data))
        using (var reader =
            XmlDictionaryReader.CreateBinaryReader(
                stream, XmlDictionaryReaderQuotas.Max))
        {
            return (T)serializer.ReadObject(reader);
        }
    }

2- Please pay attention to constructor of DataContractSerializer. 2-请注意DataContractSerializer的构造函数。 We have a second argument there, which is the entry point for injecting your known types to serializer object. 我们在那里有第二个参数,它是将已知类型注入序列化程序对象的入口点。

3- I'm using a static method for extracting all of my own defined types from my own assemblies. 3-我正在使用静态方法从我自己的程序集中提取所有自己定义的类型。 your code for this static method may look like this: 您的静态方法的代码可能如下所示:

    private static Type[] KnownTypes { get; set; }
    public static Type[] ResolveKnownTypes()
    {
        if (MyGlobalObject.KnownTypes == null)
        {
            List<Type> t = new List<Type>();
            List<AssemblyName> c = System.Reflection.Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(b => b.Name == "DeveloperCode" | b.Name == "Library").ToList();
            foreach (AssemblyName n in c)
            {
                System.Reflection.Assembly a = System.Reflection.Assembly.Load(n);
                t.AddRange(a.GetTypes().ToList());
            }
            MyGlobalObject.KnownTypes = t.ToArray();
        }
        return IOChannel.KnownTypes;
    }

Since I was not involved in WCF (I only needed a binary serialization for file operation), my solution may not exactly address the WCF architecture, but there must be access to constructor of serializer object from somewhere. 由于我没有参与WCF(我只需要文件操作的二进制序列化),我的解决方案可能不完全解决WCF架构,但必须从某个地方访问序列化器对象的构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM