WCF RIA Services – What you need to know when creating DTO/Presentation Model
Note: This is based on the WCF RIA Services Beta, so the code in this post can change in a future release of the framework.
When defining DTO or a “Presentation Model” (Not the Presentation Model pattern by Martin Fowler, a model suited for presentation purpose, not all entities are) there are things you may want to now. The following is an simple DTO:
public class Order { [Key] public int OrderID { get; set; } public IEnumerable<OrderRow> Rows { get; set; } }
The KeyAttribute is used to mark one or more properties to be the unique identifier of the object. As you can see the OrderID is a public property with both set and get methods. In some cases we want to have the OrderID as “read-only” and one or all more properties “read-only”. To make sure a property is “read-only” we can for example make sure the set method is internal:
public class Order { [Key] public int OrderID { get; internal set; } public IEnumerable<OrderRow> Rows { get; set; } }
We can also remove the set to make it “read-only”. The generated code on the client-side will now have an Order class where the OrderID is “read-only”, BUT! It still has the set method. It’s first at runtime we get an exception when we try to set a “read-only” property to a value. In this case it can be too late or results in bugs. Here is the client-side version of the generated OrderID property:
[DataMember()] [Editable(false)] [Key()] [ReadOnly(true)] public int OrderID { get { return this._orderID; } set { if ((this._orderID != value)) { this.ValidateProperty("OrderID", value); this.OnOrderIDChanging(value); this._orderID = value; this.RaisePropertyChanged("OrderID"); this.OnOrderIDChanged(); } } }
To make a whole DTO “read-only” (still set methods will be generated, so we will only know that a property is “ready-only” at runtime if we try to give it a value) we can avoid adding the Update operation to our DomainService for that DTO.
When it comes to IEnumerable types (including data structures inherit from the IEnumerable interface) they will by default be “read"-only”, in a way where we can’t Add objects to it. To make it not “read-only” we need to add a Add operation of the type the collection takes to our DomainService, for example:
public void AddOrderRow(OrderRow row) { }
If you don’t add the Add operation and no Update operation for the OrderRow, we can still on the client side change the OrderRow returned from the collection. So have that in mind. By default in C# if we return an IEnumerable<T> as in the code example in this post, it’s read-only, we can’t add or remove or do anything to an IEnumerable interface. It will be read only one the server-side if we create an instance of the DomainService, but not on the client-side. On client-side it will be generated to a EntityCollection<T>. Sadly an interface with a lot of methods that we maybe don’t even want the user to see, for example Remove or Add if we only want to return a “read-only” collection. We can have the Add and Remove disabled by not adding the Add or Remove operation to the DomainService for the type the collection holds, but still it’s available for the user on the client-side, and they can use them. If they don’t test the code or run the part where they will use them, it can results in bugs. It’s first at runtime we get an exception that we can’t use them.
When using a DTO we can’t add a constructor which takes parameters, only the default will be generated on the client side, and is also needed. So if we want to use an immutable class, we can’t by just write:
public class Order { public Order(int orderID) { //.. } //.. }
The reason why we can’t the constructor on the client-side, is that it will contain code which will not be added to the client-side Order class. So to make it possible to use a constructor, we can use the .shared feature:
Order.shared.cs
public partial class Order { public Order(int orderID) { this.OrderID = orderID; } }
Note: Remember to make the main DTO partial and you have to add the default constructor also.
You can’t never make the DTO immutable and “read-only”, because the set methods are always added to the client-side generated class. The way it’s “read-only”, is during runtime.
If you want a testable DTO where validation should be part of the test (some validation are core concerns and business logic), you can forget about it. BUT! Logic added to a DTO, will not longer make it a DTO. WCF RIA Services uses Annotation to add validations, which will take place on the client-side and server-side. If you add validation code inside of a object you want to expose through a DomainSerivice, it will stay on the server-side, so if you add a validation annotation also, you break DRY (Don’t repeat yourself), but you can write test that will test the object on the server-side. To make sure you don’t repeat yourself and don’t run the same validation twice on the server-side, you need to handle the validation by your own. A god place to do it is in the ViewModel on the client side, for example with IDataErrorInfo and INotifyDataErrorInfo interface. If we only could have partial properties, we could used the .shared feature to add our own validation to the .shared files and make sure we share our validation on the server- and client-side. BUT! Remember a DTO with logic added, is no longer a DTO, it’s an Object.
When you return the IEnumerable<T> from your Query method, the Query passed from the client-side down to the DomainService, will only take place after the data is returned. So if your query method retrieves thousands and millions of objects, it will always do that. In this case it would be advisable to use the IQueryable<T> or interpret the Query passed down to the DomainSerivce to filter your data. If you decide to return a IQueryable<T>, the query will still be executed after the Query method is executed. So what you have to do is to make sure your data access components supports the use of IQueryable<T>, Entity Framework and Linq 2 SQL does, also nHib with the LINQ support I guess. But, have in mind that you can’t execute the query, just pass the IQueryable<T> along to the next layers. If you ask me, I wouldn’t do that, it will result in a deferred execution and I will “sort” of lose my control where the query against my data source will take place.
If you want to know more about associations with DTO, check my previous post: http://weblogs.asp.net/fredriknormen/archive/2009/11/20/wcf-ria-services-and-dto-with-association.aspx
If you want to know when I publish new blog posts, or just follow my life, you can find me on twitter: http://www.twitter.com/fredrikn