Watch recordings of Brian's WCF RIA Services webinars:
This article is Part 5 of the series WCF RIA Services.
Introduction
In this article, I am going to quickly cover the metadata and shared code facilities of WCF RIA Services. In a nutshell, metadata classes allow you to add functionality in the form of attributes to the code generated entities that you are using in your services and on the client. You can add attributes that causes validation to happen, which I will go into more detail on in the next article, as well as attributes that affect the way entities are related and transmitted across the wire by RIA Services. With shared code, you can define chunks of logic or partial class extensions for your entities that are defined once on the server side, but get code generated on the client side as well so that the same logic can be used in both places.
For this article and moving forward with the rest of the series, I am going to depart from the step-by-step instruction format for the articles. Because I am building and adding functionality to the application with each article to show some of the more sophisticated capabilities of RIA Services, I cannot easily lead you through writing all the code without writing an unnecessarily long tome for each article. So I’m going to switch to a more descriptive format where I will simply highlight the appropriate sections of the code for the concepts discussed in the sections of the article.
You can find the completed code for this article here. In this articles version of the application, I have added the ability to display and add time entries associated with a task.
Metadata Classes
When working with WCF RIA Services, your client side entities are code generated at compile time as discussed in earlier articles in the series. At a simplistic level, WCF RIA Services tools reflect on the compiled entity types at compile time and generate entities with the same set of properties on the client side. However, any methods or behavior embedded in the property definitions that are part of the entity type on the server side are not carried over to the client side. If you are working with POCO entities, many attributes on your properties will also be regenerated on the client side. Those attributes can include validation attributes from the data annotations namespace, or they can include certain attributes that are used by RIA Services to influence how it tracks changes on related objects and which related objects it transfers across the wire.
The challenge is that if you are using a technology like Entity Framework, the entity classes themselves are generated code that you should not be directly modifying because it will be regenerated if you make modifications to the model in the designer. So you can’t really add attributes to the entity definitions themselves. You might start to think - “well, I can just use partial classes to add what I want”. But you would be wrong. While it is easy to add methods and new properties to a class via partial classes, you cannot really modify existing properties via a partial class extension.
Along come metadata classes to the rescue. A metadata class, or “buddy class” as it is called, is a way to solve this conundrum. A metadata class is a simple class that defines the same or a subset of the same properties defined by one of your entity classes, but those properties have no implementation and won’t actually be called at all. They are just a place where you can add attributes, and those attributes will be added to the corresponding properties in the code generated client entities. Depending on what the attributes are, they will also be used by RIA Services on the server side.
Let me show an example. In the sample application for this article, I have added the ability to display and add child TimeEntry objects to a Task object. In the Entity Data Model for the sample, the Task type has a 0 to many relationship with a set of child TimeEntry types. In the object model, this looks like this:
As mentioned, these entities are defined through code generation by Entity Framework, so I don’t have the opportunity to modify them.
If I want to make the Description property on a TimeEntry a required field, I can use the [Required] attribute from the System.ComponentModel.DataAnnotations namespace. If I do that, I would get automatic validation of that in the UI so I end up with a UI that looks like this:
I can actually get this pretty much “for free” with RIA Services and Silverlight by just adding a [Required] attribute to the Description property on the TimeEntry type. However, I can’t put it in the entity code generated by Entity Framework, so I need a metadata “buddy” class to add it to the entity.
My metadata class looks like this:
[MetadataTypeAttribute(typeof(TimeEntry.TimeEntryMetadata))]
public partial class TimeEntry
{
internal sealed class TimeEntryMetadata
{
// Metadata classes are not meant to be instantiated.
private TimeEntryMetadata()
{
}
[Required]
public string Description { get; set; }
}
}
The convention is to define a nested class inside a partial class extension for your entity type on the server side. You link the metadata class to the entity type through the MetadataType attribute. The metadata class can redefine any of the properties exposed by the entity type and add attributes to them. You do not have to redefine all of them, only those you want to add an attribute to. So in this case I just add the [Required] attribute to the Description property. These metadata classes can be generated for you when you run the initial Add Domain Service Class template. The “Generate associated classes for metadata” checkbox at the bottom of the Add Domain Service dialog will create a metadata class for each entity managed by your domain service and will redefine all of the properties in those entities within the metadata class so that you can easily just drop in an add attributes as appropriate.
The generated client entities will then contain these attributes:
public sealed partial class TimeEntry : Entity
{
...
[Required()]
public string Description
{ }
Other attributes that are important in the metadata class have to do with entity relations. When you define retrieval methods in your domain service for an enitity type such as a Task, by default WCF RIA Services will not send related entities to the client. So even though the Task type has a property for a collection of child TimeEntries, even if my domain service loads all the child entities (with the Include() method in Enitity Framework), those child entities will not be sent to the client. To get them sent, I first have to ensure that they get loaded from the data layer:
public IQueryable<Task> GetTasks()
{
return this.ObjectContext.Tasks.Include("TimeEntries").OrderBy(t=> t.StartDate);
}
And second I need to add an [Include] attribute on the corresponding property in the entity metadata class:
[MetadataTypeAttribute(typeof(Task.TaskMetadata))]
public partial class Task : INotifyPropertyChanged
{
internal sealed class TaskMetadata
{
// Metadata classes are not meant to be instantiated.
private TaskMetadata()
{
}
[Include]
public EntityCollection<TimeEntry> TimeEntries { get; set; }
}
}
Now the time entries get sent to the client side and I can display them (after adding some of course):
If you want to manage child entities as wholly owned child objects of the parent, you can also use the [Composition] attribute. Putting that on a property makes it so a change to a child object causes the parent object to be marked as modified. Then when you SubmitChanges on the client side both the parent and the changed child entities are sent along. You do have to do a little more work in your parent entity Update method because you have to handle the inserts, updates, and deletes of your children as well as those for the parent entity type.
Shared Code
Another facility that comes in handy is the ability to define a chunk of code on the server side that can be used there, but that will also be available (through code generation) on the client side as well. All you need to do to leverage this is to put the code in a file with a *.shared.cs (or *.shared.vb) naming convention in the server project. The sample code for this article includes a file named Task.shared.cs containing a partial class extension for the Task entity type that adds a computed property:
public partial class Task
{
public TimeSpan TotalTime
{
get
{
TimeSpan total = new TimeSpan();
if (TimeEntries != null)
{
TimeEntries.ToList().ForEach(delegate(TimeEntry te)
{
total += (te.EndTime - te.StartTime);
});
}
return total;
}
}
}
This property could be used to support the business logic on the server side, and is used for display on the client side as you can see from the screenshot shown earlier.
Another common use of shared code is for custom validation rules that get tied in to a property through a [CustomValidation] attribute. I’ll show an example of that next article when I go into a little more depth on validation.
Summary
Metadata allows you to add functionality to your server and client side entities by decorating properties in a metadata class with attributes that you want to affect the behavior of your entities. A metadata class is a class tied in to the entity type through the [MetadataType] attribute. You can tie in validation attributes, as well as attributes such as the [Include] and [Composition] attributes that influence the way WCF RIA Services handles child entities. Shared code allows you to share code files between the client and server side without needing to copy and maintain two versions. All you need to do for that is name your code files with a .shared.cs (or .vb) naming convention.
Next article I’ll dig in to validation a bit more.
You can download the sample code for this article here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on twitter @briannoyes.