Tuesday, July 29, 2008

Silverlight 2: Sharing Code Between Client and Server

One of the first things that came to mind when I heard that silverlight was going to be adding managed code support, was the potential to reuse code between the server and the client. I had been successfully following this methodology for my compact framework development, and now it was possible for silverlight web applications as well! [Insert happy dance here]

One of the main areas I like to reuse code has to do with the object model, and the validation thereof. Just as a reference, when I say "object model", I mean dumb data carriers for my entities. ie: Classes that contain only properties, not behavioural methods. Not that you can't add methods to your OM classes, I just prefer to keep them simple, and let other classes take care of behaviour / validation etc.

Okay - enough blog waffle, let's get straight into sharing your code between server and client!

Here's a picture of a typical basic silverlight architecture:

SilverlightArchitecture

The idea is to share code between the .NET application layer and the silverlight client layer, and even allowing the classes for the shared code to be serialized over the wire.
NOTE: For the last bit (code sharing that involve "over-the-wire" serialization), you'll need to wait for the release of Silverlight 2 RTW. I can confirm that RTW release does allow the re-use of types in referenced assemblies. :)

Step 1: Define the code

Typically, there are 2 strategies for defining the canonical source of the code: 1) Place it in a shared location, and link it to projects that need it, or 2) Define it in the project with least namespace dependencies, and link it up to other projects.
If you're sharing code across the compact framework, silverlight, and the full .NET framework, I recommend 1), but if you only plan on using the code for one of the subset frameworks, I recommend 2), which is the one I'll describe in detail here.

The reason for defining the class in the silverlight class library is purely due to maintenance reasons. When modifying the source code, always modify it at it's source. That way you know you're not adding any code that depends on framework namespaces that do not exist.
eg: If I open the class where it is defined in the silverlight class library, and try and add a DataSet property, the designer will immediately tell me that this is not possible. If the class was defined in the server assembly, I would only find the dependency error at compile time when the client assembly compiles.

One final note on the class definition: It may be helpful to mark your classes with the "partial" keyword, as you may wish to extend the defined class into server and client specific implementations. That, or you could define new client and server types which inherit from the shared base implementation - whichever makes more sense in the underlying design.

Step 2: Link the Code to the Server Assembly

The idea is that there is only one version of the code on disk, otherwise it's just code duplication, and not code sharing / reuse.
To reuse the code defined in Step 1 in your server class library, do the following:
From the server assembly project menu, select "Add Existing Item", and navigate to the class file defined in Step 1. Instead of clicking the Add button (which would just duplicate the code) select the arrow next to the add button, and select "Add As Link"

AddAsLink   ShortCutIcon

You will notice that there is a little shortcut mark on the class icon in the solution explorer. This indicates that the file is declared elsewhere (your silverlight assembly in this case).
ie: Whenever you make changes to the class definition, it will automatically be reflected in all the places where the source code file is linked.
NOTE: Be careful not to open the file from the shortcut, as Visual Studio will open it assuming you are in the current project, and you will have access to all the .NET namespaces. ALWAYS edit the code by opening it from the canonical source.

Step 3: Share the Code Over WCF Services

If your code is going to be "traversing the wire", then mark it with the usual [DataContract] and [DataMember] attributes. (NOTE: Since the release of SP1, you don't need to explicitly mark your data members - only do so if you want to explicitly exclude certain properties from serialization. ) Make sure that System.Runtime.Serialization is referenced in all projects making use of the shared code file.
From the Silverlight client project, right click and select "Add Service Reference". Add a reference to the WCF service you would like to consume, making sure that the checkbox "reuse types in referenced assemblies" is checked in the advanced options (as displayed below).

 AddServiceReference AddServiceReferenceAdvanced

When reusing types (which will only be available from silverlight 2 RTW), the svcutil, which is used to generate the client proxy class definitions for the DataContracts, will add references to the client versions of the code (the class created in Step 1)  instead of generating them in the Reference.cs file of the service.

...And that's it - now you're sharing C# code between server and client.
I'm currently finding this very handy for executing common business rules on object classes on the client in order to prevent unnecessary round tripping to the server.
It's also become very handy for generic framework implementations using the common base classes. Also, If you have the same DataContract defined in two separate services, instead of them being defined twice (once for each service reference), both services reference the existing client-side class, allowing the generic code implementation mentioned above.

I'm working on posting some (simple) sample source code - watch this space.

Another handy post on the subject

        

9 comments:

karlshifflett said...

Very nice job on this post. Easy to read the understand.

Thanks for sharing your workflow.

Cheers,

Karl

Anil Kumar Pandey said...

Thanks for sharing this useful info.....

Comanche said...

This doesn't work for me (SL3, VS2008/SP1). Any special requirements for namespace namings?

Jax said...

Comanche - no special requirements. We're using this methodology quite successfully in our enterprise application. VS does, however, have a bug whereby sometimes it generates the WCF proxy classes without re-using the referenced assembly linked classes. The workaround is simply to restart VS, and update the service reference.

Comanche said...

I restarted VS and it still doesn't work. Here is how my Web Project looks like: mediafire.com/?wzyzzlwijjz. And this is my Client SL Project: mediafire.com/?nrmmyjdavyq. I defined my entity class in the Web Project:

using System.Runtime.Serialization;
namespace RMS.Entities
{
[DataContract]
public class TestEntity
{
private string _name;
[DataMember]
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}

Then I linked it to my Client SL Project. The proxy generation tool produces the following output:

namespace RMS.ServiceReference1
{
using System.Runtime.Serialization;
public partial class TestEntity : object, System.ComponentModel.INotifyPropertyChanged
{
[...]
}
[...]
}

It's a duplicate class! Am I missing something?
Thank you.

Jax said...

2 things:
1) It's always better to define the entity in your silverlight project, and then link into the server web project. (See the post for reasons)
2) The entity must be defined in a referenced silverlight assembly, not the silverlight client itself.
If you have further problems, post your solution code somewhere, and I'll see what I can do.

