Additional Feed Customization Support
1. Problem:
In the previous version, WCF Data Services introduced the feed customization feature. This feature enabled users to map properties to certain elements in the Atom document. As an example, users could map a name property to the Atom title element. This is especially useful for generic feed readers which may not know how to interpret the properties in the content element but understand other Atom elements.
The OData protocol, and therefore our implementation, did not allow users to map to the repeating Atom link and category elements. In addition, users never had the ability to map properties based on a condition.
2. Design:
We have extended the set of Atom elements that can be mapped and introduced a new feature known as Conditional Entity Property Mapping (EPM) to solve the issues discussed above. This new feature includes 2 parts. First, users can specify standard, unconditional mapping to Atom link and category elements. Second is the ability to map to the Atom link and category elements based on a condition. For example, only apply the mapping when the link element’s rel attribute (denoted as link/@rel) has a value equal to “https://MyValue”. We’ll get into some specifics later on in this post but the condition only applies to the link/@rel and category/@scheme attributes. Conditional mapping is not supported for other Atom or Custom feed elements at this time.
3. Understanding the Feature:
3.1 Extending Feed Customizations (Unconditional Mappings):
Let’s first start with unconditional mapping to Atom link and category elements. This is the same process as the feed customization introduced in the previous version.
Mappings are described on classes through the EntityPropertyMappingAttribute. This attribute is present on generated client classes to describe the mapping, and is used to define mappings exposed by the reflection provider:
public EntityPropertyMappingAttribute(
string sourcePath,
SyndicationItemProperty targetSyndicationItem,
SyndicationTextContentKind targetTextContentKind,
bool keepInContent
)
We updated the SyndicationItemProperty enum to include the link and category element’s attributes. The full list of updates can be found in the table below.
A service describes mapping through the CSDL exposed by its $metadata endpoint. These same CSDL annotations are used to describe mappings to the Entity Framework (EF) provider. The set of valid values for FC_TargetPath has been extended to include the new link and category attributes:
<Property Name="Term" Type="String" Nullable="false"
MaxLength="40" Unicode="true" FixedLength="false"
m:FC_TargetPath= "SyndicationCategoryTerm"
m:FC_ContentKind="text"
m:FC_KeepInContent="false"
/>
The additions to both the SyndicationItemProperty enum and FC_TargetPath attribute can be found in the table below:
EntityPropertyMapping SyndicationItemProperty |
CSDL FC_TargetPath |
LinkHref |
SyndicationLinkHref |
LinkRel |
SyndicationLinkRel |
LinkType |
SyndicationLinkType |
LinkHrefLang |
SyndicationLinkHrefLang |
LinkTitle |
SyndicationLinkTitle |
LinkLength |
SyndicationLinkLength |
CategoryTerm |
SyndicationCategoryTerm |
CategoryScheme |
SyndicationCategoryScheme |
CategoryLabel |
SyndicationCategoryLabel |
In the mapping described above, the “Term” property is mapped to “SyndicationCategoryTerm” and the property is also kept in the content element. Assuming the value of the “Term” property is “MyTermValue” and no other mappings exist, that section of the entry would look something like:
…
<category term="MyTermValue" />
<content type="application/xml">
<m:properties>
…
</m:properties>
</content>
When mapping to the Atom link element, there is an additional rule to consider. Since an empty link/@rel value is interpreted as “alternate” (RFC4287), and the OData protocol does not allow mapping link/@rel to simple identifiers (eg. edit, self, alternate, …), when defining unconditional mapping to the link element we must specify a mapping to link/@rel:
[EntityPropertyMappingAttribute(
"MyHref",
SyndicationItemProperty.LinkHref,
SyndicationTextContentKind.Plaintext,
true)]
[EntityPropertyMappingAttribute("MyRel",
SyndicationItemProperty.LinkRel,
SyndicationTextContentKind.Plaintext,
true)]
[DataServiceKeyAttribute("id")]
public class Photos
{
public int id { get; set; }
public string MyHref { get; set; }
public string MyRel { get; set; }
}
3.2 New Conditional Entity Property Mappings:
Now that we’ve covered the extensions to feed customization mappings, let’s look at the second part of this feature, conditional mapping. There are situations where a user may only want to map to Atom link and category elements when they satisfy a certain condition. For example, assume we have a model that contains a collection of photos. These photos may differ in their resolution (eg. Hi-res, low-res, thumbnail). It makes sense to map the resolution to a category element since it helps to distinguish the different photos. Conditions are useful here because there may be other category elements we don’t care about.
In order to use conditions in the mappings, we introduced a new EntityPropertyMappingAttribute constructor:
public EntityPropertyMappingAttribute(
string sourcePath,
SyndicationItemProperty targetSyndicationItem,
bool keepInContent,
SyndicationCriteria criteria,
string criteriaValue
)
Notice the addition of the criteria and criteraValue parameters. The SyndicationCriteria parameter is a new enum with values:
· LinkRel
· CategoryScheme
Similarly, we are introducing new CSDL attributes for FC_Criteria and FC_CriteriaValue. For FC_Criteria, the corresponding values are:
· SyndicationLinkRel
· SyndicationCategoryScheme
The criteria indicates the attribute on which the condition exists. For example, if the criteria equals “CategoryScheme”, the condition would depend on the value of the category/@scheme.
The criteriaValue parameter specifies the value that must be matched in order for the mapping to apply. To make this clearer, let’s look at the following example:
[EntityPropertyMappingAttribute(
"Term", // sourcePath
SyndicationItemProperty.CategoryTerm, // targetPath
false, // keepInContent
SyndicationCriteria.CategoryScheme, // criteria
"https://foo.com/MyPhotos")] // criteriaValue
[DataServiceKeyAttribute("id")]
public class Category
{
public int id { get; set; }
public string Term { get; set; }
}
To understand what this mapping describes, we’ll break it down into the serialization and deserialization steps. When serializing to a feed, the “Term” property will be mapped to category/@term with category/@scheme equal to the criteriaValue, “https://foo.com/MyPhotos”. Assuming the “Term” property has a value of “MyTermValue”, the mapping above would generate a feed like:
<entry>
…
<category term="MyTermValue" scheme="**https://foo.com/MyPhotos**" />
<content type="application/xml">
<m:properties>
…
For deserialization, if the entry has a category element where category/@scheme equals “https://foo.com/MyPhotos”, the mapping will occur. All other category elements will be ignored by this mapping. It is important to know that, when the value of the attribute and criteraValue are compared, the comparison is for case insensitive, non-escaped, and equal string values.
To finish off the examples, here is a conditional mapping to the link element:
[EntityPropertyMappingAttribute(
"MyHref", // sourcePath
SyndicationItemProperty.LinkHref, // targetPath
false, // keepInContent
SyndicationCriteria.LinkRel, // criteria
"https://MyRelValue")] // criteriaValue
[DataServiceKeyAttribute("id")]
public class Photos
{
public int id { get; set; }
public string MyHref { get; set; }
}
Notice how the condition is on link/@rel. Since the criteriaValue is not a simple identifier, this mapping automatically satisfies the simple identifier rule described above. When serializing to a feed, this will look like:
…
<link href="https://hrefValue.com/ " rel="https://MyRelValue" />
…
To summarize, this new feature includes 2 parts. The first part extends Feed Customization to the Atom link and category elements. The second part introduces mapping based on conditions.
Julian Lai
WCF Data Services, Program Manager