Skip to main content

Serializing to attributes in WCF with DataContractSerializer

It’s a common problem – you want to return an object from a WCF service as XML, but you either want, or need, to deliver some or all of the property values as XML Attributes instead of XML Elements; but you can’t because the DataContractSerializer doesn’t support attributes (you’re most likely to have seen this StackOverflow QA if you’ve done a web search).  Most likely you’ve then migrated all your WCF service code to using the XmlSerializer (with all the XmlElement/XmlAttribute/XmlType attributes et al) – and you’ve cursed loudly.

Well, I’m here to rescue you, because it is possible – and the answer to the problem is actually inferred from the MSDN article entitled ‘Types supported by the Data Contract Serializer’.

The example I’m going to give is purely for illustration purposes only.  I don’t have a lot of time, so work with me!

  • Create a new Asp.Net WCF service application, you can use Cassini as your web server (probably easier – otherwise you might have to enable Asp.Net compatibility mode).
  • Open the web.config and delete the <service> element that was created for the new service.
  • The interface and implementation model for this example is overkill.  Move the [ServiceContract] and [OperationContract] declarations from the interface that was created for you new service to the class that was also created.  Delete the interface.
  • Open the .svc markup file and add the following at the end: Factory="System.ServiceModel.Activation.WebServiceHostFactory" – this enables the zero-configuration WCF model for this service (we’re going to create a RESTful service).
  • Paste the following class declarations into your svc codebehind:
public interface IExampleData
{
	string Description { get; set; }
	string Name { get; set; }
	int ID { get; set; }
}
public class ExampleData : IExampleData
{
	public string Description { get; set; }
	public string Name { get; set; }
	public int ID { get; set; }
}
public class ExampleDataAttributed : ExampleData, IXmlSerializable
{
	#region IXmlSerializable Members
	public System.Xml.Schema.XmlSchema GetSchema()
	{
		return null;
	}
	public void ReadXml(System.Xml.XmlReader reader)
	{
		//implement if remote callers are going to pass your object in
	}
	public void WriteXml(System.Xml.XmlWriter writer)
	{
		writer.WriteAttributeString("id", ID.ToString());
		writer.WriteAttributeString("name", Name);
		//we'll keep the description as an element as it could be long.
		writer.WriteElementString("description", Description);
	}
	#endregion
}

Just to demonstrate the point, the class that will be part-serialized to attributes simply derives from one that will be serialized as normal.



  • Now add the following two methods to your service class:
[OperationContract]
[WebGet(UriTemplate = "/test1")]
public ExampleData Test1()
{
	return new ExampleData() { ID = 1, 
Name = "Element-centric",
Description =
"The contents of this item are entirely serialized to elements - as normal" };
}
[OperationContract]
[WebGet(UriTemplate = "/test2")]
public ExampleDataAttributed Test2()
{
	return new ExampleData_Attributed() { ID = 2, 
Name = "Mixed",
Description =
"Everything except this description will be serialized to attributes" };
}

Cover, and bake for 40 minutes (that is – Build it).


If you left your service as Service1.svc, then run it and open up IE and browse to http://localhost:[port of cassini]/test1


The result should look something like this:

<JSLabs.ExampleData 
 xmlns="http://schemas.datacontract.org/2004/07/ExampleNamespace" 
 xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
	<Description>
		The contents of this item are entirely serialized to elements - as normal
	</Description>
	<ID>
		1
	</ID>
	<Name>
		Element-centric
	</Name>
</JSLabs.ExampleData>

Now browse to http://localhost:[port of cassini]/test2

<JSLabs.ExampleDataAttributed id="2" name="Mixed" 
  xmlns="http://schemas.datacontract.org/2004/07/JobServe.Labs.Web">
	<description>Everything except this description will be 
	serialized to attributes</description>
</JSLabs.ExampleDataAttributed>

It’s made a little less impressive by that nasty ‘orrible “xmlns=” attribute that the WCF data contract serializer automatically puts on the type – but, as you can see, the ‘ID’ and ‘Name’ properties have indeed been pushed out as attributes!


We could have made both methods return IExampleData and then used the KnownType attribute on that interface in order to get it to support either (according to what the code of the methods returned).


To support deserializing an object from the attributes, all you have to do is to implement the IXmlSerializable.ReadXml method.


Finally, as the aforementioned MSDN article says about the supported types – you should also be able to use XmlElement/XmlNode types as a way of representing XML directly – the DataContractSerializer, like in this case, take the short route and simply gets the Xml.


This also shouldn’t affect JSON formatting if you’re dual-outputting objects for either XML or JSON clients.

Comments

  1. Just used this...very kick ass. Merci.

    ReplyDelete
  2. Hi Mike - glad to be of help :)

    ReplyDelete
  3. Having trouble with this - why don't you have [DataMember] attributes on the members in ExampleData? As soon as I do that, this breaks.

    ReplyDelete
  4. This is working great. But having problem when i try to add one more Properites with [DataMember] to ExampleData. Then the DataContractSerializer is not serializing new property. It may be because of the ExampleDataAttributed class is implementing IXmlSerializable.

    Any Ideas are appreciable...

    ReplyDelete

Post a Comment

Popular posts from this blog

Asp.Net 2 and 4 default application pool generates CS0016 IIS7.5

Before I start – if you’ve found a bunch of other articles about this around the net, tried the fixes that are mentioned and still not getting any joy – then read on – you might find this solves your problem. Earlier today I discovered that when I run any ASP.Net 2 or 4 application through IIS7.5 using the default application pools (which use ApplicationPoolIdentity) on Windows 2008 R2 x64 I get an error message similar to this:   Server Error in '/MvcApplication31' Application. Compilation Error Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately. Compiler Error Message: CS0016: Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\mvcapplication31\222b4fe6\4e80a86\App_global.asax.clb4bsnc.dll' -- 'The directory name is invalid. ' Source Error: [No relevant source

Shameless plug - Use the new JobServe Web API to search for jobs your way

As my signature states - I work for JobServe in the UK.  Over the past few months I have been working on a new REST API (using the ASP.Net Web API from pre-beta to RTM) and it is now in official public beta. Now of course, this isn't just so your good selves can start running your own job searches using whatever web-enabled client takes your fancy, but that is one of the cool benefits that has come out of it - and that's why we're calling it a public beta. At the time of writing, you can use the API to run almost any job search that you can on the website.  As you would expect, you can get the results in XML or JSON (also GET requests with search parameters in the query-string are supported as well).  Note that JSONP is not currently supported - but it's slated for a future release. Sounds great, how do I get in? In order to get cracking with this - you need to request an API token .  This is not an automated process but we should be able to get you set up in a