Request: WCF Serialization Support


I have an application that the map is extremely useful for, however, I need to send it across a WCF service.
Closed Jul 10, 2012 at 6:24 AM by payonel


payonel wrote Nov 8, 2011 at 8:28 AM

Great suggestion. At first I thought this feature would require only decorating the appropriate fields in the maps, and perhaps adding a few custom serializers here and there. To my surprise, the upgrade was simple in code, but complex in design. The spoiler is that the work is done. Maps can be passed over wcf contracts. What I learned is that wcf was already detecting the IDictionary interface on the maps, and calling GetEnumerator() and Add() to read and write, respectively. Because of this, 1 key maps worked right out of the gates. When I realized this I was overly optimistic. 2 key maps failed, as did 3 key maps. The following discussion of issues relates almost exclusively to maps with multiple keys (i.e. >1 key maps).

The 1st problem was that GetEnumerator's signature didn't fit into the Add() signature as the wcf engine expected (as with normal dictionaries). This, indirectly, was actually by design. Maps are indeed IDictionaries. But, 2 key maps are IDictionary of IDictionaries, and 3 key maps...another IDictionary. There were many design decisions more important than what interface to implement, which led to actually requiring it be done this way. Even so, having an enumerable layer of dictionaries was deemed less user friends for the API (which only mattered in a few cases), and thus a Tuple<> grouping of the keys was added; the old GetEnumerator() was hidden with explicit interface implementation. Sadly, since wcf calls GetEnumerator() and Add(), I needed to pick one signature-scheme for the two.

The 2nd problem was Add(). GetEnumerator()'s signature had been changed for aesthetics of the API, but Add() had actually been hidden (except for 1 key maps, the Add() method was left exposed and was working). Add() was hidden and threw a NotSupportedException. Implementing Add() was trivial, but the side affects could be unexpected for users. Consider that a map of multiple keys is essentially a dictionary of dictionaries. But a map has many extra key handling behaviors. If a dictionary is inserted to a map (i.e. one of the dictionaries of dictionaries), we couldn't reasonably keep the inserted dictionary a literal reference of the parameter given due to map features which can be configured to create or read data in a custom manner. Returning the discussion to the original problem: wcf required a working Add() method. The problem before me was not only a code change to be compatible with GetEnumerator(), but an API issue; whether to expose Add() with some documentation of its "deep copy"-like behavior or to continue to hide the method but actually have an implementation for it (sadly, hiding it would also hide any documentation I could otherwise provide to intellisense). The potential problem could arise where a user takes a map as an IDictionary, thereby making visible the Add() method, and then presumably inserting dictionaries when in reality copies are being made. This could lead to unexpected behavior.

The decision I made was:
  1. Remove the helpful GetEnumerator(), leaving the less attractive version. foreach loops could still make implicit calls directly on the map variable; the drawback being that the element returned would potentially be layers thick with IDictionary. KeyValues is, and and always was, an optional pattern to use with foreach, giving the same behavior (i.e. helpful Tuple<>'s wrapping the keys); this again is what the previous GetEnumerator() had. The remaining GetEnumerator() would now have a compatible signature with Add()
  2. Support the Add() method, but continue to use explicit implementation to hide the unusual nature of the method.

wrote Nov 8, 2011 at 8:29 AM

wrote Nov 8, 2011 at 8:29 AM

wrote Nov 8, 2011 at 8:30 AM

payonel wrote Nov 9, 2011 at 12:03 AM

Changeset 66208 was essentially reverted back to 64620 with 66214. Changeset 66218 now resolves all currently known serialization issues (we are wcf ready).
66208's rational was:
In order to support data contract serialization, imaps need to conform to enumerable and Add() as expected by the wcf engine, save implementing custom callbacks; I chose to keep it simple. This change has the potential to break existing code of consumers of BeanMap. The breaking changes are that GetEnumerator() no longer matches the KeyValues signature, the implicit method invoked by foreach loops has been affected for multiple key maps, and a protected abstract AddImpl(k,v) is now required on subtypes to maps. Note that the provided 1, 2, and 3 key BeanMap maps are updated with this changeset to correctly implement these changes. Preexisting code calling GetEnumerator(), either explicitly or implicitly via foreach loops, can fix their code by simply replacing calls to GetEnumerator() with the KeyValues property; the latter holds the signature the prior previously had, which also in fact used to be a simple wrapper of the prior, and no other code change will be necessary. Note that iterating implicitly on a map (as via a foreach loop) is still allowed, it is only warned herein of the change as the signature of the implicitly called method by foreach has been changed. Add() will unlikely be a breaking change, at least not serverly so, as it previously would throw NotSupported, by design. Now it has been implemented by necessity to support data contracts (i.e. wcf serialization). User defined types, outside the BeanMap library, which implement IMap (or other base types to BeanMap maps), will also need to update the hereto described methods.

Then more information was found. 66214's rational was:
The entire code change in 66208 (previous) was found to be incompatable with future upgrades to serialization and wcf support; specifically I was unable to add custom serialization hooks along side built in collection serialization. wcf requires one or the other. This checkin undoes the work in 66208 (yay!). Soon to come will be wcf support through custom serialization only. Ironically, the lengthy discussion about hiding and exposing methods affected by serialization is now moot as I am able to hide the fully custom solution with more explicit interfaces. This checkin leaves 6 tests failing which will work with wcf support.

wrote Jul 10, 2012 at 6:24 AM

wrote Feb 1, 2013 at 10:53 PM

wrote May 14, 2013 at 4:38 AM