Monday, December 13, 2010

A Generic DTO Model and Other Silverlight-Related Architecture Considerations

One of the challenges in writing complex, layered applications (e.g. Silverlight applications) is the need to structure layers properly in a clean, consistent way. In particular, there are two different domains: a server domain with business entities (classes) which are retrieved/stored from/to the database and a client domain with some other set of classes which somehow map to server-side business entities*.

* The answer to an obvious “Why you cannot use the same set of classes in the server and the client domain?” would be “And have you ever tried to reference a business class library from a Silverlight application?”. It is of course not possible, as server side business classes usually somehow reference other server side technologies for persisting classes (Linq2sql, NHibernate) which are not available in the Silverlight layer.

Usually this mismatch between the server side and the client side is tackled with some help from the Data-Transfer-Object pattern. DTOs are separate classes with no business methods and no other logic but containing only public properties so that they can be easily serialized at the server-side and deserialized at the client-side. They serve as building bricks of the language your server talks to your client (Silverlight) application.

This article focuses on following issues arising around the DTO pattern:

1) how the DTO layer should be built so that classes could easily be shared at the WCF-contract level

2) how any addititional properties of business objects can me mapped in DTO classes in a uniform way (and still be able to use data binding)

3) how to build a generic DTO models so that most calls to the server services can return an instance of such generic model (and still be able to use data binding)

Creating a Silverlight Playground

Let’s start by creating a solution with three projects.

Server side:

1) a web application to host the silverlight application.

2) a class library containing all the business entities (using any persistence technology, Linq2SQL is ok). For this study, let’s assume that there are two entities in the model, Parent and Child, with following schemas:

Client side:

1) an actual silverlight application

Create a WCF service in your server-side web application, create any method in the service and create a service reference at the silverlight side (just to test the playground).

What I suggest here is to create a simple factory class for creating instances of the proxy class so that you do not have to rely on the settings provided in the client-side *.ClientConfig file. The factory class would create the instances with dynamically built endpoint address, addressing the service relatively to the source of the Silverlight application:

namespace SilverlightDTOStudy
{
    public class ServiceFactory
    {
        public static SilverlightServiceReference.SilverlightServiceClient CreateClient()
        {
            Binding binding = new BasicHttpBinding();
            // dynamic endpoint, relative to the location of the Silverlight application
            EndpointAddress endpoint = 
                new EndpointAddress( new Uri( Application.Current.Host.Source, 
                                              "../SilverlightService.svc" ) );
 
            SilverlightServiceReference.SilverlightServiceClient client = 
                new SilverlightServiceClient( binding, endpoint );
 
            return client;
        }
    }
}
 
class FactoryClient
{
   void Foo()
   {
      SilverlightServiceClient client = ServiceFactory.CreateClient();
 
      client.....

Just when the Silverlight application is correctly launched from within the ASP.NET host and it correctly invokes the WCF service, you can move to the next step, creating the DTO Layer.

Creating the DTO Layer

Let’s start by asking another, interesting question: “Why do we need DTO classes at all? Why cannot we rely on proxy classes inferred at the client side from their server-side counterparts?” And the answer to this would be: there are at least two serious issues in using business entities in WCF methods’ signatures.

First issue – suppose we have the Child class and our WCF method should return a list of Child object so that we bind it to a list at the client side. Suppose however, that along with each single Child you’d like to show it’s Parent name so that the list has two columns, a Child’s name and a Parent’s name. There’s no easy way to accomplish that using class models inferred from server-side classes, just because the Child class does not contain Parent’s name as one of its columns (it only contains a reference to the Parent which then contains Name as one of it’s fields). On the other hand – it’s fairly easy to have any additional columns in a DTO class and your ChildDTO class could for example have the ParentName column.

And the second issue – business objects at the server side cannot often be serialized but, in the same time, serialization is required for the web services / WCF to operate. For example, if you try to expose Linq2SQL classes from a WCF method, you’ll get an exception saying that Linq2SQL entities cannot be serialized. This is because the serializer follows all public properties and when it serializes your Child class, it appends its Parent to the serialized graph but the Parent object contains a reference to its Children which then contains the original object among other Children. The serialization graph has an obvious cycle and the serializer raises an exception. There are ways to overcome this for Linq2SQL but nevertheless – DTO gives you much finer control on the serialization.

DTO is really easy in practice. What you have to do is to create a set of classes which correspond to business entites but they can only contain public properties corresponding to class properties. For example:

public class ChildDTO 
{
    public int    ID { get; set; }
    public int?   ID_PARENT { get; set; }
    public string Name { get; set; }
}

It couldn’t be simpler. What you also need is a way to convert between business entities and DTO entities. There are tools to help you, like the Automapper. Alternatively all the conversion can be done manually.

There’s a cool trick involving DTO classes. Normally you’d have the DTO layer in the server-side code, you’d expose DTO classes from WCF methods and their client-side countermodels would be automatically built when you add a reference to the web service. This however could lead to multiple client-side models created for a single DTO server-side class if you decide to expose the class from multiple web services (as different client-side models are built for different web services).

Instead, create two additional projects in your solution:

1) a server-side class library.

2) a sliverlight class library.

