Skip to content
oising edited this page Feb 4, 2013 · 1 revision

In my last post, I gave an introduction of the WCF Data Services Toolkit and illustrated how it provides a simple abstraction on top of WCF Data Services, which hopefully makes it easier to layer OData on top of arbitrary data sources (e.g. existing web API). For the sake of simplicity, I only showed the read-only scenario-which makes up a large majority of customer needs-but I also need to show how you could also handle updates (POST/PUT/DELETE) within your toolkit services.

Because the toolkit is built on top of WCF Data Services, you could obviously implement IDataServiceUpdateProvider (or IUpdateable) on your data context type (the T in [O]DataService) and be good to go. The “problem” with that approach is that the toolkit encourages your context to be pretty thin, containing little more than your entity declarations (e.g. User) and repository mappings (e.g. User -> UserRepository). Using the toolkit’s repository paradigm, it’d make much more sense to place the update handling within each of your respective repositories, keeping the logic for each entity isolated and simplified.

If you’ve dug around the toolkit bits much, you might have stumbled upon an interface called IWriteableRepository and wondered what it is for. It simply allows you to mark a repository as being capable of handling updates (or writes), and implement a few simple methods to do so: CreateDefaultEntity, Remove, Save and CreateRelation. These map somewhat closely to IUpdateable/IDataServiceUpdateProvider, but live on your repository not your context.

The way this works is that the ODataContext class (which your context type needs to inherit from) is itself an IDataServiceUpdateProvider and will do the translation between that interface and IWriteableRepository, if your repository implements it. So when an update request comes in for a specific entity type, the runtime will ask which repository handles it and pass the request on appropriately. Let’s check out a demo of how this might work. We’ll use the Twitpic OData service (which is implemented using the toolkit) as an example.

Let’s do a quick rundown of the codebase. I have a context type called TwitpicData that inherits from ODataContext. It declares a handful of IQueryable properties and then overrides the RepositoryFor method to map entity types to their respective repository. It looks something like this:

public class TwitpicData : ODataContext { private readonly TwitpicProxy proxy;

public TwitpicData(TwitpicProxy proxy)
{
    this.proxy = proxy;
}

public IQueryable<Image> Images
{
    get { return this.CreateQuery<Image>(); }
}

public override object RepositoryFor(string fullTypeName)
{
    if (fullTypeName == typeof(Image).FullName || fullTypeName == typeof(Image[]).FullName)
        return new ImageRepository(this.proxy);

    throw new NotSupportedException();
}

}

NOTE: There are more entity types in the production service (e.g. User, Tag), but we’re only going to focus on Image, so I trimmed the code down. The TwitpicProxy class that you see is simply the class that knows how to make calls to the Twitpic REST API.

At this point, everything looks the same as examples we saw in the previous post, but interestingly enough, Twitpic’s API allows you to upload images, so the ImageRepository would need to handle writes.

If we look at the repository, in addition to having the expected read methods (e.g. GetOne, GetAll) and foreign property methods (e.g. GetImagesByUser, GetImagesByTag), it also implements IWriteableRepository. public class ImageRepository : IWriteableRepository

With that in place, the toolkit runtime knows to allow Image writes and who will handle them. Let’s look at the four methods the interface includes, and see how they work in the context of Twitpic.

public object CreateDefaultEntity() { return new Image { ShortId = string.Format(CultureInfo.InvariantCulture, "temp-{0}", DateTime.Now.Ticks) }; }

The CreateDefaultEntity method will be called when an add operation (POST) is being performed. All that it needs to do is create an instance of the respective type that puts it in the correct state. For many service implementations this will simply just need to “new up” an instance of the type and return it. In the case of Twitpic, when an Image type is created, we give it a temporary ID that is used to do some back-end correlation (we store the image in Azure blob storage before actually uploading it via Twitpic’s API). The details of that are outside the scope of this post, but suffice it to say, we’re just creating a new Image type that can be filled from the data being sent by the user.

Once the Image type has been created, the runtime will populate its properties with any data that was POSTed by the user. Once it has completed that, it will call the Save method on your repository, which is simply responsible for actually persisting the entity. Twitpic’s looks something like this…

public void Save(object entity) { var image = entity as Image;

if (image.Message == null || !image.ShortId.StartsWith("temp-"))
    return;

var imageContents = [Comes from somewhere]
this.proxy.UploadImage(imageContents, image.Message, image.ShortId, image.Type);

}

It simply checks that the incoming image has a Message and that its ShortId property starts with “temp-“, signifying that it’s a new image. It grabs the binary content of the updated image (omitted from the post) and then routes the call to Twitpic’s API. Notice the “image.Message” and “image.Type” are now populated. Those values came from the user.

When a user modifies an existing entity instance, CreateDefaultEntity obviously won’t be called, but Save will. It’s up to you to determine whether the entity passed to the Save method is being added or deleted. Twitpic doesn’t support image updates so we don’t need to worry about that here.

When the user deletes an entity instance, neither the CreateDefaultEntity or Save method will be called. Instead, the Remove method will be called, passing you the instance being deleted.

public void Remove(object entity)
{
    throw new NotImplementedException();
}

Twitpic doesn’t allow you to delete an image, so this repository doesn’t make use of it, but you can imagine how simple it’d be to pass the deletion on to your underlying data source.

The final method to implement on the IWriteableRepository interface is CreateRelation. This is called when the user adds a relation between two entity types, such as adding a Comment to an Image.

public void CreateRelation(object targetResource, object resourceToBeAdded)
{
    var image = targetResource as Image;
    var comment = resourceToBeAdded as Comment;

    comment.Image = image;
    this.proxy.CreateComment(comment);
}

The method simply takes a Comment (the resource being added), associates it with an Image (the target of the relation), and passes it on to the Twitpic API.

That’s it. Now we have an OData service that provides users with read and write operations of Twitpic images, and adding the write functionality only took about 20 lines of trivial code (aside from the Twitpic specific code). The service could now be consumed using the WCF Data Services client or any other OData/HTTP consumer that knows how to perform write operations.

If you read through this post and thought: “Hey, where is all the meat of the write operations? You kept showing this Twitpic proxy thing but didn’t actually go into the code”, I’d respond with: “Exactly”. My whole objective with the toolkit is to handle as much of the infrastructure/OData plumbing as possible and let you focus on the portion that you know best: communicating with your underlying data source. Chances are, you know your data really well, and so as long as we give you the right hooks, and make it easy, your job shouldn’t be too bad.

NEXT STEPS: While I think this is pretty simple, there is room for improvement in numerous areas. For instance...

  • CreateDefaultEntity should be optional since many implementations will only be creating a new instance.
  • The current IWriteableRepository interface isn’t generic so all the method parameters are of type object. We could make a generic version but that wouldn’t help with the CreateRelation method, since the “resourceToBeAdded” could be numerous different types.
  • I ultimately want to remove IWriteableRepository and make the write methods discoverable via conventions, the same way the read methods are. This would solve the above two issues and also allow you to have multiple CreateRelation methods (e.g. CreateCommentRelation, CreateTagRelation).

I’m doing a session on the toolkit at MIX in a few weeks and would love to chat with folks using it or looking to use it. Please don’t hesitate to send any feedback or comments my way.

Clone this wiki locally