Custom Data exchanged with WCF services is defined through DataContracts using DataContract and DataMember attributes to the data definition classes. Please refer to the msdn help for more information:
http://msdn.microsoft.com/en-us/library/ms733127.aspx
The recommended way of doing so is to apply the DataMember attributes to properties
namespace MyTypes { [DataContract] public class PurchaseOrder { private int poId_value; // Apply the DataMemberAttribute to the property. [DataMember] public int PurchaseOrderId { get { return poId_value; } set { poId_value = value; } } } }
However there are cases where you want to apply the DataMember attributes to the fields instead of the properties. I will describe here one of these cases.
The Case
The case is: you want to develop an N-tier application, exposing business data through a WCF service facade. To do so, you define a custom data structure, representing your business, you expose it as a DataContract and you share the DataContract assembly between the WCF service and the client application.
Let's take the following definition of a DataContract representing a contact:
[DataContract] public class Contact : INotifyPropertyChanged { #region fields private string _firstName; private string _lastName; private bool _isDirty = false; #endregion #region properties [DataMember] public string FirstName { get { return _firstName; } set { if (_firstName == value) return; _firstName = value; OnPropertyChanged("FirstName"); } } [DataMember] public string LastName { get { return _lastName; } set { if (_lastName == value) return; _lastName = value; OnPropertyChanged("LastName"); } } [DataMember] public bool IsDirty { get { return _isDirty; } set { if (_isDirty == value) return; _isDirty = value; OnPropertyChanged("IsDirty"); } } #endregion #region methods public Contact(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; } public override string ToString() { return string.Format("FirstName: '{0}' LastName: '{1}' IsDirty: {2}", FirstName, LastName, IsDirty); } #endregion #region INotifyPropertyChanged implementation protected void OnPropertyChanged(string propertyName) { if (!ReferenceEquals(null, PropertyChanged)) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); if (propertyName != "IsDirty") IsDirty = true; } public event PropertyChangedEventHandler PropertyChanged; #endregion }
The only addition to the first simple case is the support for the INotifyPropertyChanged interface. Indeed, if you want to develop a proper application, you probably want to handle self-tracking entities, to make sure the service at server level can easily figure out what happened to the data in order to efficiently process it. First step to handling self-tracking entities is to be able to determine whether data has been changed since last retrieval. This is handled through the IPropertyChanged interface.
Here starts our problems.
Let's take following service definition:
[ServiceContract] public interface IContactService { [OperationContract] Contact PassContactThrough(Contact theContactToPassThrough); }
And service implementation:
public class ContactService : IContactService { public Contact PassContactThrough(Contact theContactToPassThrough) { return theContactToPassThrough; } }
All this operation is doing is passing the Contact through. We therefore expect to get back the same Contact information as the one we initially sent.
To test this, let's create a client, add a reference to the service and to the data contract assembly and do the following implementation:
static void Main(string[] args) { ContactServiceClient service = new ContactServiceClient(); var contactToPassThrough = new Contact("Walter", "Almeida"); Console.WriteLine("Contact Before Service Call: " + contactToPassThrough.ToString()); Contact returnedContact = service.PassContactThrough(contactToPassThrough); Console.WriteLine("Contact After Service Call: " + returnedContact.ToString()); }
We send through a Contact data for Walter Almeida. The Contact is clean, marked with IsDirty = false.
However, the returned Contact contains the proper "Walter Almeida" information but is marked ad IsDirty = true!
The explanation is the following: when sent to the service, the Contact data is serialized, accordingly to the DataMember attributes information. When received by the service, the received data is deserialized into a new Contact instance. When deserializing, the service will assign values to the Contact instance properties, because it is the properties that are marked as DataMembers. When doing so: the OnPropertyChanged method is called and IsDirty is set to true.
We could have been lucky here if IsDirty (also marked as DataMember) would be the last one to be deserialized thus compensating the previous calls to IsDirty = true. However when no specified otherwise, the DataMembers are serialized in alphabetical order. Therefore IsDirty is deserialized before LastName...
Here are candidate solutions to this problem with benefits and drawbacks:
Solution 1: Do not share the exact same DataContract assembly
Instead of sharing the exact same data contract assembly, the server should use a assembly that has the datacontract definition without the INotifyPropertyChanged interface implementation: just data, no behaviour:
[DataContract] public class Contact : INotifyPropertyChanged { #region fields private string _firstName; private string _lastName; private bool _isDirty = false; #endregion #region properties [DataMember] public string FirstName { get { return _firstName; } set { _firstName = value; } } [DataMember] public string LastName { get { return _lastName; } set { _lastName = value; } } [DataMember] public bool IsDirty { get { return _isDirty; } set { _isDirty = value; } } #endregion }
However this requires two different versions of the data definition, leading to the associated maitainability issues.
Moreover we also loose all the tracking capabilities at server side, which is wrong: it is possible for the service to perform actions on the data and therefore these actions should be also tracked.
Solution 2: Change order of properties serialization
By default DataMembers are serialized in alphabetical order. You can redefine this order the following way:
[DataContract] public class Contact : INotifyPropertyChanged { #region fields private string _firstName; private string _lastName; private bool _isDirty = false; #endregion #region properties [DataMember(Order = 0)] public string FirstName { get { return _firstName; } set { if (_firstName == value) return; _firstName = value; OnPropertyChanged("FirstName"); } } [DataMember(Order = 1)] public string LastName { get { return _lastName; } set { if (_lastName == value) return; _lastName = value; OnPropertyChanged("LastName"); } } [DataMember(Order = 2)] public bool IsDirty { get { return _isDirty; } set { if (_isDirty == value) return; _isDirty = value; OnPropertyChanged("IsDirty"); } } #endregion #region methods public Contact(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; } public override string ToString() { return string.Format("FirstName: '{0}' LastName: '{1}' IsDirty: {2}", FirstName, LastName, IsDirty); } #endregion #region INotifyPropertyChanged implementation protected void OnPropertyChanged(string propertyName) { if (!ReferenceEquals(null, PropertyChanged)) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); if (propertyName != "IsDirty") IsDirty = true; } public event PropertyChangedEventHandler PropertyChanged; #endregion }
The problem is now solved: IsDirty is not altered by the deserialization. Or better said: the IsDirty value is altered during deserialization but restored to the proper value at the end of deserialization when the IsDirty value is deserialized.
This solution is working but not perfect: there are still unecessary calls to the property changed event handler. And it can become cumbersome if we further complexify the Contact class. Moreover this solution does not work if we define a hierarchy of data classes and define the IsDirty property on the base class:
public class BaseContact : INotifyPropertyChanged { #region fields private bool _isDirty = false; #endregion #region properties [DataMember] public bool IsDirty { get { return _isDirty; } set { if (_isDirty == value) return; _isDirty = value; OnPropertyChanged("IsDirty"); } } #endregion #region INotifyPropertyChanged implementation protected void OnPropertyChanged(string propertyName) { if (!ReferenceEquals(null, PropertyChanged)) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); if (propertyName != "IsDirty") IsDirty = true; } public event PropertyChangedEventHandler PropertyChanged; #endregion } [DataContract] public class Contact : BaseContact { #region fields private string _firstName; private string _lastName; #endregion #region properties [DataMember] public string FirstName { get { return _firstName; } set { if (_firstName == value) return; _firstName = value; OnPropertyChanged("FirstName"); } } [DataMember] public string LastName { get { return _lastName; } set { if (_lastName == value) return; _lastName = value; OnPropertyChanged("LastName"); } } #endregion #region methods public Contact(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; } public override string ToString() { return string.Format("FirstName: '{0}' LastName: '{1}' IsDirty: {2}", FirstName, LastName, IsDirty); } #endregion }
In this case setting orders on DataMembers will not solve the problem because DataMembers of base classes (and therefore IsDirty) are always serialized/deserialized first.
Solution 3: Mark fields as DataMember instead of properties
Third solution is to mark the fields with the DataMember attribute, rather than the properties. This way, the deserialization step will set fields rather than properties and therefore OnPropertyChanged will never be called.
The limitation is that serialization of private fields is not allowed in partial trust scenarios (and this is the case for instance when developping a SilverLight client). Everything works fine when running in a full trust environment. To overcome this issue, the fields must be set as internal rather than private and you need to mark the assembly defining the data contracts with the following attribute (in assemblyinfo.cs):
[assembly: InternalsVisibleTo("System.Runtime.Serialization")]
This gives access right to the serialization assmebly to access your internal members. Not doing so would result in the following exception at runtime, when running in partial trust environments:
The data contract type 'Contact' cannot be deserialized because the property '_firstName' does not have a public setter. Adding a public setter will fix this error. Alternatively, you can make it internal, and use the InternalsVisibleToAttribute attribute on your assembly in order to enable serialization of internal members - see documentation for more details. Be aware that doing so has certain security implicationsSo here is the final version of the Contact data contract:
[DataContract] public class Contact : INotifyPropertyChanged { #region fields [DataMember( Name = "FirstName")] internal string _firstName; [DataMember( Name = "LastName")] internal string _lastName; [DataMember( Name = "IsDirty")] internal bool _isDirty = false; #endregion #region properties public string FirstName { get { return _firstName; } set { if (_firstName == value) return; _firstName = value; OnPropertyChanged("FirstName"); } } public string LastName { get { return _lastName; } set { if (_lastName == value) return; _lastName = value; OnPropertyChanged("LastName"); } } public bool IsDirty { get { return _isDirty; } set { if (_isDirty == value) return; _isDirty = value; OnPropertyChanged("IsDirty"); } } #endregion #region methods public Contact(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; } public override string ToString() { return string.Format("FirstName: '{0}' LastName: '{1}' IsDirty: {2}", FirstName, LastName, IsDirty); } #endregion #region INotifyPropertyChanged implementation private void OnPropertyChanged(string propertyName) { if (!ReferenceEquals(null, PropertyChanged)) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); if (propertyName != "IsDirty") IsDirty = true; } public event PropertyChangedEventHandler PropertyChanged; #endregion }
Please note the extra Name = xx setting of the DataMembers. This is to ensure we get a equivalent data schema as the one when marking the Properties as DataMembers. This implementation solves our issue and works in both full trust and partial trust environment. That's it for now!
Great post! What if you would like to know which properties have been set on the client? Something other that an auto-generated "Specified"-suffixed property...
Posted by: Mihai | 26/07/2010 at 09:29
Thanks Mihai for your comment,
Well, a full self-tracking service definition is already another subject. I will probably write a post on this subject later, so keep in touch!
In the meantime you can have a look at the self-tracking entities templates for Microsoft Entity Framework 4
Posted by: Walter Almeida | 26/07/2010 at 10:44
Thanks Walter, I'll keep in touch!
BTW, I'm already using an ORM (NHibernate) on the server-side. For the issue with the client-to-server communication and detecting which properties the client has set, maybe the DataContractSerializer can be extended? Although I now see that it is sealed... hmm.
Posted by: Mihai | 26/07/2010 at 11:30
Hello Mihai,
I think the best way to let your service server-side to know what has changed in your entities is to include this information in your datacontract, having as you said, for example, auto-generated "specified" suffix or more complex and complete information. We are talking about a service oriented scenario and information other than technical stuff (transactions, security etc.) should IMHO be contained in the message.
I would advise you to check this product : Llblgen Pro ( http://www.llblgen.com ). It is an excellent product which gives you designer, template writting and code generation support for several ORM products, including NHibernate. It could be that someone already wrote templates for self tracking entities for NHibernate. Ask their support team, they are very efficient, they will reply to you! ( http://www.llblgen.com/tinyforum/ )
Good luck in your work, I would appreciate if you let me know about your findings!
Posted by: Walter Almeida | 26/07/2010 at 13:32
Thanks, sounds like a neat tool! I've used MyGeneration so far (but no designer there last time I checked).
Posted by: Mihai | 26/07/2010 at 16:26
Thanks for taking the time to post this, Walter. You present a very intelligent and thorough case. Even though I do a lot with WCF and self-tracking entities, I have not seen your ideas elsewhere.
Posted by: Larry Spencer | 30/06/2011 at 23:50
Thanks Larry, I appreciate your comment:) And I am glad my post was helpfull!
Posted by: Walter Almeida | 05/07/2011 at 19:17
I think if any person works to his/her optimum levels then no work is very tough to accomplish. 330)
Posted by: Bennett | 12/04/2013 at 12:30
You can also detect serialisation and prevent updates to your IsDirty flag that way. Seems smaller and tidier as it is self documenting and you don't need to worry about someone in future forgetting and putting DataContract on the public property by accident.
VB version:
Public Sub NotifyPropertyChanged(ByVal info As String)
If (Not isDeserializing) Then IsDirty = True
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Sub OnOnDeserializing(context As StreamingContext)
isDeserializing = True
End Sub
Public Sub OnDeserialized(context As StreamingContext)
isDeserializing = False
End Sub
Posted by: Ryan O'Neill | 02/05/2013 at 12:03
ショップ素晴らしいヒーター;ある 多くの珍しいヒーターの設計 したがってサイズ。ギリシア人も で有能だった島をどのように彼らの家 営業キャビティ壁。ただし、最近そこいくつか スペシャ リスト メソッド方法の 人保つことができる を食品ホット。あなたも 消費電気 ベースボードヒーター。
Posted by: ベルト レディース | 18/10/2013 at 10:41
ブライダルの使用デザイナー財布意味、自尊心と態度の花嫁。、を参照してください、チェーンいっぱいですに加え魔法の機能を述べないためで無料味と精神。かどうかに関係なくいくつかの売り手を知っている人商品思われる利用できないためにチャンネルを承認します。これは主にで起こるインスタンスファッションのhobos。
Posted by: 女性 ブランド 財布 | 07/11/2013 at 13:51
逆 を人気の信念 氷口内炎は ないフォーム 一緒にヘルペス。右ここでので、フロリダ州砂漠私たち 収獲グリーン サラダ 任意年と はあまり好き。探す素晴らしいヒーター;ある 多くのユニークなヒーターの設計 とサイズ。ロッカーwindows は 用に設計された適しています が商業または アパートおよびコンドミニアム。 エルメスベルトメンズ
Posted by: エルメスベルトメンズ | 13/11/2013 at 08:56