Please note that I am still tweaking this article, I just wanted to publish it quickly because I have had a lot of questions from other developers concerning this very topic. I will remove this note when the article is complete and I will provide a link to a project that implements this functionality from DB –> L2S –> WCF –> Model –> WPF and Silverlight.
I hope that subject was descriptive enough. Here is the problem: You are developing applications using an MVVM and want to use your Model code for WPF applications and Silverlight. You will quickly find that you need to build your Model and ViewModel in a Silverlight targeted assembly. Many people actually add the Model and ViewModel class files in the Silverlight project file. That is a) nasty and b) you can not use your model as your data contract for your WCF service.
The solution I found was very simple. Create your WCF service and use your Model classes as data contracts. Now your Model classes will all be decorated with DataContract and DataMember attributes. These are not compatible with Silverlight so you need a solution for this part of the problem. In short, you need to conditionally compile out the non-Silverlight compatible attributes (yes it is a bit ugly in code, but it saves you a lot of time and it works!).
Example
So lets walk through a simple application that lists Employee records from a database. The architecture looks like this:
There are a lot of lines there but we will focus on the Silverlight side of things. The goal is to create one Model class that is shared between the WCF service and the Silverlight application. This Model class should also be able to be used by WPF applications (and ASP.NET) as well. I will call the procedure for making the Model work across both architectures Portable Model or pModel.
Linq / SQL Metal / ADO.NET Entity Framework Objects
Use one of these technologies to get your data from the database into an object. These objects should NOT (and can not) be passed around through to Silverlight application. The solution is to use a Portable Model. In this example I used SQL Metal to generate out a Data Context for my database which has an Employee table.
WCF
At my WCF tier I want to perform the following:
- Query the database and get all the Employees.
- Convert the DataContext / SQL Metal / Linq objects to a Model object.
- Send the Model object down the wire to the client.
Traditionally this can be done without issue for WPF applications. This is because both types of applications are running the full blown CLR. They can share the same DLL and the WCF proxy will nicely convert the WCF message from the wire into a well typed Model object.
* A note about sending EF or Data Context objects down the wire. While this will work, technically, if your client uses the full CLR (WPF / ASP.NET), it is highly discouraged. Take a SQL Metal Data Context object for example. When you send that type of object down to the client it actually has the ability to talk directly to the database bypassing your WCF layer (and any business logic you should have in your Model or Business Entity code). Your tiering breaks down at this point your you break many good development practices.
When you throw Silverlight into the mix, the problem becomes a matter of CLR mismatch. Your traditional Model class that you are sharing between the client and WCF service is built against the full CLR so you can not add the reference to your Silverlight application. You could use the proxy generated code in the Silverlight application but this really breaks some of MVVM.
Take your ‘Employee’ Model code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace VMR.Model
{
[DataContract]
public class Employee : System.ComponentModel.INotifyPropertyChanged
{
public Employee() { }
private int _EmployeeID;
[DataMember]
public int EmployeeID
{
get { return _EmployeeID; }
set
{
_EmployeeID = value;
SafeNotify("EmployeeID");
}
}
private string _Username;
[DataMember]
public string Username
{
get { return _Username; }
set
{
_Username = value;
SafeNotify("Username");
}
}
private string _FirstName;
[DataMember]
public string FirstName
{
get { return _FirstName; }
set
{
_FirstName = value;
SafeNotify("FirstName");
}
}
private string _LastName;
[DataMember]
public string LastName
{
get { return _LastName; }
set
{
_LastName = value;
SafeNotify("LastName");
}
}
private string _Password;
[DataMember]
public string Password
{
get { return _Password; }
set
{
_Password = value;
SafeNotify("Password");
}
}
private bool _CanUserLogin;
[DataMember]
public bool CanUserLogin
{
get { return _CanUserLogin; }
set
{
_CanUserLogin = value;
SafeNotify("CanUserLogin");
}
}
private string _EmailAddress;
[DataMember]
public string EmailAddress
{
get { return _EmailAddress; }
set
{
_EmailAddress = value;
SafeNotify("EmailAddress");
}
}
private string _PhoneNumber;
[DataMember]
public string PhoneNumber
{
get { return _PhoneNumber; }
set
{
_PhoneNumber = value;
SafeNotify("PhoneNumber");
}
}
#region INotifyPropertyChanged Members
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
#endregion
private void SafeNotify(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return string.Format("{0} {1}", FirstName, LastName);
}
}
}
This Model will work for WCF, WPF and ASP.NET but will not work for Silverlight because the agCLR (Silverlight CLR) does not support the DataContract and DataMember attributes. You could fake it and create a mock attribute in Silverlight but that seems like a poor architecture.
Enter – Shared Class Files and Conditional Compilation
Visual Studio has had a cool feature for some time that lets you add a ‘Link’ to another source file in a different project. Both projects will compile the code file and both projects can edit the code. Physically, only one instance exists.
The Model / CLR dilemma can be solved partially by creating a new Silverlight Class Library project in your solution and adding a Link to the Model class.
Now your linked class will not yet compile until you tell the compiler to ignore the DataContract and DataMember attributes when building for Silverlight.
You do this by wrapping your attributes in a conditional compilation statement. This definitely make the model code look a bit ugly, however the beauty is that now when you build your solution you will end up with a CLR and agCLR compiled version of the assembly. There are other ways to do this with targeting and platform based builds, however, by doing it this way you can add project references to the Silverlight version of the Model and your project will be much better organized.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace VMR.Model
{
#if !SILVERLIGHT
[DataContract]
#endif
public class Employee : System.ComponentModel.INotifyPropertyChanged
{
public Employee() { }
private int _EmployeeID;
#if !SILVERLIGHT
[DataMember]
#endif
public int EmployeeID
{
get { return _EmployeeID; }
set
{
_EmployeeID = value;
SafeNotify("EmployeeID");
}
}
private string _Username;
#if !SILVERLIGHT
[DataMember]
#endif
public string Username
{
get { return _Username; }
set
{
_Username = value;
SafeNotify("Username");
}
}
Below you will see the CLR Model (VMR.Model project) and the agCLR Model (VMR.Silverlight.Model project).
How do you add one of the Model class code files as a link to the Silverlight Class Library project?
Like normal, add an Existing Item to the Silverlight.Model assembly. Locate the normal Model class on the disk.
Click the down arrow next to the Add button and choose Add As Link.
Now you have one source for both your CLR (WPF) and agCLR (Silverlight) Model code. By doing this the namespaces are the same and message deserialization is transparent on both platforms. You can now use your ViewModel (your view model will need to be different for WPF and Silverlight because it will need to reference the platform specific version of the model) in your client code to retrieve the Model data and bind it to the XAML in your WPF or Silverlight application.