Customizing Serialization of Entities in the ADO.NET Data Services Client Library
This post is inspired by this forum thread :Data services client -- exception on saveChanges
Problem Statement
I have added some custom proeprties to the Entity Types Generated by DataSvcUtil.exe /Add Service Reference / Hand Coded ,
these properties do not exist on the server and should not be sent to the server .
When I try to save an object of a Type which has custom client-Side only properties to the Data Service using AddObject and SaveChanges,
I get the following error :
Error processing request stream. The property name '{PropertyName}' specified for type '{TypeName} is not valid
This error is basically from the server telling the client library that its sending a payload which does not seem to match the definition
of the Entity Type on the server.
We can solve this problem by customizing the Payload that the client generates when it sends the entity to the server.
The DataServiceContext exposes an event called WritingEntity that is fired right before we send the payload over the wire.
Its argumentlist contains ReadingWritingEntityEventArgs which gives you access to the Entity Instance being Serialized ( e.Entity )
and the ATOM Payload (e.Data ) we are about to send to the Server.
Removing a property from the ATOM Payload that is being sent to the server.
void dataContext_WritingEntity(object sender, ReadingWritingEntityEventArgs e) {
// e.Data gives you the XElement for the Serialization of the Entity
//Using XLinq , you can add/Remove properties to the element Payload
XName xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
XElement xePayload = e.Data.Descendants().Where<XElement>(xe => xe.Name == xnEntityProperties).First<XElement>();
//The XName of the property we are going to remove from the payload
XName xnProperty = XName.Get(“{PropertyName}”, e.Data.GetNamespaceOfPrefix("d").NamespaceName);
//Get the Property of the entity you don't want sent to the server
XElement xeRemoveThisProperty = xePayload.Descendants().Where<XElement>(xe => xe.Name == xnProperty).First<XElement>();
//Remove this property from the Payload sent to the server
xeRemoveThisProperty.Remove();
}
}
Generalizing this further , we want to make it easier to customize the serialization of multiple entity types without having to write
multiple copies of the WritingEntity event handler.
We will add an attribute that specfies that a certain CLR property should not be serialized in the ATOM Payload when the client library
sends the entity to the server.
/// <summary>
/// Properties marked with this Attribute are not serialized in the payload when sent to the server
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class DoNotSerializeAttribute : Attribute
{
}
Now , we change the WritingEntity Event handler to remove any property that has this attribute on it .
void DataServiceContextEx_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
{
// e.Data gives you the XElement for the Serialization of the Entity
//Using XLinq , you can add/Remove properties to the element Payload
XName xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
XElement xePayload = null;
foreach (PropertyInfo property in e.Entity.GetType().GetProperties())
{
object[] doNotSerializeAttributes = property.GetCustomAttributes(typeof(DoNotSerializeAttribute), false);
if (doNotSerializeAttributes.Length > 0)
{
if (xePayload == null)
{
xePayload = e.Data.Descendants().Where<XElement>(xe => xe.Name == xnEntityProperties).First<XElement>();
}
//The XName of the property we are going to remove from the payload
XName xnProperty = XName.Get(property.Name, e.Data.GetNamespaceOfPrefix("d").NamespaceName);
//Get the Property of the entity you don't want sent to the server
XElement xeRemoveThisProperty = xePayload.Descendants().Where<XElement>(xe => xe.Name == xnProperty).First<XElement>();
//Remove this property from the Payload sent to the server
xeRemoveThisProperty.Remove();
}
}
}
We can encapsulate this functionality into its own class and any DataServiceContext type that inherits from this class should be able to
inherit this functionality too .
Complete Source Code :
Using this in your applications :
Create a partial class with the same name as shown above and include it in your application to get this behavior.
Attribute your types with the DoNotSerializeAttribute attribute so that this property is not serialized.
public class TestType
{
public int ID { get; set; }
/// <summary>
/// This Property is client-only , should not be sent to the server
/// </summary>
[DoNotSerialize]
public string ClientOnlyProperty { get; set; }
}
Comments
Anonymous
December 12, 2008
One of the most common feature requests we hear for Astoria is the ability to mark properties on an entityAnonymous
March 27, 2009
You know this completes the circle since whe can partialize a data entity to implemente INotifyPropertyChanged and we can mark an attribute to dont be part of the context we now really can customize and use a data entity in business layer so we dont need extra classes to achieve this, thats one of the many things i love data services, great post! thank you!!Anonymous
April 22, 2009
This is a very cool idea, but I'm curious if it can be used on a field in an ADO.NET entity model? If so, how would I set a field there to have the DoNotSerialize property?Anonymous
April 22, 2009
Hi Charles, This property is client-side only. By design , the server does not expose any events for us to hook in and change the markup . Which scenario are you trying to solve with this ? -PhaniAnonymous
May 12, 2009
I may be missing something but I'm trying to use this in a Silverlight project and my derived class that inherits from DataServiceContext is auto generated for my Service Reference. If I update the code that defines my Service Reference to inherit from my new custom class it is simply blown away the next time I refresh the service. What am I missing?Anonymous
May 21, 2009
Hi John, You are right . I wanted to update this blog entry to use a partial class instead of an inherited class for a long time . Using a partial class preserves this code in another code file and doesnt get affected if you re-generate the client proxy classes. -PhaniAnonymous
July 15, 2009
I was having the same issue with ADO.NET Service generated classes that we wanted to extend (since they are partial classes). I tried several techniques to do the serialization in order to exclude the newly created (extended) properties to the ADO.NET generated classes, since ADO.NET detects the newly created properties; which causes an exception specifying the NONE existing properties. Some didn't work, other were very tricky. So I found this solution much cleaner and practical. The only problem I found was the LINQ code to access the extended properties to remove them from the ATOM Payload, doesn't work anymore. The methods to process the Lambda expression don't exist or the version I have in silverlight is different. So I fix it this way to eliminate a propertyname (string): XName xNameProperty = XName.Get(propertyName, rootElement.GetNamespaceOfPrefix("d").NamespaceName); //Get descendants for xNameProperty object within ALL xElements IEnumerable<XElement> PropertyElements = rootElement.Descendants(xNameProperty); //Initialize variable XElement elementxHasProperty = null; //Find xElement for the searched propertyName foreach (XElement element in PropertyElements) { //Verify if it is the property looked for if (element.Name.LocalName == xNameProperty.LocalName) { elementxHasProperty = element; break; } } //Verify if the propertyName was found to remove the property from being sent to ADO.NET Services if (elementxHasProperty != null) elementxHasProperty.Remove(); Thank you so much for your guidanceAnonymous
July 13, 2010
Hi, We had a tough time trying to solve this issue of not sending the custom properties of the entities to the server. your article was of great help :). Thanks a lotAnonymous
May 12, 2011
This post inspired me to write an [Encrypt] attribute to encrypt the contents of each table property. Source code: azuretableencrypt.codeplex.com I'm tracking issues on this MSDN thread. social.msdn.microsoft.com/.../5ba62aa0-46c1-4599-b44f-8df4798cacadAnonymous
May 27, 2011
When ADO tried to write one of my properties (it was a 'get' only) it crashed the original Phani's code. After slight change all came to normal and worked. Great job Phani! Thanks a lot! XElement xeRemoveThisProperty = xePayload .Descendants() .Where<XElement>(xe => xe.Name == xnProperty) .FirstOrDefault<XElement>(); //Remove this property from the Payload sent to the server if (xeRemoveThisProperty != null) xeRemoveThisProperty.Remove();Anonymous
August 18, 2011
Hi Phani, I describe some goals to my table storage entity class in stackoverflow.com/.../effective-custom-azure-table-storage-entity-serialization, two of them I think that can't accomplish with this approach. There are another approach?! Thank'sAnonymous
September 13, 2011
AHHH what a great solution. Too bad I found this right at the end of my project! There was some bad code written because of the need for client-side metadata that I thought couldn't be placed in the entity. It also allows helps solve a problem of controls such as the telerik RadGridView wanting to sort and group data based on the binding source and not the converted value. Sometimes it can't sort and group on the binding source. I am thinking about storing the converted value in one of these extended properties.Anonymous
November 22, 2011
The comment has been removed