CSLA for Silverlight: Inconsistency with ContextDictionary key type

CSLA for Silverlight: Inconsistency with ContextDictionary key type

Old forum URL: forums.lhotka.net/forums/t/5941.aspx


paupdb posted on Friday, December 05, 2008

As with most of my posts, this is aimed at Rocky and the Magenic guys.

I've been doing some work recently in Csla Silverlight where I needed to have a business object property of a Dictionary<> type. 
Now obviously Dictionnary doesn't implement IMobileObject, so that was out.  Looking through the framework I found ContextDictionary which fit the bill - a Csla-serialisable dictionary type.

All was good until I hit errors when trying to iterate through the keys after a fetch from the server.  It was at this point that I discovered that ContextDictionary has two different implementations in Csla for .Net and Cslafor Silverlight respectively. 
In Csla for .Net, ContextDictionary extends HybridDictionary, whilst in Csla for Silverlight it extends Dictionary<string, object>. Since Silverlight doesn't have a HybridDictionary, Dictionary is fine.
The problem though is that the concrete implementation of ContextDictionary on the Silverlight side is restricted to string key types, whilst on the server you can have object key types.
So when I populated my ContextDictionary object with int keys on the server side, I hit trouble once it got back over to Silverlight.

The easy fix for this would obviously be for the Csla for Silverlight ContextDictionary to extend Dictionary<object, object>, but I know this might cause some issues for the ApplicationContext class which currently employs ContextDictionary objects.  So maybe more refactoring needed...

To me though, a far better solution then would be to create a MobileDictionary class in Csla (which allows object keys and values) to go with MobileList.  And then make ContextDictionary internal to force people to use MobileDictionary?

Either way,  that small inconsistency in the current implementation of ContextDictionary isn't great and from a naming perspective a general purpose mobile dictionary called MobileDictionary would be nicer to see in code than ContextDictionary which has a more specific sounding name.

RockfordLhotka replied on Friday, December 05, 2008

Thanks Paul,

I don't disagree that a MobileDictionary would be nice. But for what is effectively version 1 we were focused on getting CSLA working, so we only created serializable base types for things required directly by CSLA.

This whole serialization thing is less than ideal, but is really unavoidable. Silverlight simply doesn't have what is required to make something like the BinaryFormatter or NDCS, so MobileFormatter is our answer. Sadly, implementing IMobileObject, especially for lists or dictionaries, is non-trivial.

I was working with a limited time/budget and so we had to prioritize our efforts around what was necessary to get the framework itself functioning - realizing that in future versions there's the need for continual expansion of types.

paupdb replied on Sunday, December 07, 2008

Fair enough Rocky. 
I was thinking it would be as easy as copying the ContextDictionary, renaming it to MobileDictionary and changing the key type to object in the Silverlight implementation.
From your reply this would not seem to be the case.

Consider MobileDictionary a feature request for a future version then :)

Although I might have a crack at implementing a MobileDictionary myself - if I have some success I'll contribute it to Csla if you like.

RockfordLhotka replied on Sunday, December 07, 2008

MobileFormatter will require that both the key and value implement IMobileObject. So I suppose it can be done by creating the type with a constraint on both generic parameters to require them to implement that interface.

 

The only real trick is that ContextDictionary has (I think) a bit of code to deal with the key value, since we know it is string, and that code would need to change to somehow serialize the key values and put them into the serialized byte stream. Pretty much the same as the code for the values now – but in a way such that the keys and values can be matched up on deserialiation.

 

Rocky

 

From: paupdb [mailto:cslanet@lhotka.net]
Sent: Sunday, December 07, 2008 4:29 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] CSLA for Silverlight: Inconsistency with ContextDictionary key type

 

Fair enough Rocky. 
I was thinking it would be as easy as copying the ContextDictionary, renaming it to MobileDictionary and changing the key type to object in the Silverlight implementation.
From your reply this would not seem to be the case.

Consider MobileDictionary a feature request for a future version then :)

Although I might have a crack at implementing a MobileDictionary myself - if I have some success I'll contribute it to Csla if you like.


paupdb replied on Monday, December 08, 2008

Hi Rocky

I've put together a basic MobileDictionary and my initial testing seems to indicate that it works OK with the following combinations:
primitive key + IMobileObject value
primitive key + primitive value
IMobileObject key + IMobileObject value
IMobileObject key + primitive value

I haven't yet tightened up the generic parameters because I thought that might exclude primitives from being used - correct me if I'm wrong on that count.  I also had the server side match the Silverlight side in terms of inheriting from Dictionary<>.  This means that the same source code is used on both sides through the VS add as link feature instead of seperate implementations.

