Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement an inherited Dictionary over WCF

Tags:

c#

wcf

c#-3.0

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

like image 254
chilltemp Avatar asked Nov 05 '08 23:11

chilltemp


People also ask

What is by serialization with respect to WCF?

The process forms a sequence of bytes into a logical object; this is called an encoding process. At runtime when WCF receives the logical message, it transforms them back into corresponding . Net objects. This process is called serialization.

How do you serialize an object in WCF?

This code constructs an instance of the DataContractSerializer that can be used only to serialize or deserialize instances of the Person class. DataContractSerializer dcs = new DataContractSerializer(typeof(Person)); // This can now be used to serialize/deserialize Person but not PurchaseOrder.

Which namespace is used for serialization in WCF?

By default WCF uses the DataContractSerializer class to serialize data types.

What is used if you expect the service to accept and return inherited types?

If you expect the service to accept and return an inherited type then we use the knowntype attribute.


3 Answers

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

like image 134
jezell Avatar answered Sep 21 '22 05:09

jezell


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/"><Data><ParmData><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>
like image 42
Marc Gravell Avatar answered Sep 21 '22 05:09

Marc Gravell


  • 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

like image 28
chilltemp Avatar answered Sep 22 '22 05:09

chilltemp