tags:

views:

360

answers:

4

I have a WCF service which exposes a method that returns an array of objects which contain an Image property (see code below). In the same solution, I have a class library project which has a service reference to my WCF project. In the class library, when I attempt to "Update Service Reference" my proxy class becomes unavailable. When I remove the "Graphic" property from my class, I have no difficulty updating the service reference in the class library and my code all compiles and runs fine. I put the "Graphic" property back in and the proxy class again becomes unavailble. And what is even stranger is that the only class exposed by the service reference is "Image".

What am I overlooking here?

[Serializable]
public class PhotoDTO
{
    public Guid Id { get; set; }
    public Image Graphic { get; set; }
}


[ServiceContract]
public interface IGeneralService
{
    [OperationContract]
    PhotoDTO[] GetPhotos(Guid subsectionId);
}
A: 

Try explicitly specifying your DataContract

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

    [DataMember()] 
    public Image Graphic { get; set; }
}

UPDATE

Heres some code for testing serialization issues. Without the Bitmap type (which inherits from Image and I am using Image.FromFile() to load a png file) in the list of known types this breaks. The exception raised in this context indicates the need for Bitmap to be a known type. The serialization should then work.

 public static void LogDTO<T>(T dto)
 {
        DataContractSerializer ser =
            new DataContractSerializer(typeof(T),
                 new []{typeof(System.Drawing.Image),
                        typeof(System.Drawing.Bitmap)});

        FileStream writer = new FileStream("C:\\temp\\" + dto.ToString() + ".xml",
                                                             FileMode.Create);
        ser.WriteObject(writer, dto);
        writer.Close();
    }
Simon Fox
nope. that doesn't work. i've played around with that some, as well as with fiddling around with the [KnownType(typeof(System.Drawing.Image))] attribute but without much luck.Some posts indicate that if my Graphic property were a byte[] that it would work and I suspect that is true. However, code on either side of the service call wants to work with the Graphic property as an Image. One option would be to provide another property byte[] GraphicAsByte { get; set; } or something like that which would convert the Image into a byte[], but that seems like sort of a hack.
Steve Elmer
see my update, nice code for debugging serialization issues...
Simon Fox
+1  A: 

EDIT: As alexdej correctly points out, Image will serialize/deserialize with DCS correctly, but you have to either change the DataContract type to Bitmap or use config to specify a KnownType of Bitmap for System.Drawing.Image at runtime (you can't attribute it because you don't own the Image class).

The Image class isn't suitable for serialization by DataContractSerializer- it's got all sorts of ties to GDI buffers and stuff under the covers. DCS is intended to represent data objects where you control the entire structure of the class. The confusion comes because in 3.5SP1 they added the ability for DCS to serialize objects that aren't tagged with DataContractAttribute (mostly as a convenience for people too lazy to attribute their wire classes). The unfortunate side effect is that the serializer will happily TRY to serialize any old object, but will fail to produce a useful result in many cases (like yours).

One way or another, you'll need to turn it into a byte[] or a Stream to get it over the wire, and rehydrate it as an image. If you're using WCF and the same DataContract types on both sides (eg, not a generated type), you can leave Graphic as a property, but don't mark it with DataMember. Make the set on Graphic populate storage for another proprty ImageBytes (that IS tagged DataMember) by calling Image.Save to a MemoryStream, then dump the byte[]. Make the set on ImageBytes load the graphic property's internal Image storage from the byte[] passed in in the same manner. When the object is deserialized on the other end (eg, the deserializer calls the ImageBytes setter), poof- your Graphic property's storage is populated, and it all just works. Full automatic serialization/deserialization behavior- the ImageBytes property is just an implementation detail.

nitzmahone
+1  A: 

The Image class is compatible with the DCSerializer -- Image implements ISerializable and 'does the right thing' in terms of just serializing the raw byte[] data of the image.

You should be getting some Warnings from the Update Service Reference process (look for them in the Error List window). What's probably happening is Update Service Reference is failing to find the System.Drawing.Image type because your client project does not have a reference to System.Drawing.dll. Try adding an assembly reference to System.Drawing.dll and do Update Service Reference again.

BTW, you should be putting [DataContract] and [DataMember] attributes on your type, not [Serializable], as mentioned by another poster. Alternatively if you're using 3.5SP1 you can leave the attributes off completely.

alexdej
+1 for making me go read the docs on DCS again to call BS on the ISerializable support, but you're right! I guess you learn something new every day. :)
nitzmahone
A: 

Thanks, nitzmahone. That's what I was afraid of, though. I was hoping to avoid having to have a second property associated with the same field as it seems a bit "smelly", but that may be the simplest solution:

[DataContract]
public class PhotoDTO
{
    private Guid _id = Guid.Empty;
    private Image _graphic;


    [DataMember]
    public Guid Id
    {
        get { return _id; }
        set { _id = value; }
    }

    [DataMember]
    public byte[] GraphicBytes
    {
        get 
        {
            if (_graphic == null) return new byte[0];

            using (MemoryStream ms = new MemoryStream())
            {
                _graphic.Save(ms, ImageFormat.Png);
                return ms.ToArray();
            }
        }

        set 
        {
            if (value.Length > 0)
            {
                using (MemoryStream ms = new MemoryStream(value))
                {
                    _graphic = Image.FromStream(ms);
                }
            }
            else
            {
                _graphic = null;
            }
        }
    }

    public Image Graphic
    {
        get { return _graphic; }
        set { _graphic = value; }
    }


    public PhotoDTO()
        : this(Guid.Empty, null)
    { }

    public PhotoDTO(Guid id, Image graphic)
    {
        _id = id;
        _graphic = graphic;
    }
}

}

Steve Elmer
alexdej is right- DCS actually will do the right thing with Image because it's ISerializable. See my revised answer above.
nitzmahone