partial or full object serialization

2019-09-06 08:42发布

问题:

Consider the following Student defintion:

public class Student
{
    public Guid Id {get; set;}
    public String FirstName {get; set;}
    public String LastName { get; set; }
}

Using C# serialization attributes, how can you apply two different serialization configurations?

When the object is passed to the DataContractSerializer, the user could specify "idOnly" (partial) or "full" serialization.

I have a two runtime use cases:

  1. Only serialize the Guid
  2. Full serialization of the Object.

回答1:

I don't know what kind of serialization are you talking about. But if you use the BinaryFormatter, the way to go is to make the Student class implement the ISerializable.

Then, you can do something like:

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
  info.AddValue("Id", Id);

  if (SomeCondition)
  {
    info.AddValue("FirstName", FirstName);
    info.AddValue("LastName", LastName);
  }
}

Where SomeCondition may use a static (or thread-static) variable or some information inside the StreamingContext.



回答2:

OK, delete the answer for XmlSerializer because you're using DataContractSerializer.

One way you can accomplish this with DataContractSerializer is by use of surrogates. A surrogate basically is a replacement class that you swap in for one of your "real" classes while serializing, deserializing, and creating schemas. You can use this trick to replace your full Student class with a simple StudentId class depending (e.g.) upon the state of some ThreadStatic state variable indicating whether the full or partial student is to be serialized. (I use ThreadStatic in case you might have multiple threads serializing data in parallel.)

Thus your Student class would become something like this:

[DataContract()]
public class Student
{
    [DataMember]
    public Guid Id { get; set; }
    [DataMember]
    public String FirstName { get; set; }
    [DataMember]
    public String LastName { get; set; }
}

[DataContract()]
public class StudentId
{
    [DataMember]
    public Guid Id { get; set; }
}

And then your global flag:

public static class SerializationFlags
{
    [ThreadStatic]
    static bool studentGuidOnly;

    public static bool StudentGuidOnly 
    {
        get { return studentGuidOnly; }
        set { studentGuidOnly = value; }
    }
}

Next you must create a IDataContractSurrogate class telling the DataContractSerializer what replacements to make. In this example you will conditionally replace Student when only the Id is desired. Since you are only doing serialization, not deserialization or schema generation, most methods can remain unimplemented:

public class StudentSurrogate : IDataContractSurrogate
{
    #region IDataContractSurrogate Members

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public Type GetDataContractType(Type type)
    {
        if (type == typeof(Student) && SerializationFlags.StudentGuidOnly)
        {
            return typeof(StudentId);
        }
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        throw new NotImplementedException();
    }

    public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
    {
        throw new NotImplementedException();
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj != null)
        {
            var type = obj.GetType();
            if (type == typeof(Student) && SerializationFlags.StudentGuidOnly)
            {
                var surrogate = new StudentId
                {
                    Id = ((Student)obj).Id,
                };
                return surrogate;
            }
        }
        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        throw new NotImplementedException();
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        throw new NotImplementedException();
    }

    #endregion
}

And finally, here is an example of how it is used:

public static class DataContractSerializerHelper
{
    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }

    public static string GetXml<T>(T obj, DataContractSerializer serializer) where T : class
    {
        using (var textWriter = new StringWriter())
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars = "    "; // The indentation used in the test string.
            using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static string GetXml<T>(T obj) where T : class
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(T));
        return GetXml(obj, serializer);
    }
}

public static class SurrogateTest
{
    public static void Test()
    {
        Student kid = new Student();
        kid.Id = Guid.NewGuid();
        kid.FirstName = "foo";
        kid.LastName = "bar";

        DataContractSerializer dcs = new DataContractSerializer(
            typeof(Student),
            new Type [] { typeof(StudentId) },
            Int32.MaxValue,
            false, true, new StudentSurrogate());

        SerializationFlags.StudentGuidOnly = false;

        string xml1 = DataContractSerializerHelper.GetXml(kid, dcs);

        SerializationFlags.StudentGuidOnly = true;

        string xml2 = DataContractSerializerHelper.GetXml(kid, dcs);
     }
}

In this test case, xml1 is

<?xml version="1.0" encoding="utf-16"?>
<Student xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <FirstName z:Id="2">foo</FirstName>
    <Id>fa98b508-2fe9-4a09-b551-ba2ed1f70b70</Id>
    <LastName z:Id="3">bar</LastName>
</Student>

and xml2 is

<?xml version="1.0" encoding="utf-16"?>
<Student xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns="" i:type="StudentId" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Id>fa98b508-2fe9-4a09-b551-ba2ed1f70b70</Id>
</Student>

which is what you seek. Finally, note that, while my test case serializes Student as a top-level object, the replacement will occur if it is nested deep inside some class object graph.

For another example, see here: http://blogs.msdn.com/b/carlosfigueira/archive/2011/09/14/wcf-extensibility-serialization-surrogates.aspx