tags:

views:

3676

answers:

4

I’m trying to implement a dictionary for use with WCF. My requirements are:

  • actual (private variable or base class) type equivalent to Dictionary
  • Comparer = System.StringComparer.InvariantCultureIgnoreCase
  • Custom (override/new) Add(key, value) method (to include validations).
  • Override ToString()
  • Use of the same type on both the client and host

I’ve attempted using this class in a common project shared by the WCF host and client projects:

[Serializable]
public class MyDictionary : Dictionary<string, object>
{
  public MyDictionary()
    : base(System.StringComparer.InvariantCultureIgnoreCase)
  { }

  public new void Add(string key, object value)
  { /* blah */ }

  public override string ToString()
  { /* blah */ }
}

[DataContract]
[KnownType(typeof(MyDictionary))]
[KnownType(typeof(object[]))]
[KnownType(typeof(double[]))]
[KnownType(typeof(string[]))]
[KnownType(typeof(DateTime[]))]
public class ResultClass
{
  public object Value{ get; set; }
  /* More properties */
}
public class ParmData
{
  public object Value{ get; set; }
  /* More properties */
}
[DataContract]
[KnownType(typeof(MyDictionary))]
[KnownType(typeof(object[]))]
[KnownType(typeof(double[]))]
[KnownType(typeof(string[]))]
[KnownType(typeof(DateTime[]))]
public class ParameterClass
{
  public List<ParmData> Data{ get; set; }
  /* More properties */
}

[OperationContract]
ResultClass DoSomething(ParameterClass args);

Results:

  • When I pass MyDictionary as one of the ParameterClass.Data.Value elements, I get a missing KnownType exception.
  • I can safely return MyDictionary in the ResultClass, but it is no longer my type. It is just a Dictionary, and is not castable to MyDictionary. Also comparer = System.Collections.Generic.GenericEqualityComparer<string>, not the case insensitive comparer I’m looking for.

The help I’m asking for is to either fix my failed attempt, or a completely different way to achieve my stated requirements. Any solution should not involve copying one dictionary to another.

Thanks

A: 

Preamble: note that adding a "new" Add doesn't stop people calling the old Add simply by casting. Also, in terms of "mex", that is a very vague data-contract - does it need to be so open-ended? (lots of object etc...)

First: haven't you missed a few [DataContract]/[DataMember] markers there? In particular:

  • ResultClass.Value
  • ParamData
  • ParamData.Value
  • ParameterClass.Data

Can you clarify exactly which version of .NET you are using? DataContractSerializer etc have been tweaked via service packs. With 3.5 SP1 installed (the only thing I have to hand) it does at least serialize and deserialize via DataContractSerializer (no WCF stack), and the correct Add method is called.

Can you check whether the following works with your local version? (it works for me with 3.5 SP1 and with the missing attributes) [output first]:

1
MyDictionary
abc=123
def=ghi

Code:

        // or long-hand in C# 2.0
        ParameterClass pc = new ParameterClass {
            Data = new List<ParmData> { new ParmData {
                Value = new MyDictionary  {
                    {"abc",123},
                    {"def","ghi"}
                }}}};
        DataContractSerializer dcs = new DataContractSerializer(pc.GetType());
        string xml;
        using(StringWriter sw = new StringWriter())
        using(XmlWriter xw = XmlWriter.Create(sw)) {
            dcs.WriteObject(xw, pc);
            xw.Close();
            xml = sw.ToString();
        }
        using(StringReader sr = new StringReader(xml)) {
            ParameterClass clone = (ParameterClass)dcs.ReadObject(XmlReader.Create(sr));
            Console.WriteLine(clone.Data.Count);
            Console.WriteLine(clone.Data[0].Value.GetType().Name);
            MyDictionary d = (MyDictionary)clone.Data[0].Value;
            foreach (KeyValuePair<string, object> pair in d)
            {
                Console.WriteLine("{0}={1}", pair.Key, pair.Value);
            }
        }

Obviously this just tests DataContractSerializer (without the entire WCF stack), but it seems to work... so: does the same code work with your local version of .NET? If not, is installing the latest 3.0 service pack an option? (ideally via installing 3.5 SP1).

For info, I get xml:

<?xml version="1.0" encoding="utf-16"?><ParameterClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/"&gt;&lt;Data&gt;&lt;ParmData&gt;&lt;Value xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:type="d4p1:ArrayOfKeyValueOfstringanyType"><d4p1:KeyValueOfstringanyType><d4p1:Key>abc</d4p1:Key><d4p1:Value xmlns:d6p1="http://www.w3.org/2001/XMLSchema" i:type="d6p1:int">123</d4p1:Value></d4p1:KeyValueOfstringanyType><d4p1:KeyValueOfstringanyType><d4p1:Key>def</d4p1:Key><d4p1:Value xmlns:d6p1="http://www.w3.org/2001/XMLSchema" i:type="d6p1:string">ghi</d4p1:Value></d4p1:KeyValueOfstringanyType></Value></ParmData></Data></ParameterClass>
Marc Gravell
Installed .NET Runtimes: 1.0.3705.0 Version 1.0 RTM; 1.1.4322.2407 Version 1.1 Post-SP1 with KB928366; 2.0.50727.1433; 3.0.4506.648.Yes, the contract must be open ended. This is a wrapper for a legacy system.
chilltemp
But does it work?
Marc Gravell
Hmmm... that looks like 3.0SP1
Marc Gravell
+4  A: 

Add CollectionDataContract to the Dictionary class:

For more information on using collection data contracts to implement dictionaries, check this link:

http://msdn.microsoft.com/en-us/library/aa347850.aspx

jezell
This looks like the correct approach. The only problem is I end up with a new MyDictionary class in a child namespace. I'm investigating how to convince it to use the correct namespace. (http://social.msdn.microsoft.com/forums/en-US/wcf/thread/8368d9c0-1048-43c5-85c1-bee1cdd25449/)
chilltemp
The serializer supports dictionary-based collections even without the attribute - this simply customises the schema (names etc)
Marc Gravell
A: 
  • Use the CollectionDataContract attribute as jezell suggested
  • Manually generate the reference (proxy) code with SvcUtil, using the /collectionType parameter. This parameter is not supported by the vs2008 service reference GUI.

Source: WCF Collection Type Sharing

chilltemp
+1  A: 

I finally found a way to do this. I found this link that pointed in the right direction but I will try to summarize.

  1. Make sure you add [CollectionDataContract] to your custom collection
  2. Add Service Reference through VS like you normally do
  3. Expand the service reference and look for the Reference.svcmap file
  4. Under the client options node you will see <CollectionMappings/>. Replace it with the following xml.

    <CollectionMappings>
      <CollectionMapping TypeName="Full.Namespace.Here" Category="List" />
    </CollectionMappings>

  5. Right click on the Service Reference and click update. It should no longer generate that "proxy" class and will let you use your shared class.

Shaun Bowe
Editing an auto-generated file is generally not a good idea. It's way to easy to accidentally overwrite your changes.
chilltemp