Create your DTO classes in the server-side library. Then, in the silverlight class library, right click “Add existing …”, point to *.cs files in the server-side library containing DTO classes and then click the small triangle next to “Add” to show the drop down menu and select “Add as link”.

This way you’ll have the same source code file in two different libraries. When you change it, it automatically changes at both sides.

Now, if a DTO class is exposed from the WCF web service, the proxy generator will follow the default “Reuse types in all referenced assemblies” setting (right click a service reference and pick “Configure Service Reference”) and because you have the same types at the client side (it does not matter that in fact they come from different assemblies, what the proxy generator cares is the namespace and class names and they are equal since you are compiling the same files for both assemblies!) they will be automatically used in the client-side proxy signatures.

But the trick does not end here. You see, the C# compiler recognizes the SILVERLIGHT switch so that you can use #IF SILVERLIGHT #ENDIF pragmas. This is useful to add any specific Siverlight or server-side code to DTO class files and you are guaranteed the code will correctly compile for both assemblies. For example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SilverlightDTO.DTO
{
    public class ChildDTO 
    {
        public int    ID { get; set; }
        public int?   ID_PARENT { get; set; }
        public string Name { get; set; }
 
#if SILVERLIGHT
 
       /* This would compile only at the silverlight side */
 
#else
 
        /* This will compile only at the server-side */
 
        /// Convert from business entity to DTO
        public static ChildDTO ToDto( SilverlightDTOStudy.BusinessLayer.Child Child )
        {
            ChildDTO childDto = new ChildDTO();
 
            childDto.ID        = Child.ID;
            childDto.ID_PARENT = Child.ID_PARENT;
            childDto.Name      = Child.Name;
 
            return childDto;
        }
 
        /// Convert from DTO to business entity
        public static SilverlightDTOStudy.BusinessLayer.Child FromDto( ChildDTO ChildDto )
        {
            SilverlightDTOStudy.BusinessLayer.Child child = new SilverlightDTOStudy.BusinessLayer.Child();
 
            child.ID        = ChildDto.ID;
            child.ID_PARENT = ChildDto.ID_PARENT;
            child.Name      = ChildDto.Name;
 
            return child;
        }
 
#endif
 
    }
}

The WCF could then expose a DTO:

public class SilverlightService
{
   public ChildDTO TestDTO()
   {
      // start from a business entity
      Child c = ... retrieve it somehow, use Linq2SQL or anything else ...
 
      ChildDTO cDTO = ChildDTO.ToDTO( c );
 
      return cDTO;
   }
}

Just when a WCF method correctly exposes a DTO class and you can inspect a result at the Silverlight side, you are ready to continue.

Mapping additional properties

One of the issues you face constantly is the lack of additional properties at the client side. Suppose your Silverlight list should contain Child name and its Parent name. Normally, at the server-side, you’d just rely on lazy loading and put Parent.Name in your binding expression. But on the client side, your DTO class does not contain a reference to its parent. It only contains it’s ID.

The DTO pattern let’s you tackle this in an easy way – you just add an additional property to your ChildTO class, ParentName of type string, add a line of code to your ToDTO method and you are done. You put ParentName in a binding expression. But this is not the right way to go*.

