ADO.NET Data Services Friendly Feeds , Mapping CLR Types

As I mentioned in my last blog post , here are some samples of how to map your entity properties to Atom/custom markup in the atom:entry element.

You can apply Friendly Feed mappings on the CLR entity types by decorating the Entity classes with the EntityPropertyMappingAttribute type.
We will focus on the kinds of mappings and how to achieve them .

1) Mapping to ATOM elements in the atom:entry payload :  

The EntityPropertyMapping (EPM) attribute has two constructors , one which binds the property to an Atom element in the feed ,
and another which binds the property to a custom element . We shall discuss the former in this section.

For ATOM Mappings , the EPM Attribute constructor takes the following parameters.

  1. propertyName : The property of the Entity Type whose value should be mapped
  2. targetSyndicationItem : The atom:entry element to which this property has to be mapped to
  3. targetTextContentKind : The content-type of the mapped  atom:entry element
  4. keepInContent : set this to false if you want the entity’s property value to turn up only in the mapped atom:entry element and not in the <contents> section.

Lets proceed  , using the same BlogPost  type that we discussed last time .

 public class BlogPost
{
    public double Lat { get; set; }
    public double Long { get; set; }
    public int BlogPostID { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public string Author { get; set; }
    public string PostURI { get; set; }
    public string ContentSummary { get; set; }
}

1. Map the “Title” property  of the BlogPost Entity type to the entry:title element

2. Map the “Author” property to entry:author element 

This is what the code would look like :

 [EntityPropertyMapping(
       "Title",/*Source property path , the property of the Entity type to be mapped*/
       SyndicationItemProperty.Title,/* Syndication item to which the Source Property is mapped*/
       SyndicationTextContentKind.Plaintext,/* Syndication content kind for the syndication item this property is bound to */
       true/* If  false the property value is only placed at the mapped location & removed from the <content> section of the atom:entry*/
       )]
[EntityPropertyMapping("Author", SyndicationItemProperty.AuthorName, SyndicationTextContentKind.Plaintext, true)]
public class BlogPost

As described in  my previous blog post , you can map an Entity’s properties to the following atom:entry elements in the payload :

atom:entry Element TargetSyndicationItem
entry:author/email SyndicationItemProperty.AuthorEmail
entry:author/name SyndicationItemProperty.AuthorName
entry:author/uri SyndicationItemProperty.AuthorUri
entry:published SyndicationItemProperty.Published
entry:rights SyndicationItemProperty.Rights
entry:summary SyndicationItemProperty.Summary
entry:title SyndicationItemProperty.Title
entry:Updated SyndicationItemProperty.Updated
entry:contributor/name SyndicationItemProperty.ContributorName
entry:contributor/email SyndicationItemProperty.ContributorEmail
entry:contributor/uri SyndicationItemProperty.ContributorUri

 

2) Mapping to non-ATOM/custom elements in the atom:entry payload :  

For non-ATOM/custom Mappings , the EPM Attribute constructor takes the following parameters.

  1. propertyName : The property of the Entity Type whose value should be mapped
  2. targetName  (Target Path)   : The xml path markup which describes the path to the custom markup this property should be mapped to .
  3. targetNamespacePrefix: The xml prefix for the custom element/attribute that this property is mapped to.
  4. targetNamespaceUri: The xml namespace to which the custom element/attribute that this property is mapped should be under.
  5. keepInContent : set this to false if you want the entity’s property value to turn up only in the mapped atom:entry element and not in the <contents> section.

The Xml Path syntax for custom mappings.

this syntax is very logical and looks like the following .

Lets say that you wanted to map a property to a custom element in markup that looks like this :

 <mycustomRoot xmlns=”https://www.mycustomFormat.org”>
  <customElement>property value goes here</customElement>
</mycustomRoot>

for this example ,

targetName  would be “mycustomRoot/customElement”

targetNamespacePrefix would be an empty string as this markup has no custom prefix.

targetNamespaceUri would be “https://www.mycustomFormat.org

keepInContent is subjective to whether you want to keep the property value in the <content> section or not.

and now , lets say that you wanted to map a property to a custom attribute of an element in markup that looks like this :

 <mycustomRoot xmlns:me="https://www.georss.org.georss">
  <me:customElement customAttribute="property value goes here"></me:customElement>
</mycustomRoot>

for this example ,

targetName  would be <“mycustomRoot/customElement/@customAttribute>”

targetNamespacePrefix would be “me”.

