samy
samy

Reputation: 14972

Partial serialization with DataContractJsonSerializer

I have the following class used in a legacy WCF service

[MessageContract()]
public class Document
{
    [MessageHeader(MustUnderstand = true)]
    public MetaData Data { get; set; }

    [MessageHeader(MustUnderstand = true)]
    public string Name { get; set; }

    [MessageBodyMember(Order = 1)]
    public Stream File { get; set; }
}

This is passed so a method called AddDocument to push the document into a store.

At some point in the past I added logging on all our services through a Castle.Windsor interceptor which serialized the data passed to keep a trace of what's passed to the service.

public void Intercept(IInvocation invocation)
{
    Logger.Debug(() =>
    {
        StringBuilder sb = new StringBuilder(1000);
        sb.AppendFormat("{2} -> {0}.{1}(", invocation.TargetType.Name, invocation.Method.Name, SomeCode);
        sb.Append(string.Join(", ", invocation.Arguments.Select(a => a == null ? "null" : DumpObject(a)).ToArray()));
        sb.Append(")");
        return sb.ToString();
    });

    invocation.Proceed();

    Logger.Debug(() =>
    {
        StringBuilder sb = new StringBuilder(1000);
        sb.AppendFormat("OUT {0}", invocation.ReturnValue != null ? DumpObject(invocation.ReturnValue) : "void");
        return sb.ToString();
    });
}

with the serialization in DumpObject using the DataContractJsonSerializer

private string DumpObject(object argument)
{
    using (var ms = new MemoryStream())
    {
        try
        {
            var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(argument.GetType());
            ser.WriteObject(ms, argument);
            return System.Text.Encoding.UTF8.GetString(ms.GetBuffer(), 0, Convert.ToInt32(ms.Length));
        }
        catch (Exception)
        {
            return "NA";
        }
    }
}

I never was able to serialize the Document class because it contains a Stream, and the argument always was serialized as 'NA'. However I would like to know if it is possible to serialize the rest of the object without the stream.

I thought about changing the object type just for serialization, for example by mapping it to another object with a subset of the original object, but this solution doesn't please me since I'm checking and swapping a lot of data for an operation that should be very low key.

Upvotes: 0

Views: 391

Answers (1)

Yoh Deadfall
Yoh Deadfall

Reputation: 2781

This can be done by implementing the IDataContactSurrogate and providing it to the DataContractJsonSerializer.

var settings = new DataContractJsonSerializerSettings() { DataContractSurrogate = new SkipStreamSurrogate()};
var serializer = new DataContractJsonSerializer(argument.GetType(), settings);

serializer.WriteObject(ms, argument);

Implementation:

public sealed class SkipStreamSurrogate : IDataContractSurrogate
{
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public Type GetDataContractType(Type type)
    {
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // Skip serialization of a System.Stream
        if (obj is Stream)
        { return null; }

        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
}

Upvotes: 1

Related Questions