How to create a RIA service wrapper for OData Source

How to create a RIA service wrapper for OData Source

Rate This
  • Comments 18

NOTE: LightSwitch in Visual Studio 2012 now supports OData natively. See LightSwitch Architecture: OData

Introduction

There has been a lot of discussion of whether there is OData support in Visual Studio LightSwitch. The answer is both Yes and No. No, there is no Native support for OData in version 1 of Visual Studio LightSwitch, but ‘yes’ there is a workaround.

LightSwitch v1 has native support for SQL Server and SharePoint data sources. But you can write some custom code to provide access to another data source. This post will show you how to access an OData source in LightSwitch by wrapping access to in a WCF RIA DomainService.

These instructions assume you already have an OData service available. There are a number of guides available on how to create a new OData service (http://msdn.microsoft.com/en-us/library/cc668810.aspx)

The basic steps of creating the RIA service wrapper are as follows:

1. Create a class library project.

2. Add a WCF Service Reference to the project to provide access to the external OData source.

3. Add a WCF RIA DomainService to expose the OData DataServiceContext.

Once your DomainService has been defined, you can add it to your LightSwitch project using via the “Add New Data Source” wizard.

clip_image001

The main point in the illustration above is: The LightSwitch data service calls an in-memory instance of a RIA DomainService which calls a DataServiceContext (generated by add-service-reference), which calls remotely to an OData service.

You should be aware that there are a couple limitations on the OData Services that can be exposed using RIA Services and LightSwitch.

Complex Types

While both OData and RIA Services support complex types on entities, due to scheduling constraints LightSwitch will not.  If a complex type property is exposed on an entity, LightSwitch will import the entity, ignoring that property.   There are a couple of workarounds for this that we will detail in another blog post.


Navigation Properties without Foreign Keys

An OData service can contain navigation properties that are not associated with any foreign key. This is likely the case with many-to-many relationships, but can also occur for 0..1-Many or 1-Many relationships. For example, the Netflix OData catalog contains a many-to-many relationship between Titles and Genre. Unfortunately, RIA Service associations are foreign key based. If an OData association is not foreign key based, there isn't a good way to represent it over a RIA Service.

If an OData service does contain these types of associations, there isn't currently a way to represent these in LightSwitch. However, in our next beta we will be adding the ability to call parameterized queries on a RIA Service from LightSwitch. Using this functionality, queries that represent these unsupported associations could be exposed. For Netflix, for example, you could define GetGenresByTitle and GetTitlesByGenre queries on your RIA Service, which call into the appropriate OData navigation properties.

With these limitations in mind, I've defined a LightSwitch-compatible OData service.  To keep things simple, this service only contains two entity types, Product and Category.  There is a 1-Many relationship between Category and Product. I’ve listed the definitions for the entities below:

Category
ID (Int32)
CategoryName (String)
Description (String)
Products (Collection of Product)

Product
ID (Int32)
ProductName (String)
QuantityPerUnit (String)
UnitsOnHand (Int32)
CategoryID (Int32)
Category (Category)

We will define our LightSwitch-compatible RIA Service in a standard class library. Using Visual Studio 2010 Professional, create a new Class Library project. To access the OData service, we will need to add a service reference to the project.   Add a service reference to your project, entering the address of your particular OData service. I’ve added my service reference using the ProductCatalog namespace.

clip_image003

Adding the service reference will define classes representing the entities, and a DataServiceContext object to read and write data from the service. To create a RIA Service, we can simply expose these defined entity classes through a DomainService class. Add a DomainService class to the project (I’ve called it ProductService in my case).

clip_image005

A prompt will appear, asking whether we’d like to base this DomainService on an existing model. We can just select <empty domain service class> and hit Ok. We’ll need to add some functions to expose queries for each OData entity. To keep things simple, we’ll define a read-only DomainService. This requires that we add a query function for each entity type we’d like to expose. For LightSwitch, we require that each entity type has a parameterless query exposed, with the QueryAttribute applied. This allows us to identify which query represents the “Select *” operation for that entity type. We can then apply additional filters to this query from LightSwitch.

I’ve added three methods to my DomainService below, Initialize, GetProducts and GetCategories. The Initialize method will be called for each request to the DomainService. Within it, I’m instantiating my DataServiceContext to communicate with the OData service. This context is then used to return the query for my other two functions.

Visual Basic:
Imports
System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.ComponentModel.DataAnnotations

Imports System.Linq

Imports System.ServiceModel.DomainServices.Hosting

Imports System.ServiceModel.DomainServices.Server

 

Public Class ProductService

    Inherits DomainService

 

    Dim _context As ProductCatalog.ProductCatalogEntities

 

    Public Overrides Sub Initialize(ByVal context As System.ServiceModel.DomainServices.Server.DomainServiceContext)

        MyBase.Initialize(context)

 

        'initialize the context. 

        _context = New ProductCatalog.NorthwindEntities(New Uri("http://localhost:56583/ProductCatalog.svc/"))

    End Sub

 

    <Query(IsDefault:=True)> _

    Public Function GetProducts() As IQueryable(Of ProductCatalog.Product)

        Return _context.Products

    End Function

 

    <Query(IsDefault:=True)> _

    Public Function GetCategories() As IQueryable(Of ProductCatalog.Category)

        Return _context.Categories

    End Function

End Class

C#:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Data;

using System.Diagnostics;

using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

using System.Linq;

using System.ServiceModel.DomainServices.Hosting;

using System.ServiceModel.DomainServices.Server;

 

public class ProductService : DomainService

{

       ProductCatalog.ProductCatalogEntities _context;

 

       public override void Initialize(System.ServiceModel.DomainServices.Server.DomainServiceContext context)

       {

              base.Initialize(context);

 

              // Initialize the context.

              // Or you can store the connection string in your web.config

              _context = new ProductCatalog.NorthwindEntities(new Uri("http://localhost:56583/ProductCatalog.svc/"));

       }

 

       [Query(IsDefault = true)]

       public IQueryable<ProductCatalog.Product> GetProducts()

       {

              return _context.Products;

       }

 

       [Query(IsDefault = true)]

       public IQueryable<ProductCatalog.Category> GetCategories()

       {

              return _context.Categories;

       }

}

LightSwitch requires that each imported entity has a primary key defined. Unfortunately, the entities defined by the service reference do not include attributes to identify the key. However, RIA Services provides a mechanism to annotate an existing class with additional attributes using a metadata class. We need to add a KeyAttribute to the key property for each entity on our OData service. I’ve done this for my Product and Category entities below.

Visual Basic:

Imports System.ComponentModel

Imports System.ComponentModel.DataAnnotations

Imports System.Data.Services.Client

 

Namespace ProductCatalog

 

    <MetadataType(GetType(Product.Metadata))> _

    Partial Public Class Product

        Friend NotInheritable Class Metadata

            <Key()> _

            Public Property ID As Int32

        End Class

    End Class

 

    <MetadataType(GetType(Category.Metadata))> _

    Partial Public Class Category

        Friend NotInheritable Class Metadata

            <Key()> _

            Public Property ID As Int32

        End Class

    End Class

End Namespace

C#:

using Microsoft.VisualBasic;

using System;

using System.Collections;

using System.Collections.Generic;

using System.Data;

using System.Diagnostics;

using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

using System.Data.Services.Client;

 

namespace ProductCatalog

{

 

       [MetadataType(typeof(Product.Metadata))]

       public partial class Product

       {

              internal sealed class Metadata

              {

                     [Key()]

                     public Int32 ID { get; set; }

              }

       }

 

       [MetadataType(typeof(Category.Metadata))]

       public partial class Category

       {

              internal sealed class Metadata

              {

                     [Key()]

                     public Int32 ID { get; set; }

              }

       }

}

We can now build this library and consume it from LightSwitch.  To do this, we need to add a data source to our LightSwitch project.   Using the Attach Data Source Wizard, we can add a reference to our RIA Service we just built and import the entities into our LightSwitch project. 

clip_image007

clip_image009

Unfortunately, the entities imported into LightSwitch do not have relationships between them.  The entities defined by our OData service reference do not contain enough information for LightSwitch to properly infer any relationships.  We will need to add additional information to our metadata classes on the DomainService to identity the relationships.  This is done by applying an AssociationAttribute on any properties that represent a relationship.   I’ve done this for my product catalog below.   CategoryID is the name of the foreign key property on the Product entity.

Visual Basic: 

Imports System.ComponentModel

Imports System.ComponentModel.DataAnnotations

Imports System.Data.Services.Client

 

Namespace ProductCatalog

 

    <MetadataType(GetType(Product.Metadata))> _

    Partial Public Class Product

        Friend NotInheritable Class Metadata

            <Key()> _

            Public Property ID As Int32

 

            <Association("Product_Category", "CategoryID", "ID", IsForeignKey:=True)> _

            Public Property Category As Category

        End Class

    End Class

 

    <MetadataType(GetType(Category.Metadata))> _

    Partial Public Class Category

        Friend NotInheritable Class Metadata

            <Key()> _

            Public Property ID As Int32

 

            <Association("Product_Category", "ID", "CategoryID", IsForeignKey:=False)> _

            Public Property Products As DataServiceCollection(Of Product)

        End Class

    End Class

End Namespace

 

C#:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Data;

using System.Diagnostics;

using System.ComponentModel;

using System.ComponentModel.DataAnnotations;

using System.Data.Services.Client;

 

namespace ProductCatalog

{

 

       [MetadataType(typeof(Product.Metadata))]

       public partial class Product

       {

              internal sealed class Metadata

              {

                     [Key()]

                     public Int32 ID { get; set; }

 

                     [Association("Product_Category", "CategoryID", "ID", IsForeignKey = true)]

                     public Category Category { get; set; }

              }

       }

 

       [MetadataType(typeof(Category.Metadata))]

       public partial class Category

       {

              internal sealed class Metadata

              {

                     [Key()]

                     public Int32 ID { get; set; }

 

                     [Association("Product_Category", "ID", "CategoryID", IsForeignKey = false)]

                     public DataServiceCollection<Product> Products { get; set; }

              }

       }

}

If we rebuild this project and import it into a LightSwitch project, we should see a relationship between Product and Category.  At this point, all of the standard LightSwitch functionality (screens, queries, business types, etc.) should work for this data source. 

Conclusion

The workaround you have just seen shows the capabilities of WCF RIA service in Visual Studio LightSwitch to get access to an OData Service. We welcome comments and feedback on what level of OData support we need in our next version. In future blog posts we will detail how to modify the data on your OData service (if supported).

Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post
  • Thanks a lot for this post! I'm definitely looking forward to the next one.

  • This code in vb would be extremely helpful.

  • @Michael - Both VB and C# code is included in the post. Are you having trouble with the example?

  • "Complex Types

    While both OData and RIA Services support complex types on entities, due to scheduling constraints LightSwitch will not.  If a complex type property is exposed on an entity, LightSwitch will import the entity, ignoring that property.   There are a couple of workarounds for this that we will detail in another blog post."

    Please explain what you mean by complex types and do you have an updated blog post?

  • @Johanthan Manley

    A complex type is a user-defined data structure that can be included as a property of an entity.  For example, I may define a complex type called Address that contains Street, City, State and ZipCode as string properties.  I can then add the Address as a property on entities I define.  

    Have not yet updated the blog post - we've made a few changes to the way we function with RIA Services so will provide an update once we release our next beta.

  • This information is really useful. Thanks. Where can I find a complete list of the attribute decorations for entity classes being exposed in this way that LightSwitch recognises?  I'm hoping to hook into my own object layer using this technique and would like to decorate my classes as fully as possible for LigthSwitch consumption.  Thank you

  • Hi David,

    Take a look at this topic in the library: msdn.microsoft.com/.../gg589479.aspx

    HTH

  • Team,

    Could you plz update this post for Beta2, if possible?

  • Interesting presentation of what should be done to take OData to LightSwitch with RIA Services.

    As one of the articles I've read about LightSwitch, talk that is a user-friendly software for non-programmers. So let me ask you a question:

    You think really that this whole procedure presented is easy to learn and use for someone not so far programmer learning to use LightSwitch?

  • @JamieH - No this technique is not meant for the LightSwitch developer but rather the professional developer looking to integrate OData sources into LS or make accessible other enterprise services. LS already supports SQL Server, Azure, SharePoint and any other datasource that provides an EF Provider (RSSBus just released one for Quickbooks) out of the box. This article is meant for folks looking to extend LightSwitch's capabilities.

    Cheers!

  • Very interesting and helpful post. however, I am trying to define a read-write DomainService. At this time, I am able to update an entity, but not to create a new one. Any advice or sample would be great.

  • The conclusion mentions "In future blog posts we will detail how to modify the data on your OData service (if supported)." I can't find any other follow up blog post on this subject.  Before I go down the road of implementing your example... is there a blog post that follows up on creating and updating entities?

  • Thanks for reminding us.  We haven't yet put togher the blog post for updating and creating entities but I'll def. try get something out soon.  If you have any specific questions, let me know.

    Thanks!

    Sheel

  • Where is the "In future blog posts we will detail how to modify the data on your OData service (if supported)"

    Please !!

    Thanks

  • That will do it for my needs! But how can I get paging to work? My OData feed exposes only a limited amount of results per request and therefore it would be nice to use the builtin paging controls of lightswitch to go back and forth between the data.

    Thanks

    Tobias

Page 1 of 2 (18 items) 12