To keep the relationship between key and value I used a simple incremental counter concatenated to two constant string values to define the serialisation keys for the dictionary key and dictionary value.  Then some basic linq enabled me to query the serialised info values during deserialisation.

Anyhow its a rough cut but it does work - let me know what you think.

using System.Collections.Generic;
using System.Linq;
using Csla.Serialization;
using Csla.Serialization.Mobile;
using System;

  [Serializable()]
  public class MobileDictionary : Dictionary<object, object>, IMobileObject
  {
    #region IMobileObject Members
    private const string keyTxt = "MobDictKey";
    private const string valueTxt = "MobDictValue";

    void IMobileObject.GetState(SerializationInfo info)
    {
      int count = 0;
      foreach (var key in this.Keys) {
        if (!(key is IMobileObject))
          info.AddValue(keyTxt + count, key);

        object value = this[key];
        if (!(value is IMobileObject))
          info.AddValue(valueTxt + count, value);

        count++;
      }
    }

    void IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
    {
      int count = 0;
      foreach (var key in this.Keys) {
        IMobileObject mobilekey = key as IMobileObject;
        if (mobilekey != null) {
          SerializationInfo si = formatter.SerializeObject(mobilekey);
          info.AddChild(keyTxt + count, si.ReferenceId);
        }

        object value = this[key];
        IMobileObject mobile = value as IMobileObject;
        if (mobile != null) {
          SerializationInfo si = formatter.SerializeObject(mobile);
          info.AddChild(valueTxt + count, si.ReferenceId);
        }
        count++;
      }
    }

    void IMobileObject.SetState(SerializationInfo info)
    {
      // Find the MobileDictionary keys
      var mobileKeyList = from key in info.Values.Keys
                          where key.StartsWith(keyTxt)
                          select key.Substring(keyTxt.Length);

      foreach (string keyCounter in mobileKeyList) {
        object value = null;
        // Look for a non-MobileObject base field value
        if (info.Values.ContainsKey(valueTxt + keyCounter))
          value = info.Values[valueTxt + keyCounter].Value;

        Add(info.Values[keyTxt + keyCounter].Value, value);
      }
    }

    void IMobileObject.SetChildren(SerializationInfo info, MobileFormatter formatter)
    {
      // Find MobileDictionary keys
      var mobileKeyList = from key in info.Children.Keys
                          where key.StartsWith(keyTxt)
                          select key.Substring(keyTxt.Length);

      foreach (string keyCounter in mobileKeyList) {
        object value = null;
        // Look in the MobileObject children for a value
        if (info.Children.ContainsKey(valueTxt + keyCounter))
          value = formatter.GetObject(info.Children[valueTxt + keyCounter].ReferenceId);

        // Else look in the non-MobileObject base fields for a value
        else if (info.Values.ContainsKey(valueTxt + keyCounter))
          value = info.Values[valueTxt + keyCounter].Value;

        object key = formatter.GetObject(info.Children[keyTxt + keyCounter].ReferenceId);
        this.Add(key, value);
      }

      // Attempt to find non-MobileObject keys for MobileObject values which do not have MobileObject keys
      var mobileValueList = from    value in info.Children.Keys
                            where   value.StartsWith(valueTxt) &&
                                    !mobileKeyList.Contains(keyTxt + valueTxt.Substring(valueTxt.Length))
                            select  value.Substring(valueTxt.Length);

      foreach (string valueCounter in mobileValueList) {
        if (this.ContainsKey(info.Values[keyTxt + valueCounter].Value))
          this[info.Values[keyTxt + valueCounter].Value] = formatter.GetObject(info.Children[valueTxt + valueCounter].ReferenceId);
      }
    }

    #endregion
  }

RockfordLhotka replied on Monday, December 08, 2008

Thanks for doing that work, that is very helpful!!

I've added this to the wish list (http://www.lhotka.net/cslabugs/edit_bug.aspx?id=252) and it will probably go into 3.6.1.

3.6.0 is feature locked at this point, as I hope for final release on 12/15 - so only show-stopping bugs (and lots of XML commenting) are happening between now and RTW :)

RockfordLhotka replied on Friday, December 12, 2008

Thank you, this looks quite good.

 

I will absolutely consider this for 3.6.1, as a high priority, because it is obviously useful.

 

As I think I said previously – 3.6.0 is feature-locked and only critical bug fixes are going in now in anticipation of release.

 

Rocky

 