* Why this is so? Suppose that you not only want to see a Parent’s name in a Silverlight grid but also Parent’s Parent’s name, Parent’s SuperParent’s name and 5 other interesting properties which are somehow related to your Child. So you happily add additional columns to your DTO classes until you realize that what you have is a huge mess of additional properties everywhere.

Instead, you can just have an extra “generic” properties exposed as the mapping from string (field names) to object (field values). It’s simply a matter of a DTO’s base class:

public class BaseDTO
{
    public Dictionary<string, object> ExtendedProperties { get; set; }
}

Whenever you need an additional column in your DTO object, you just put in in the container:

Child c = ....
 
ChildDTO cDTO = ChildDTO.ToDTO( c );
 
// add an extra "field" at the server side
cDTO.ExtendedProperties = new ...;
cDTO.Add( "ParentName", c.Parent.Name );
 

and refer to it easily at the client-side:

<TextBlock Text="{Binding ExtendedProperties[ParentName]}" />
(yes, Silverlight supports binding to dictionaries!)
Generic DTO Models

What we have discussed so far could dispel most DTO-related doubts. The only remaining interesting issue is the way to expose collections/sets of DTO classes from within WCF methods. Suppose your WCF call should expose two lists of DTO objects (because you need to feed two combos on your child window).

A naive approach would be to build an extra model for such call:

public class FooModel
{
   public List<ChildDTO> Children;
   public List<ParentDTO> Parents;
}
 
public class SilverlightService
{
   public FooModel Foo()
   {
      // expose FooModel - initialize both collections and return a composite object 
   }
}

This is not the right way to go*.

* Why this is so? Just imagine how many of such different composite models you’ll end up with having few dozens of WCF methods.

Instead, you could just provide a generic DTO model which should be suitable for any WCF call. Such model should just contain all possible lists of DTO classes and for a specific call – only one (or two, or three) selected sets would actually be filled up with data. But wait, here’s another trick. Instead of lists, expose dictionaries. Also, add a reference pointing to the DTOModel to your BaseDTO class and a method to initialize this reference:

public class BaseDTO
{
    public DtoModel Model { get; set; }
    public Dictionary<string, object> ExtendedProperties { get; set; }
}
 
public class DtoModel
{
    public DtoModel() { }
 
    public Dictionary<int, ChildDTO> Children;
    public Dictionary<int, ParentDTO> Parents;
 
    public void MapEntities()
    {
        if ( Children != null )
            foreach ( var child in Children.Values )
                child.Model = this;
        if ( Parents != null )
            foreach ( var parent in Parents.Values )
                parent.Model = this;
    }
}

When you call a service, fill all the required sets at the server side:

public class SilverlightService 
  {
      [OperationContract]
      public DtoModel ExampleCall()
      {
          DtoModel modelDTO = new DtoModel();
 
          modelDTO.Children = new Dictionary<int, ChildDTO>();
          modelDTO.Parents = new Dictionary<int, ParentDTO>();
 
          /* insert children */
          foreach ( Child child in DataModel.Instance.Child.Take( 5 ) )
          {
              ChildDTO childDto = ChildDTO.ToDto( child );
 
              childDto.ExtendedProperties = new Dictionary<string, object>();
              childDto.ExtendedProperties.Add( "ParentName", child.Parent.Name );
 
              modelDTO.Children.Add( childDto.ID, childDto );
 
              /* insert parent */
              ParentDTO parentDto = ParentDTO.ToDto( child.Parent );
              if ( !modelDTO.Parents.ContainsKey( parentDto.ID ) )
                  modelDTO.Parents.Add( parentDto.ID, parentDto );
          }
 
          return modelDTO;
      }
  }

and map DTO entities to the DTO model at the Silverlight side:

private void Button1_Click( object sender, RoutedEventArgs e )
{
    var client = ServiceFactory.CreateClient();
 
    client.ExampleCallCompleted += new EventHandler<ExampleCallCompletedEventArgs>( client_ExampleCallCompleted );
    client.ExampleCallAsync();
}
 
void client_ExampleCallCompleted( object sender, ExampleCallCompletedEventArgs e )
{
    e.Result.MapEntities();
 
    DataGrid1.ItemsSource = e.Result.Children.Values.OrderBy( c => c.ID );
}

