Vocabularies in WCF Data Services


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


Comments (11)

  1. Vaccano says:

    This is a great feature!

    Some feedback:

    It seems that you are exposing way more of the reading and parsing than need be.  Especially on the client side.

    Why not automatically read and parse the Metadata file and create the vocabulary objects side by side with their object tree?

    For example (using your example above):

    When I add a service endpoint and the Person class is generated,  It should have a IEnumerable<IEdmVocabularyAnnotation> AgeVocabulary object on it.  Already setup.  

    When the parsing is done, it is clear that it is for the "Age" property.  So naming it AgeVocabulary (or AgeSomething) should not be too hard.

    The parsing code is boiler plate.  Why not just hide it and make it all work easier out of the box?

    The same goes for the server side.  AnnotationsBuilder looks to be completely boiler plate code.  Just let me provide you the path to my XML file and have the parsing done automatically.  (Maybe let me override it if I have a compelling reason.  (But most people will not have a compelling reason.))

    Don't let my feed back make you think I don't like this feature.  It looks great!

  2. Vaccano says:

    In retrospect, I can see how adding it to the business class could cause massive problems for some client side apps.  It may not be a good suggestion.

    However, I still think that my point about automatically creating validation objects when the client side classes are made is a good one.  (Just maybe not on the actual business class.)

  3. maybe a better integration System.ComponentModel.DataAnnotations Namespace would be better? says:

    any comments on why not using System.ComponentModel.DataAnnotations Namespace? thx

  4. Vaccanoll says:

    >>As always, we are very interested in hearing your thoughts and feedback.

    An occasional, "Thanks for your feedback" on some of your posts would make this more believable.

  5. Turker says:

    Vaccanoll, I apologize for the very late reply on this thread. Thank you very much for your early feedback.

    I hear you on the feedback for the client side. We are focusing on the usability and completeness of it at the moment. AnnotationsBuilder was intended to give the flexibility to the user as to how to store and load the annotations.

  6. LeonardChaney says:

    I have been looking around for information on how to document or annotate the oData metadata.  Rather than sending each user a list of the fields and descriptions, I'd like it to appear in the metadata.  That might not be what they are looking for but it is getting there and is readable.

    From this article, it looks like the best we can do right now is to have the WCF service include a static XML file containing the annotations and that is sufficient although not the best solution going forward.  

    We do not use the EDM designer but rather our own classes using iQueryable and DataServiceXXXX.  Is there a way we can code annotations into the classes?

    Sincerely,

    Len

  7. LeonardChaney says:

    Also, my desire is for a simple description attached to each data element in the metadata.  Here is what I have found so far.  Is this correct?

    <Annotations Target="DataServiceSite.City">

    <ValueAnnotation Term="Display.Title" String="City in which the site is located"/>

    </Annotations>

    <Annotations Target="DataServiceSite.State">

    <ValueAnnotation Term="Display.Title" String="State in which the site is located"/>

    </Annotations>

    <Annotations Target="DataServiceSite.ZIP">

    <ValueAnnotation Term="Display.Title" String="ZIP code of the City"/>

    </Annotations>

    Does this look correct?

    I also want to document/annotate some enums.  My best guess is that I would use a validation vocubulary although I don't want to validate but it does document to choices.  Can you show me how that would look?

    It appears that this is pretty new so there is not a lot of infor out there right now or at least I cannot find it.  Any help would be greatly appreciated.

    Sincerely,

    Len

  8. Hi Len,

    WCF Data Services currently do not support adding annotations into classes e.g a fluent api for annotations. As you mention currently the way achieve your scenario is writing a so called annotations file and configuring the service to pass your annotations on to the $metadata endpoint.  

    Your annotation example looks syntactically correct. It is hard to say if it semantically what you want without knowing your model and scenario. You seem to be annotating the City EntityType or ComplexType with the Title term from a vocabulary with the strong name Display and you are assigning a string value for the Title. Without knowing your scenario, if I were a client who understands the TItle term from your Display vocabulary, one way I could interpret the annotations is that, I should use the given string values for the types City, State and ZIP as Title instead of say using hte Name properties of those types.

    I realize the content is limited to blog posts right now and we are planning to publish a more detailed post on vocabularies soon. Please make sure you visit the forum (social.msdn.microsoft.com/…/threads) for questions and issues.

  9. Hi Turker,

    Glad to hear that there is still progress being made on this – I look forward to your upcoming post.  I wish that you guys would make it just work end to end for people that are using entity framework and code first POCOs.  That is:

    I can put data annotation attributes in my POCOs on the server side and have them transparently flow thru to the client.  I don't want to have to create/maintain a custom annotations file, I just want it to read the annotations from my code first POCOs, put that in the metadata, and then in the case of silverlight, have the Add/Update Service Reference code generator be smart enough to read the metadata and setup IDataErrorInfo style validation on the generated data objects I will use on the client side.  if you could do that, I think you'd make me and a lot of other people really happy!  You could do something similar on the clientside for Javascript that would read the metadata and produce validation logic for upshot or whatever toolkit was being used.  

    Just having to define validation logic in one place would be very nice.

  10. I am having difficulties using the vocabular based on the samples above.

    I am pretty new at this but two things appear to be wrong.

    I believe:

    bool parsed = CsdlReader.TryParse(xmlreaders, out annotationsmodel, out errors, model);

    should be:

    bool parsed = CsdlReader.TryParse(xmlreaders, model, out annotationsmodel, out errors);

    bassed on: msdn.microsoft.com/…/hh919633(v=vs.103).aspx

    Also in the annotations file I believe there needs to be a Namespace in the Using and not a NamespaceUri:

    <Using NamespaceUri="vocabularies.odata.org/Validation" Alias="Validation"/>

    according to this:

    msdn.microsoft.com/…/bb738545(v=vs.100).aspx

    Is there another example, possibly with code for downloading available?

    Thank you in advance.

  11. @wvii, I apologize for the trouble. You correctly spotted some of the issues. I just updated the blog post to reflect some changes that were made in the 5.0 RTM release. Could you please try again with the updated code snippets and see if you still have the problem?

    Thank you.