targetNamespaceUri would be “https://www.mycustomFormat.org”

keepInContent  is subjective to whether you want to keep the property value in the <content> section or not.

A note , the complexity of your custom markup has a direct effect on the performance costs  for Serialization/De-Serialization of the entity type .

with this ,example , lets map the lat & long properties to geoRss markup ,which looks like this :

 <geo:lat xmlns:geo="https://www.georss.org/georss">47.684</geo:lat>
<geo:long xmlns:geo="https://www.georss.org/georss">-122.122</geo:long>

Final type definition looks like this :

    [EntityPropertyMapping(
        "Title",/*Source property path , the property of the Entity type to be mapped*/
        SyndicationItemProperty.Title,/* Syndication item to which the Source Property is mapped*/
        SyndicationTextContentKind.Plaintext,/* Syndication content kind for the syndication item this property is bound to */
        true/* If  false the property value is only placed at the mapped location & removed from the <content> section of the atom:entry*/
        )]
    [EntityPropertyMapping("Author", SyndicationItemProperty.AuthorName, SyndicationTextContentKind.Plaintext, true)]
    [EntityPropertyMapping("Lat",/*Source property path , the property of the Entity type to be mapped*/
        "lat",/*The xml path markup which describes the path to the custom markup this property should be mapped to . */
        "geo",/*The xml prefix for the custom element/attribute that this property is mapped to. */
        "https://www.georss.org/georss", /*The xml namespace to which the custom element/attribute that this property is mapped should be under*/
        true /*set this to false if you want the entity’s property value to turn up only in the mapped atom:entry element and not in the <contents> section. */
        )]
    [EntityPropertyMapping("Long", "long", "geo", "https://www.georss.org/georss", true)]
    public class BlogPost
    {
        public double Lat { get; set; }
        public double Long { get; set; }
        public DateTime Published { get; set; }
        public int BlogPostID { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string Author { get; set; }
        public string PostURI { get; set; }
        public string ContentSummary{ get;set;   }
        public string IconUri {get;set;}
    }

Special cases

I . Complex type properties

Consider the following model ,

 public class Address
    {
        public long DoorNumber { get; set; }
        public string Street { get; set; }
        public int ZipCode { get; set; }
    }
    [DataServiceKey("CustomerID")]
    public class Customer
    {
        public int CustomerID { get; set; }
        public Address myAddress { get; set; }
    }

and lets say that you wanted to map the property Street of the complex type Address when accessed through the

entity type “Customer”  to the atom:title element ,

this can be achieved via setting the propertyName property to an appropriate path .

because propertyName not just takes the property name , it also accepts a path to the property’s location. 
 
in this case , the propertyName would be : “myAddress/Street”  , and the Epm attribute would look like this :

 [DataServiceKey("CustomerID")]
[EntityPropertyMapping(
        "myAddress/Street",
        SyndicationItemProperty.Title,
        SyndicationTextContentKind.Plaintext,
        true
        )]
public class Customer
{
   public int CustomerID { get; set; }
   public Address myAddress { get; set; }
}

II Mapping properties declared on base type
Consider this data model :

 [DataServiceKey("CustomerID")]
public class Customer
{
        public int CustomerID { get; set; }
        public Address myAddress { get; set; }
        public string BaseTypeField { get; set; }
}

public class DerivedCustomer : Customer
{
  public string DerivedTypeField { get; set; }
}

and you want to map a property declared on the Base type “Customer” on the derived type “DerivedCustomer”

The type declaration would look like this :

 [EntityPropertyMapping("BaseTypeField", SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, true)]
public class DerivedCustomer : Customer {
   public string DerivedTypeField { get; set; }
}

In other words , when specifying the propertyName , property paths always make simple properties look like they are declared

on the type which has annotations , mapping inherited properties is no different from mapping properties declared on the same type.

Considerations for location of EPM Attribute

Use the following guidelines to decide where in the entity model you should add the attribute for either ATOM or custom Mappings,

Where is property defined ? Where do I apply the EPM attribute?
Simple property on the Entity Type ex: “Title” property of “BlogPost” type above Entity Type
Simple property on entity’s base type Derived Entity Type or Base Entity Type
Complex Property on the Entity Type Complex Types cannot be mapped directly See , 3
Simple property defined on complex type which is a property on an Entity Type Entity Type

Hope you enjoyed this post about applying Friendly Feeds mappings to CLR data models.

The next blog post will discuss how to apply the same mappings to EDM models.