WCF Data Services: customizing your OData feeds and making them GeoRSS compliant

Version 4.0 of the .NET Framework brings a good deal of new features to the WCF Data Services module (ex-Astoria, ex-ADO.NET Data Services), including OData protocol version 2 support; you will find on MSDN a summary page of these new features.

One of the new features that is unfortunately not often highlighted is the availability in this new version of something called Friendly Feeds, or Customizable Fields, in other words a way to customize the OData feeds generated by WCF Data Services. This feature is fully documented in the MSDN Article “Feed Customization”, and I will try in this post to give you an example of its usage.

The main idea with this feature is to allow you to personalize the Atom/AtomPub feed that is otherwise automatically generated, so that you can specify for instance how the standard Atom elements should be filled, and even add completely custom elements to the feed.

I see two main usages for this feature:

  1. Make the default standard Atom feed more relevant by specifying values for the default fields that are not filled by default; for example, elements like Title or Summary are empty by default, like AuthorName or AuthorEmail, etc. Giving values to these fields will make the feed much easier to consume for application that only understand vanilla Atom feeds and/or ignore the OData extensions, where one can find all the exposed properties.
  2. Enrich the Atom feed by following well-known extensions, like for example the GeoRSS standard that allows you to add geolocation information to an Atom or RSS feed.

To illustrate the first point, let’s consider a vanilla OData feed that represents, in my example, Vélib’ rental stations in Paris (Vélib’ is a public bicycle rental program in Paris). Entries in this feed show the station’s name and address, as well as its location by way of longitude / latitude properties. Here is one such entry:

 <entry>
  <id>https://localhost:10420/Stations.svc/StationsVelib(16122)</id>
  <title type="text"></title>
  <updated>2010-08-12T15:18:43Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="StationsVelib" href="StationsVelib(16122)" />
  <category term="ODataModel.StationsVelib" scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:id m:type="Edm.Int32">16122</d:id>
      <d:arrd>16</d:arrd>
      <d:nom_arrd>16ème arrondissement</d:nom_arrd>
      <d:nom>station_16122</d:nom>
      <d:description xml:space="preserve">...</d:description>
      <d:adresse>Route de la muette a neuilly - 75016 paris</d:adresse>
      <d:lat m:type="Edm.Double">2.2585</d:lat>
      <d:long m:type="Edm.Double">48.8798</d:long>
    </m:properties>
  </content>
</entry>

This XML snippet is perfectly usable as is, but what happens if we try to use it in an application that only understands the standard Atom format? Let’s try for example with Internet Explorer 8; when browsing the full feed, we will see something like this:

image

Not really useful! We have tons of information in the OData feed that we could display at this stage, like the name of the rental station or its description. This data is of course present in the OData properties, but IE8 doesn’t know about that. Once we have personalized our feed, we will see something like this:

image

Better, don’t you think? Furthermore, if we look at the generated XML feed, we can see another change:

 <entry>
  <id>https://localhost:10420/Stations.svc/StationsVelib(16122)</id>
  <title type="text">station_16122</title>
  <summary type="html">Route de la muette a neuilly - 75016 paris</summary>
  <updated>2010-08-12T15:22:39Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="StationsVelib" href="StationsVelib(16122)" />
  <category term="ODataModel.StationsVelib" scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:id m:type="Edm.Int32">16122</d:id>
      <d:arrd>16</d:arrd>
      <d:nom_arrd>16ème arrondissement</d:nom_arrd>
      <d:nom>station_16122</d:nom>
      <d:description xml:space="preserve">...</d:description>
      <d:adresse>Route de la muette a neuilly - 75016 paris</d:adresse>
      <d:lat m:type="Edm.Double">2.2585</d:lat>
      <d:long m:type="Edm.Double">48.8798</d:long>
    </m:properties>
  </content>
  <geo:lat xmlns:geo="https://www.georss.org/georss">2.2585</geo:lat>
  <geo:long xmlns:geo="https://www.georss.org/georss">48.8798</geo:long>
</entry>

</HTML

In addition to the fact that th standard Atom elements are now correctly filled in, we can see that the geographic location of the rental station is now exposed in the form of geo:lat and geo:long elements, with the entry element itself, which makes our feed compliant with GeoRSS. We can now directly consume this feed in mapping applications that understand GeoRSS, like, for example, Bing Maps:

image

So… How did we DO this?

In summary, the idea is to modify the EDMX (Entity Data Model) file, that is generated by Visual Studio when you add an ADO.NET Entity Data Model to your project, in order to add in the CSDL part (Conceptual Schema Definition) some additional attributes that will allow us to tell WCF Data Services what it should do. These additional attributes are not part of the standard CSDL format and are thus not directly supported by Visual Studio’s tooling; we must hence edit the EDMX file by hand.

In order to do this, you will open your EDMX file with the XML editor (by right-clicking on the file). You can collapse the edmx:StorageModels section, and we will focus on our EntityType element, when the modifications are to be made.

Here is my original EntityType:

 <EntityType Name="StationsVelib">
  <Key>
    <PropertyRef Name="id" />
  </Key>
  <Property Name="id" Type="Int32" Nullable="false" />
  <Property Name="arrd" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="nom_arrd" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="nom" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="description" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
  <Property Name="adresse" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="lat" Type="Double" />
  <Property Name="long" Type="Double" />
</EntityType>

The extensions offered by WCF Data Services will materialize as additional attributes that we will add to the Property elements that we want to change. Here are the attributes I am going to use in this example:

FC_TargetPath The element where we want the property to appear in the generated feed. We can specify the path of any element, or we can use predefined identifiers for standard Atom fields like SyndicationTitle, SyndicationSummary, etc.
FC_ContentKind The content type, i.e. plain text or HTML
FC_NsPrefix For a non-Atom element, the prefix to use
FC_NsUri Again for a non-Atom element, the URI of the namespaces
FC_KeepInContent Should the original property be kept in the feed?

Again, these attributes are extensively documented in MSDN.

In order to use these extensions, we must first reference the new schema in our EDMX file:

 <edmx:Edmx Version="2.0" xmlns:edmx="https://schemas.microsoft.com/ado/2008/10/edmx" xmlns:m="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">

Then we can make our changes. Here is my modified EntityType:

 <EntityType Name="StationsVelib">
  <Key>
    <PropertyRef Name="id" />
  </Key>
  <Property Name="id" Type="Int32" Nullable="false" />
  <Property Name="arrd" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="nom_arrd" Type="String" MaxLength="255" Unicode="true" FixedLength="false" />
  <Property Name="nom" Type="String" MaxLength="255" Unicode="true" FixedLength="false"
            m:FC_TargetPath="SyndicationTitle"
            m:FC_ContentKind="text"
            m:FC_EpmKeepInContent="true" />
  <Property Name="description" Type="String" MaxLength="Max" Unicode="true" FixedLength="false" />
  <Property Name="adresse" Type="String" MaxLength="255" Unicode="true" FixedLength="false"
            m:FC_TargetPath="SyndicationSummary"
            m:FC_ContentKind="html"
            m:FC_EpmKeepInContent="true" />
  <Property Name="lat" Type="Double"
            m:FC_TargetPath="lat"
            m:FC_NsUri="https://www.georss.org/georss"
            m:FC_NsPrefix="geo" m:FC_KeepContent="true" />
  <Property Name="long" Type="Double"
            m:FC_TargetPath="long"
            m:FC_NsUri="https://www.georss.org/georss"
            m:FC_NsPrefix="geo" m:FC_KeepContent="true" />
</EntityType>

You can see I have added attributes to the properties I wanted to exposed in a different way. In my case, I have mapped the station name to the Atom SyndicationTitle, the address to the Atom SyndicationSummary, and I have mapped the lat and long fields to brand new elements following the GeoRSS specification.

I hope this example was useful to show you how you can easily customize your WCF Data Services OData feeds in order to make them easier to read and consume, and to expose your geolocation data as standard GeoRSS feeds.