Do you get the idea now? Having all DTOs pointing back to the DTOModel, you can cross-reference entities from different tables:

public class ChildDTO : BaseDTO
{
    public int    ID { get; set; }
    public int?   ID_PARENT { get; set; }
    public string Name { get; set; }
 
    /// Cross reference Parent using model's Parent dictionary
    public ParentDTO Parent
    {
        get
        {
            if ( this.Model != null &&
                 this.Model.Parents != null
                )
                return this.Model.Parents[this.ID_PARENT.Value];
 
            return null;
        }
    }

and then you can for example bind either using ParentName in ExtendedProperties or to Parent.Name:

<data:DataGrid x:Name="DataGrid1" AutoGenerateColumns="False"
  GridLinesVisibility="None" HeadersVisibility="All"
  RowBackground="White" AlternatingRowBackground="AliceBlue"
  Grid.Row="1">            
    <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="Child name" Binding="{Binding Name}" />
        <data:DataGridTextColumn Header="Parent name (method 1: extended props)" 
           Binding="{Binding ExtendedProperties[ParentName]}" />
        <data:DataGridTextColumn Header="Parent name (method 2: reference to parent)" 
           Binding="{Binding Parent.Name}" />
    </data:DataGrid.Columns>
</data:DataGrid>      

Conclusion

I have demonstrated few useful techniques which can simplify the architecture of a layered application built around the DTO pattern. You’ve learned how to share DTO classes in server-side and client-side assemblies. You’ve learn how to expose additional properties and build DTO models in an uniform way. All this combined together will hopefully make the architecture of your Silverlight application clean and maintanable.

As usual, the source code can be downloaded for this article.

Happy coding.

8 comments:

Anonymous said...

I admit, I've just took a look on your article but AFAIK The Dictionary class isn't serializable to XML so how can it be a public property's type?

Wiktor Zychla said...

I guess what you lack is the information provided here:

http://blogs.msdn.com/b/sowmy/archive/2006/02/22/536747.aspx

Regards,
Wiktor

Anonymous said...

Yeap, that's it. I've never had a chance to use WCF in my environment. Thanks anyway, great paper. Best regards.

Wiktor Zychla said...

I remember the fact that dictionaries are serialized in wcf was a nice surprize for me too. Especially since it does not work well with classic asp.net web services.

Regards.

Anonymous said...

it works w/o problem when you serializing to JSON. I mean, when you use a asp.net ws as a scripting service.

Anonymous said...

Its a nice article. However what I lack here is Silverlight UI validation. Any idea how it can be addressed.

Thanks
Imthi

Anonymous said...

i get the below build errors eventhough i followed the Project Dependencies for Build Please help
Error 2 The type or namespace name 'BusinessLayer' does not exist in the namespace 'SilverlightDTOStudy' (are you missing an assembly reference?) C:\Users\sh244568\Documents\Visual Studio 2010\Projects\SilverlightDTOStudy\SilverlightDTOStudy.Web\Global.asax.cs 7 27 SilverlightDTOStudy.Web
Error 3 The type or namespace name 'SilverlightDTO' could not be found (are you missing a using directive or an assembly reference?) C:\Users\sh244568\Documents\Visual Studio 2010\Projects\SilverlightDTOStudy\SilverlightDTOStudy.Web\SilverlightService.svc.cs 8 7 SilverlightDTOStudy.Web
Error 4 The type or namespace name 'BusinessLayer' does not exist in the namespace 'SilverlightDTOStudy' (are you missing an assembly reference?) C:\Users\sh244568\Documents\Visual Studio 2010\Projects\SilverlightDTOStudy\SilverlightDTOStudy.Web\SilverlightService.svc.cs 9 27 SilverlightDTOStudy.Web
Error 5 The type or namespace name 'DtoModel' could not be found (are you missing a using directive or an assembly reference?) C:\Users\sh244568\Documents\Visual Studio 2010\Projects\SilverlightDTOStudy\SilverlightDTOStudy.Web\SilverlightService.svc.cs 23 16 SilverlightDTOStudy.Web

Wiktor Zychla said...

You could possibly recreate the service reference in the silverlight project - just drop the existing reference and then readd it.