I’m a bit new to WCF and I don’t think I completely understand what the deal is with DataContracts. I have this ‘RequestArray’ class:
[DataContract] public class RequestArray { private int m_TotalRecords; private RequestRecord[] m_Record; [System.Xml.Serialization.XmlElement] [DataMember] public RequestRecord[] Record { get { return m_Record; } } [DataMember] public int TotalRecords { get { return m_TotalRecords; } set { if (value > 0 && value <= 100) { m_TotalRecords = value; m_Record = new RequestRecord[value]; for (int i = 0; i < m_TotalRecords; i++) m_Record[i] = new RequestRecord(); } } } }
The idea is when the client says requestArray.TotalRecords=6; the Record array will be allocated and initialized (I realize that I’m hiding an implementation behind an assignment, this is out of my control).
The problem is that when the client does this, the TotalRecord’s set code is not called, breakpoints in the service confirm this as well. Rather, some sort of generic setter has been generated that is called instead. How do I get the client to use my setter instead?
EDIT: It looks like I didn’t quite get how the [DataContract] works, but it makes sense that the client wouldn’t be executing this code. Like I mentioned in a comment, if I ‘manually’ do the work of the setter, I see that the set code does get executed right when I call the service function.
The serialization I’m still uncertain on. The contents of the RequestRecord[] array are not getting carried over. The Record class has setters/getters, I feel like I need a helper function somewhere to help it serialize the whole class.
Thanks all for your help!
The premise that the client proxy will execute the same code on the client and on the server is flawed because WCF is based on interfaces. I explain this point in bullet #2 below.
Rules of Sharing WCF Interfaces & Implementation
If you want to share the implementation of the data contract you will need to factor the RequestArray class into a class library that holds NOTHING BUT the data contract classes, including presumably also the RequestRecord class.
Rules I live by:
Group all data contracts by themselves (100%) into one or more assemblies, no exceptions.
Group all service contracts into one or more assemblies.
Group all service types, i.e., the classes that implement the service contract, into one ore more assemblies.
Group all client channel proxies, i.e., the class that invokes the methods defined on the service contract interface, into one or more assemblies.
In a generic framework where all client software operates as a WCF service (I avoid duplex connections), it is safe to merge rules 2, 3 & 4 so that service contracts, service types and channel proxies are grouped together into one assembly.
The main reason for separating the interfaces into a more flexible dependency chain is that it is possible to deploy a limited set of assemblies to the client without exposing unnecessary and potentially proprietary implementation details. Another reason is that it makes refactoring so much easier, especially in cases where you want to implement or extend a generic framework through inheritance or delegation.
Examining the Code
There are a few BIG problems with the code for RequestArray…
The setter logic will overwrite any of the modified elements of the m_Record array variable when the DataContract instance is deserialized. This violates deserialization principles.
The Record property will not be able to be deserialized because the Record property on the RequestArray class is Read-only (since it lacks a setter). Generally, I find that for DataContract classes the best approach for read-only properties is simply a method. It is a bad idea to get into the habit of treating data contracts like they are anything more than bit buckets. What the attributes are basically doing is dynamically creating an interface definition specifically used for data serialization and deserialization. I believe that it is a mistake to think of the data on the wire as objects. Rather, it is an opt-in way of specifying the relevant data parts of an object that need to persist over the wire.
The TotalRecords property becomes dangerous if it ends up (correctly) just allowing the m_TotalRecords variable to be set, since it will be totally independent of the internal array. In order for me to get this to work acceptably in my sample code (below), I had to shield the set with
if (m_TotalRecords == 0). In the sample code I have saved for future use, I comment out the TotalRecords property altogether, but I leave m_TotalRecords just to illustrate that the private object is in fact preserved over the wire.Fixed Code
I adapted bendewey’s sample code (thanks!) and came up with this complete test. Note: I had to define RequestRecord. Also, please see the code comments. If there are any bugs or anything that is unclear, please let me know.
The listing for this sample program, after running TestWCFDataContract is:
Array contents after ‘new’:
Array contents after modification:
Array contents upon deserialization: