Update (6/4/2012): This post is updated to reflect the few changes that were made to the vocabularies in OData v3 and the vocabularies feature in WCF Data Services 5.0. Please download the WCF Data Services 5.0 from the download center to follow along the code samples below.


One new feature in WCF Data Services  is Vocabularies. You can learn more about vocabularies and how they are supported in OData here. In a nutshell, a vocabulary is a namespace containing terms where each term is a named metadata extension for an OData service. In this post, I’ll explain how to use this feature to produce an OData service extended with vocabularies, give an example of how to consume annotated metadata on the client side, and finally give some examples of annotations supported in the CTP.

 

Following are a few example uses which might leverage vocabularies:

 

  • Validation metadata may be invented, such that a service may describe valid ranges, value lists, expressions, etc. for properties of entity types.
  • Visualization metadata may be defined to support generic browsing and visualization of data published via OData.
  • Adaptations of micro formats or RDF vocabularies may be defined in terms of vocabularies to enable bridging and integration between OData Services and other linked data and semantic web technologies

Applying Annotations to Data Service

In the WCF Data Services October CTP it’s possible to apply annotations to a data service and produce a $metadata endpoint which is extended by vocabularies. Let’s look at how to achieve this in this new version of the WCF Data Services.

 

Applying annotations to a data service is a two-step process:

·         Author an annotations file

·         Configure the data service to use produce an annotated $metadata endpoint

Let’s take a closer look at these steps through a simple scenario. Imagine the following simple model of a Person entity shown in CSDL:

 

<Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" Namespace="VocabSample">

  <EntityType Name="Person">

    <Key>

      <PropertyRef Name="ID"/>

    </Key>

    <Property Name="ID" Nullable="false" Type="Edm.Int32"/>

    <Property Name="FirstName" Type="Edm.String"/>

    <Property Name="LastName" Type="Edm.String"/>

    <Property Name="Age" Nullable="false" Type="Edm.Int32"/>

  </EntityType>

  <EntityContainer Name="SampleService" m:IsDefaultEntityContainer="true">

    <EntitySet Name="People" EntityType="VocabSample.Person"/>

  </EntityContainer>

</Schema>

 

Suppose, as the service owner, you want to extend the service metadata using a term which describes a valid range of values for certain properties(the Age property in this case). The first thing you’d need to do is to author an annotation file targeting your EDM.

Authoring an annotations file

To support vocabularies in EDM, we specified a new CSDL syntax used to apply terms. This makes it possible to apply vocabularies using familiar EDM constructs and existing reference mechanisms. An annotations file is an XML document that specifies how terms from one or more vocabularies are applied to a target model.

The following is an example of how to apply a “Validation” vocabulary’s “Range” term to a “Person.Age” property:

 

<Schema Namespace="VocabSample" Alias="VocabSample" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">

  <Using Namespace="Org.OData.Validation.V1" Alias="Validation"/>

  <Annotations Target="VocabSample.Person/Age">

    <TypeAnnotation Term="Validation.Range">

      <PropertyValue Property="Min" Decimal="16" />

      <PropertyValue Property="Max" Decimal="90" />

    </TypeAnnotation>

  </Annotations>

</Schema>

 

The annotations file is a CSDL which uses the new annotation syntax. The Target attribute of <Annotations> specifies that the annotation is being applied to the Person entity’s Age property.. The Term attribute of the <TypeAnnotation> specifies that the term being applied is called “Range”. <PropertyValue> elements set the values for the two properties of the Range term. The “Validation” identifier used to qualify the term name in the annotations is an alias for the globally unique namespace, specified by NamespaceUri="http://vocabularies.odata.org/Validation".

The next step is to configure the data service to use the annotations and to produce an extended $metadata endpoint.

Configuring the data service

In the October 2011 CTP of WCF Data Services, we added the following property to the DataServiceConfiguration class:

 

public Func<Microsoft.Data.Edm.IEdmModel, IEnumerable<Microsoft.Data.Edm.IEdmModel>> AnnotationsBuilder

Following is an example of the InitializeServer method that shows how to use the AnnotationsBuilder to configure the service to use the annotations:

    public static void InitializeService(DataServiceConfiguration config)
    {
        //const string annotationsFile = @"D:\Code\BlogPostDemo\BlogPostDemo\Annotations.xml";
        const string annotationsFile = @"D:\Vocabularies\Annotations\Annotations.xml";
        config.SetEntitySetAccessRule("People", EntitySetRights.AllRead);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
        config.DisableValidationOnMetadataWrite = true;
        config.AnnotationsBuilder = (model) =>
        {
            IEdmModel annotationsmodel;
            IEnumerable<EdmError> errors;
            var xmlreaders = new XmlReader[] { XmlReader.Create(annotationsFile) };
            bool parsed = CsdlReader.TryParse(xmlreaders, model, out annotationsmodel, out errors);
            return parsed ? new IEdmModel[] { annotationsmodel } : null;
        };
    }

That’s it! Running the service and navigating to the $metadata endpoint will result in the following payload:

<?xml version="1.0" encoding="UTF-8"?>

