views:

44

answers:

2

Hi,

consider the following class and struct

public class Entity {
    public IdType Id {get;set;}
    public string Data {get;set;}
}

[TypeConverter(IdTypeConverter))]
public struct IdType {
    ... any data ...
}

The IdTypeConverter can convert the IdType struct from and to string.

Now, what I want is this class to be serializable for AJAX, WCF and ViewState. The senario could be a WCF web service which provides as array of Entity[] to a DataSource. And a custom control which binds to the datasource, saves this class to it's ViewState and sends the data to client side code.

This could be achieved easily by simply adding a [Serializable] attribute to all serialized types. But I don't want the IdType to be serialized, but to be converted to a string. Thus the JSON representation should be

{ 'Id'=>'StringRepresentationOfId', 'Data'=>'foo' }

This would analogously be the optimal serialization for WCF and ViewState.

Another solution would be to write another class

public class JsonEntity {
    public JsonEntity(Entity from) {
        Id = from.Id;
        Data = from.Data;
    }
    public string Id {get;set;}
    public string Data {get;set;}
}

and use this for JsonSerialization. But I don't like this, because this would imply that the control which is sending the data to the client knows about the Entity type.

The actual question is: Is it possible to customize JsonSerialization with attributes without breaking WCF and ViewState serialization?

EDIT: An answer like "That' impossible" would satify me, since I'd stop trying.

+1  A: 

Another solution similar to creating a proxy class just for serialization would be using the [NonSerialized()] attribute.

IdType could be non-serializable and another serializable string property could be implemented as a proxy (which would - on get or set - assign to / read from the IdType).

The solution could even look elegant with the use of aspect-oriented programming.

Jaroslav Jandek
Sure! So far this is the only solution which solves the json part, avoiding that the control, which serializes the data, has to know about the type at compile time. Though I don't like extra public fields on my data entities.
Michael Stoll
+1  A: 

From what I've read, the LosFormatter class used for ViewState serialization checks if there is a TypeConverter for a given object and uses it, so you should be covered there.

This article describes how to create your own JavaScriptConverter to perform custom serialization. The Serialize method that you have to implement returns IDictionary<string, object>, so you might have to create a JavaScriptConverter class for your Entity class rather than for your IdType struct. Below is an example, though I haven't had a chance to test it out:

Note: the examples in my post just look up the associated TypeConverter for IdType in order to convert it to a string, but unless there's a specific reason to do it that way you might as well just call a specific function (e.g. override ToString) that returns a the string representation directly instead of looking up the TypeConverter as my example code is doing.

public class EntityJsonConverter : System.Web.Script.Serialization.JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        if (obj == null)
            throw new ArgumentNullException("obj");

        Entity entity = obj as Entity;
        if (entity != null)
        {
            var values = new Dictionary<string, object>();
            TypeConverter idTypeConverter = TypeDescriptor.GetConverter(entity.Id);
            if (idTypeConverter != null)
            {
                if (idTypeConverter.CanConvertTo(typeof(string)))
                    values.Add("Id", idTypeConverter.ConvertTo(entity.Id, typeof(string)));
                else
                    throw new SerializationException(string.Format("The TypeConverter for type \"{0}\" cannot convert to string.", this.GetType().FullName));
            }
            else
                throw new SerializationException(string.Format("Unable to find a TypeConverter for type \"{0}\".", this.GetType().FullName));

            values.Add("Data", serializer.Serialize(entity.Data));

            return values;
        }
        else
            throw new ArgumentException(string.Format("Expected argument of type \"{0}\", but received \"{1}\".", typeof(Entity).FullName, obj.GetType().FullName), "obj");
    }

    public override IEnumerable<Type> SupportedTypes
    {
        get { yield return typeof(Entity); }
    }
}

If you also want the behavior that you described for XML serialization, you could have the IdType struct implement the IXmlSerializable interface. Below is an example of a WriteXml method implementation that uses the TypeConverter defined for the IdType struct:

    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
    {
        TypeConverter converter = TypeDescriptor.GetConverter(this);
        if (converter != null)
        {
            if (converter.CanConvertTo(typeof(string)))
                writer.WriteString(converter.ConvertTo(this, typeof(string)) as string);
            else
                throw new SerializationException(string.Format("The TypeConverter for type \"{0}\" cannot convert to string.", this.GetType().FullName));
        }
        else
            throw new SerializationException(string.Format("Unable to find a TypeConverter for type \"{0}\".", this.GetType().FullName));
    }
Dr. Wily's Apprentice
This JavaScriptConverter type looks nice. Is there an attribute which tells the JsonSerializer to use a custom JavaScriptConverter?
Michael Stoll
I don't think there is an attribute for that, but check out the article at http://nayyeri.net/custom-json-serialization-in-asp-net-ajax. It shows how to register the JavaScriptConverter in a config file. I haven't had a chance to verify any of that, though.
Dr. Wily's Apprentice