Comanche said...

Thank you very much - now it works! This is what I've done:
- created SL Class Library "RMS.Entities.SL" and placed my entity classes there;
- referenced this library from my SL Client Project "RMS";
- added links to the above mentioned entity classes in my SL Web Project "RMS.Web";
- removed/added service references in the SL Client Project.

But could you please explain several things to me?

1. If I open the generated Reference.cs class, locate there smth. like "ObservableCollection<RMS.Entities.TestEntity>", right-click on "TestEntity" and select "Go to definition", VS takes me to the link (located in the "RMS.Web" project) but not to the real file (located in "RMS.Entities.SL")! It doesn't seem to be a real trouble, I'm just curious because this is really strange!

2. For each entity involved in my service references, VS creates corresponding project element in the SL Client Project, under Properties/DataSources (creates when service reference is being added). E.g. "RMS.Entities.TestEntity.datasource". What does this element mean?

3. I removed DataContract/DataMember attributes from my entity classes, but my application still works. WHY?!

4. Having removed these attributes, would I run into any compatibility issues with my WCF services?

5. Does this approach work under all Framework versions supporting WCF? (AFAIK, these are 3.5, 3.5SP1, 4.0) And, generally speaking, is this approach a "hack" or a recommended and safe one?

Thank you for your time.

Jax said...

1) I think it depends on where you open the class file from the first time. If it's already been opened from the server project, then from then on when you navigate to it (evem from the client), it will open the server version. You need to close the file, and then open it from the client to see it from the "client" perspective. (Compile errors etc.)
2) I would guess it's just a way for the project to know where you referencing / linking the file from. If it's more than that, I'm not sure, you'd have to google it :)
3) The DataContractSerializer is used for serializing the objects. In days gone by, the tags used to be inclusive (forced you to define them), but Microsoft later changed tact (for the better), and allowed the DataContractSerializer to serialize objects without tags. I mostly use the tags, because this is a handy strategy for when there are properties which you don't want to traverse the wire.
4) Nope - should work just fine.
5) The DataContractSerializer behaviour talked about in 3) was only revised in 3.5 SP1 (if memory serves), so you'd need at least that on the web server.
4.0) I don't think of it as a hack. As mentioned, we have this implemented now for quite some time on a silverlight line of business application, and has proved to be VERY useful for sharing entities and business logic.

Last word on sharing entities which we implemented (I hope to post something on it some time)
Collections: On the server out collections are exposed as Collection, but this collection type doesn't support binding on the silverlight client. ObservableCollection, which does support binding, is not
available on the server (unless you import some WPF presentation assemblies)
What we did (since we feel that binding is a client-only concern, was create a wrapper collection called ObservableObjectCollection (similar to the one defined in the silverlight toolkit assemblies, only it support generics).
This collection class takes as it's constructor the existing collection, and then overrides all the Add and Remove behaviours to ammend the original list, thus providing binding behaviour while still maintaining the original object.
ie: The partial entity class on the client has an added collection property of type ObservableObjectCollection which lazy-loads the original collection in the getter.

Jax said...

A further word on 4.0: Miscrosoft have implemented a different strategy in their RIA services framework, whereby the file is marked as shared, and then copied at compile time to the client. You can add this behaviour to your own code via project compile commands, but I prefer the strategy mentioned in this post, as any change in the file is immediately reflected in every project reference. This becomes handy when you're sharing code between multiple projects which aren't necessarily in the same solution : ie. doesn't require a comile action from a specific source.