<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">

  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:MaxDataServiceVersion="3.0" m:DataServiceVersion="1.0">

    <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm" Namespace="VocabSample">

      <EntityType Name="Person">

        <Key>

          <PropertyRef Name="ID"/>

        </Key>

        <Property Name="ID" Nullable="false" Type="Edm.Int32"/>

        <Property Name="FirstName" Type="Edm.String"/>

        <Property Name="LastName" Type="Edm.String"/>

        <Property Name="Age" Nullable="false" Type="Edm.Int32"/>

      </EntityType>

      <EntityContainer Name="SampleService" m:IsDefaultEntityContainer="true">

        <EntitySet Name="People" EntityType="VocabSample.Person"/>

      </EntityContainer>

      <Annotations Target="VocabSample.Person/Age">

        <TypeAnnotation Term="Org.OData.Validation.V1.Range">

          <PropertyValue Decimal="16" Property="Min"/>

          <PropertyValue Decimal="90" Property="Max"/>

        </TypeAnnotation>

      </Annotations>

    </Schema>

  </edmx:DataServices>

</edmx:Edmx>

 

Client Side

On the client side, you can use System.Data.Edm to parse the metadata and extract the annotations.  The example below illustrates how to get the vocabulary annotations applied to Person.Age:

//GET service metadata
const string metadataUri = "http://localhost:51672/vocabsample.svc/$metadata";
string metadata = GetMetadata(metadataUri);

//Parse the metadata
IEdmModel annotatedModel;
IEnumerable<EdmError> errors;
XmlReader xmlReader = XmlReader.Create(new StringReader(metadata));
bool parsed = EdmxReader.TryParse(xmlReader, out annotatedModel, out errors);

//Find the Age property of Person EntityType
IEdmProperty age = null;
if (EdmTypeKind.Entity == annotatedModel.FindType("VocabSample.Person").TypeKind)
{
    IEdmEntityType person = (IEdmEntityType)annotatedModel.FindType("VocabSample.Person");
    age = person.FindProperty("Age");
}

 

You can get the name of the terms applied to the Age property as follows:

foreach (IEdmVocabularyAnnotation annotation in annotations)
{
    if (annotation.Term.TermKind == EdmTermKind.Type )
    {
        IEdmTypeAnnotation typeAnnotation = (IEdmTypeAnnotation)annotation;
        Console.WriteLine(typeAnnotation.Term.Name);
    }
}

 

You can access the properties of a term as follows:

foreach (IEdmVocabularyAnnotation annotation in annotations)
{
    if (annotation.Term.TermKind == EdmTermKind.Type)
    {
        IEdmTypeAnnotation typeAnnotation = (IEdmTypeAnnotation)annotation;
        if (typeAnnotation.Term.Name == "Range")
        {
            IEdmPropertyValueBinding min = null;
            IEdmPropertyValueBinding max = null;
            foreach (var prop in typeAnnotation.PropertyValueBindings)
            {
            if (prop.BoundProperty.Name == "Min")
            {
                min = prop;
            }
            else if (prop.BoundProperty.Name == "Max")
            {
                max = prop;
                }
            }
            IEdmDecimalValue minVal = (IEdmDecimalValue)min.Value;
            Console.WriteLine(minVal.Value);
            IEdmDecimalValue maxValue = (IEdmDecimalValue)max.Value;
            Console.WriteLine(maxValue.Value);
        }
    }
}

 

Example Annotations

In the example above, we looked at annotating a Property of an entity. In OData annotations are supported on several CSDL elements. It’s possible to annotate the following CSDL elements in the following way assuming the service that is being annotated is the sample Northwind service.

EntityContainer

  <Annotations Target="NorthwindEntities">

    <ValueAnnotation Term="Display.Title" String="All Entities" />

  </Annotations>

EntitySet

<Annotations Target="NorthwindEntitites/Invoices">

    <ValueAnnotation Term="Display.Title" String="Order Invoices" />

</Annotations >

EntityType

<Annotations Target="NorthwindModel.Employee">

  <TypeAnnotation Term="Contact.Person">

    <PropertyValue Property="FirstName" Path="FirstName" />

    <PropertyValue Property="LastName" Path="LastName" />

  </TypeAnnotation>

</Annotations>

Property

<Annotations Target="NorthwindModel.Employee/EmployeeID">

  <TypeAnnotation Term="Display.Hide" />

</Annotations>

NavigationProperty

<Annotations Target="NorthwindModel.Customer/Orders">

  <ValueAnnotation Term="Display.Title" String="Customer Orders"/>

</Annotations>

FunctionImport

An imaginary FunctionImport and annotation:

 

  <FunctionImport Name="MyFunctionImport" EntitySet="MyEntitySet" ReturnType="Collection(Namespace.MyType)">
    <Parameter Name="Param2" Nullable="true" Mode="In" Type="Edm.String" />
    <Parameter Name="Param3" Nullable="true" Mode="In" Type="Edm.String" />
  </FunctionImport>
  <Annotations Target="MyContainer/MyFunctionImport(Edm.String, Edm.String)">
    <ValueAnnotation Term="Org.OData.Display.Title" String="My FunctionImport" />
  </Annotations>
Summary

Vocabularies feature is a significant new feature that we added to Data Services. Vocabularies increase the expressiveness of OData metadata document to enable a broader spectrum of experiences. Clients and data providers can cooperate to enable richer experiences by enhancing OData metadata with vocabularies.

Let us know what you think and make sure to visit the WCF Data Services forum if you have any questions or issues.


Turker Keskinpala

Program Manager

WCF Data Services/OData Team