One thing that often comes up when developing Silverlight applications is the desire to share entity classes between Silverlight and server-side code in WCF.
.NET RIA Services handles the sharing for us automatically. But you’ll find yourself needing the functionality in cases where you’re simply not using .NET RIA Services.
Sharing in WCF happens by default when you mark up your entities using [DataContract] and [DataMember] attributes. However, I find polluting my classes with those to be really ugly, as well as limiting. I also dislike the handling of namespaces on the client. Finally, that approach doesn’t serialize methods, just property values, so you can’t have computed properties or helper methods.
Instead, the approach we take is to cross-compile the classes in Visual Studio, and simply let the runtime use the client-side and server-side representations as appropriate. It’s very easy to set up, and once you start doing it this way, you’ll probably never want to go back to the generated approach.
With a little additional tooling help, this is what Prism does for code sharing. I often hear of folks who want to use Prism simply because they think it magically provides WPF/Silverlight code sharing, but it’s really just a part of the VS tooling, with a little help from a synchronization add-in provide with Prism.
Note that this approach will work for any code you need to share between the client and server or full CLR and Silverlight CLR.
The first step is to create a solution that has both the service and the Silverlight client. You can do this without the service in the solution, but you’ll have to bump out to the service project to sync compiles with the client. It’s just easier to have them both in the same solution.
Create the Entities Projects
The next step is to create the class library projects that will hold the entities. Since I spend most of my time working in the Silverlight client project, I typically create the Silverlight version first, and let it be the project of record, but doing it the opposite way works just fine as well.
Name the Silverlight class library the name you’d normally give it, but append “Silverlight” to the end. Similarly, append something like “Web” if you’re creating the server-side project.
Be sure to delete the default Class1.cs files from both projects.
The next step is to unify the default namespace between the two projects. This is one reason why you deleted the default class files – it’s easier to sync things that way. Open the project properties window for the entities projects.
Leave the assembly names alone (to avoid confusion), but set the default namespace to be the same in both cases. If you named your projects with the same root, all you need to do is remove the .Silverlight or .Web from the end.
Add Entity Classes
The next step is to add some entities to one of the projects. I’m going to leave the Silverlight project as the primary holder of the entities, and will therefore add the classes there. Again, either way will work.
using System;
using System.Net;
namespace PeteBrown.CodeSharing.Entities
{
public class Employee
{
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime DateOfBirth { get; set; }
public string EmailAddress { get; set; }
public Uri WebSite { get; set; }
}
}
Note that the class doesn’t have any of the usual markup cruft you need to deal with when defining entities that you’ll use in WCF. These are POCOs (Plain Old CLR Objects) and therefore could have been generated from anything from hand-coding to an ORM.
You’ll need to remove the System.Windows.* namespaces that don’t apply to a cross-CLR entity. That’s the one downside with starting with the Silverlight project. I’ve asked the SL team to either keep those out of the Class template, or provide an alternate class file template for Silverlight v4.
Once you have your class added to one project, the next step is to link it to the other project.
Adding Linked Classes
In the web project, select the option to add an existing item. Find the class you just added to the Silverlight project, and choose the Add option to “Add as Link”
This will link the Employee.cs file to the .web project. Changes made in the Silverlight project will automatically carry over to the linked project, as long as both are in the same solution.
Note that this does cause some issues with Source Safe. VSS, being the outdated and kludgy product it is (why do we still use it? why do my customers still use it?) requires some playing around to get it to properly link the two files rather than treat them as two distinct files.
Using the Shared Entity Class on the WCF Project
First, reference the Silverlight entity project from the Silverlight client project and the web entity project from the web server project. You need to do that before you create any services or, more importantly, add any service references. Make sure you get the references correct – C# will let you reference a Silverlight project from a full CLR project, but it gets messy once you start doing anything more than a vanilla class definition.
First, create the WCF service in your web project
Next, create a WCF method that returns the class, it doesn’t really matter what you do, as long as you get the entity class into the signature:
[ServiceContract(Namespace = "uri:com.irritatedvowel.demos.silverlight.codesharing")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class EmployeeService
{
[OperationContract]
public Employee GetEmployee()
{
return new Employee()
{
FirstName = "Pete",
LastName = "Brown",
DateOfBirth = new DateTime(1927, 01, 01),
WebSite = new Uri("http://www.irritatedvowel.com", UriKind.Absolute),
EmailAddress = "foo@bar.com"
};
}
}
In this case, I simply return a new hard-coded instance of the Employee class.
Using the Employee Class on the Client
Add a service reference as your normally would, but before clicking “OK”, you’ll want to double-check the code sharing settings. By default, the “add reference” dialog and process is set up to share any types that the client and server have in common. This is typically used for things like basic types, collections etc. but also works with your own classes.
In the service reference dialog, do all the reference steps as you normally would, but click the “advanced” button on the bottom left.
That will pop up this dialog. Note that I hate the approach of hiding useful stuff under an “Advanced…” button or tab – it discourages exploration and implies that the stuff behind that button or tab is somehow dangerous.
Once you have the window open, you have two choices here related to the topic of this post. You can either reuse all types in referenced assemblies (the default), of you can reuse only specific types. Leaving it on the default option is usually the prudent approach, as it requires less upkeep as you add on to the projects. We’ll follow that approach here.
Finish adding the service as normal.
You can now use the shared type on both the client and server, in the correct namespace. The entity type you’re using on the client isn’t buried under the service reference namespace as it would be if you used the usual approach of generating the client types based on the WSDL. This approach also enables sharing the same types between multiple service references without having to translate between them or otherwise have multiple confusing types defined on the client.
using PeteBrown.CodeSharing.Entities;
using PeteBrown.CodeSharing.Services;
namespace PeteBrown.CodeSharing
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var client = new EmployeeServiceClient();
client.GetEmployeeCompleted += (s, ea) =>
{
// do something, bind/whatever
};
client.GetEmployeeAsync();
}
}
}
Note that calling the service from code-behind is just for demo purposes here. My service calls are usually centralized in another class or performed directly from the ViewModel, depending on the complexity of the solution. Don’t fill up your code-behind with stuff like this. You can learn more about the ViewModel pattern by searching for MVVM or ViewModel
There’s one last step, and that has to do with adding custom code that may be platform-specific.
Alternate Implementations Based on Platform
My entities are usually pretty dumb: they’re just DTOs without anything that might be platform-specific. For that reason, I’m going to use a completely contrived example here, because I’d look at any realistic example and throw it away as “not how I’d do it” :)
Keep in mind, though, that this whole technique works for more than just entities, which is why I’m showing the conditional compilation approach here.
Silverlight projects automatically define the SILVERLIGHT conditional compilation symbol.
namespace PeteBrown.CodeSharing.Entities
{
public class Employee
{
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime DateOfBirth { get; set; }
public string EmailAddress { get; set; }
public Uri WebSite { get; set; }
#if SILVERLIGHT
public string ClientOnlyMethod()
{
return "I exist only on the client.";
}
#else
public string ServerOnlyMethod()
{
return "I exist only on the server";
}
#endif
}
}
Note that you don’t want to use conditional compilation for anything you want serialized. There may be instances where you can actually serialize if the types implement common interfaces, or if the serializer would otherwise know how to translate between them. You’ll need to test that on a case-by-case basis.
In the example above, I conditionally compile two methods. Since methods are just ignored by the serializer, there are no issues to contend with.
That’s it for sharing your code between the two platforms. The source for this project is available here.