From: paupdb [mailto:cslanet@lhotka.net]
Sent: Monday, December 08, 2008 7:10 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: CSLA for Silverlight: Inconsistency with ContextDictionary key type

 

Hi Rocky

I've put together a basic MobileDictionary and my initial testing seems to indicate that it works OK with the following combinations:
primitive key + IMobileObject value
primitive key + primitive value
IMobileObject key + IMobileObject value
IMobileObject key + primitive value

I haven't yet tightened up the generic parameters because I thought that might exclude primitives from being used - correct me if I'm wrong on that count.  I also had the server side match the Silverlight side in terms of inheriting from Dictionary<>.  This means that the same source code is used on both sides through the VS add as link feature instead of seperate implementations.

To keep the relationship between key and value I used a simply incremental counter concatenated to two constant string values to define the serialisation keys for the dictionary key and dictionary value.  Then some simply linq enabled me to query the serialised inf o values when during deserialisation.

Anyhow its a rough cut but it does work - let me know what you think.

using System.Collections.Generic;
using System.Linq;
using Csla.Serialization;
using Csla.Serialization.Mobile;
using System;

  [Serializable()]
  public class MobileDictionary : Dictionary<object, object>, IMobileObject
  {
    #region IMobileObject Members
    private const string keyTxt = "MobDictKey";
    private const string valueTxt = "MobDictValue";

    void IMobileObject.GetState(SerializationInfo info)
    {
      int count = 0;
      foreach (var key in this.Keys) {
        if (!(key is IMobileObject))
          info.AddValue(keyTxt + count, key);

  &n bsp;     object value = this[key];
        if (!(value is IMobileObject))
          info.AddValue(valueTxt + count, value);

        count++;
      }
    }

    void IMobileObject.GetChildren(SerializationInfo info, MobileFormatter formatter)
    {
      int count = 0;
      foreach (var key in this.Keys) {
        IMobileObject mobilekey = key as IMobileObject;
        if (mobilekey != null) {
          SerializationInfo si = formatter.SerializeObject(mobilekey);
          info.AddChild(keyTxt + count, si.ReferenceId);
        }

        object value = this[key];
        IMobileObject mobile = value as IMobileObject;
        if (mobile != null) {
          SerializationInfo si = formatter.SerializeObject(mobile);
          info.AddChild(valueTxt + count, si.ReferenceId);
        }
        count++;
      }
    }

    void IMobileObject.SetState(SerializationInfo info)
    {
      // Find the MobileDictionary keys
      var mobileKeyList = from key in info.Values.Keys
            ;               where key.StartsWith(keyTxt)
                          select key.Substring(keyTxt.Length);

      foreach (string keyCounter in mobileKeyList) {
        object value = null;
        // Look for a non-MobileObject base field value
        if (info.Values.ContainsKey(valueTxt + keyCounter))
          value = info.Values[valueTxt + keyCounter].Value;

        Add(info.Values[keyTxt + keyCounter].Value, value);
      }
    }

    void IMobileObject.SetChildren(Serializatio nInfo info, MobileFormatter formatter)
    {
      // Find MobileDictionary keys
      var mobileKeyList = from key in info.Children.Keys
                          where key.StartsWith(keyTxt)
                          select key.Substring(keyTxt.Length);

      foreach (string keyCounter in mobileKeyList) {
        object value = null;
        // Look in the MobileObject children for a value
        if (info.Children.ContainsKey(valueTxt + keyCounter))
          ; value = formatter.GetObject(info.Children[valueTxt + keyCounter].ReferenceId);

        // Else look in the non-MobileObject base fields for a value
        else if (info.Values.ContainsKey(valueTxt + keyCounter))
          value = info.Values[valueTxt + keyCounter].Value;

        object key = formatter.GetObject(info.Children[keyTxt + keyCounter].ReferenceId);
        this.Add(key, value);
      }

      // Attempt to find non-MobileObject keys for MobileObject values which do not have MobileObject keys
      var mobileValueList = from    value in info.Children.Keys
                  ;           where   value.StartsWith(valueTxt) &&
                                    !mobileKeyList.Contains(keyTxt + valueTxt.Substring(valueTxt.Length))
                            select  value.Substring(valueTxt.Length);

      foreach (string valueCounter in mobileValueList) {
        if (this.ContainsKey(info.Values[keyTxt + valueCounter].Value))
          this[info.Values[keyTxt + valueCounter].Value] = formatter.GetObject(info.Children[valueTxt + valueCounter].Reference Id);
      }
    }

    #endregion
  }



Copyright (c) Marimer LLC