Tuesday, January 19, 2010

WCF RIA Services – Advanced Topics

 

Introduction

This article introduces advanced information on RIA Services. Readers should have some understanding of the core concepts in RIA and have read some of the very good articles Brad has written about it:

Index on Brad’s articles.

This article also comes with a very small sample. You can use it to test the information in the article.

Projecting Entities

In this first part we will take a look at the capabilities of the projection code generator. The following snippet shows an Email property at the model in the server side:

   1: [RegularExpression(@"^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$")]
   2: public string Email
   3: {
   4:     get;
   5:     set;
   6: }

And the following snippet shows the generated code at the client side for a Email model property. The DataMember attribute is already popular. It tells the DataContractSerializer to include it in the serialization. The next attribute is the regular expression attribute. It tells RIA to validate the value that is being set in the property against the given regular expression. If it doesn’t match then a validation error happens.

If you add an assembly with your custom attributes both at the client and at the server side any attribute in the server side models is projected to the client. You can with this extend the metadata system and add your own. Give it a try:

  • Create two assembly projects, one for NET40 and one for SL.
  • Now add a simple attribute to the NET40 project.
  • Annotate a property in your models with it.
  • Notice that it does not get projected.
  • Now create a file link in the silverlight annotations project to it.
  • Recompile the solution and notice that it gets projected!

Once the Email address property enters its setter the first set is to validate the property. The validation code will look at the attributes in the property and will make sure that the property respects the conditions indicated in them. In the given example that it matches the email. If it does not match an exception is thrown and nothing else executes. For the user this is a control changing to the view state invalid.

   1: [DataMember()]
   2: [RegularExpression("^[\\w-]+(?:\\.[\\w-]+)*@(?:[\\w-]+\\.)+[a-zA-Z]{2,7}$")]
   3: public string Email
   4: {
   5:     get
   6:     {
   7:         return this._email;
   8:     }
   9:     set
  10:     {
  11:         if ((this._email != value))
  12:         {
  13:             this.ValidateProperty("Email", value);
  14:             this.OnEmailChanging(value);
  15:             this.RaiseDataMemberChanging("Email");
  16:             this._email = value;
  17:             this.RaiseDataMemberChanged("Email");
  18:             this.OnEmailChanged();
  19:         }
  20:     }
  21: }

Once the property is considered valid it starts calling the included extensibility methods. For each property a <PropertyName>Changing and a <PropertyName>Changed partial method is generated. Programmers can include custom code to handle those state changes. The RaiseDataMemberChanged raises the PropertyChanged event necessary for data binding to work.

Business Entity Data Annotations

There are several annotations that are not shown in most of the samples and posts about RIA Services. In the projects I’m working on most of the metadata is coming from CSDL files because we are using a metadata provider that takes metadata from Entity Framework’s MetadataWorkspace. The provider distributed with RIA does not work well and you might have to add the metadata manually through a metadata class.

Composite Attribute – This attribute tells RIA to treat the parent object and the child object as if they where one. This should be in relationships such as Invoice – InvoiceLine.

Include Attribute – This attribute tells RIA to generate the association code in the client side. By default associations are not included and you cannot include an association whose Type is managed by another domain service. Imagine you have a service for managing currency and one for managing orders. You can’t include the association from order to customer. To work around this you have to use the foreign key scalar properties to represent the relationship. When using Entity Framework you have to enable them when you are creating the model.

ExternalReference Attribute – This attribute tells RIA that the entity is comming from another Domain Service and prevents RIA from trying to generate an EntitySet to that entity. This prevents the error “The entity type 'X' is exposed by multiple DomainService types. Entity types cannot be shared across DomainServices.”.

Association Attribute – The association attribute tells ria how two entities relate. It tells what are the keys in both sides. The values of this attribute are used to create a filter method in the client that extracts from the child EntitySet the entities that match the relation keys:

   1: private bool FilterBusinessAddress(GeographicAddress entity)
   2: {
   3:     return (entity.PersonId == this.Id);
   4: }

It takes from the GeographicAddress EntitySet on the DomainContext the one(s) that matches the id of owner.

Understanding DomainService and DomainContext

The introductory tutorials show us that once we create a domain service and mark it with the EnableClientAccess attribute it gets projected into the client with the shape of a DomainContext. In the documentation it says that it is a stateful client side representation of a domain service. The million dollar word here is stateful.

   1: [EnableClientAccess()]
   2: public class ContactsManagementService : DomainService

Stateful is a major innovation. In most cases service agents are stateless. But let’s look at what stateful means in practice. The first thing to notice is that our context gets projected as a partial class and we can add our code to it.

   1: public sealed partial class ContactsManagementContext : DomainContext

For each query method (something that returns IQueryable<T>) the context gets one EntitySet and one GetXXXQuery method. Lets take as a server side example this method:

   1: public IQueryable<Person> GetPersons()
   2: {
   3:     return new List<Person>().AsQueryable();
   4: }

In the client side we will get an entity set for persons that is coming from an EntityContainer. This container handles accepting and discarding changes to more than one entity at the same time.

In RIA one very important pattern is the Unit of Work pattern where it is possible to affect more than one entity at the same time in an operation. They can be of the same type or not.

This allows for scenarios where, for instance, you are editing a list of Customer objects, maybe directly in a grid, maybe in a master-detail view, the important thing is that at any moment you can give the user the chance to save the changes or to discard them. At this moment the EntityContainer comes in hand, you can the SubmitChanges or RejectChanges at the DomainContext but it is the container that is doing the work.

   1: public EntitySet<Person> Persons
   2: {
   3:     get
   4:     {
   5:         return base.EntityContainer.GetEntitySet<Person>();
   6:     }
   7: }

There is not much that is possible to do with EntityContainer for now. One thing I find usefull in knowing is that how it gets generated depends on what methods of the CUD pattern you have:

   1: internal sealed class ContactsManagementContextEntityContainer : EntityContainer
   2: {
   3:     
   4:     public ContactsManagementContextEntityContainer()
   5:     {
   6:         this.CreateEntitySet<EmailAddress>(EntitySetOperations.None);
   7:         this.CreateEntitySet<GeographicAddress>(EntitySetOperations.None);
   8:         this.CreateEntitySet<Person>(EntitySetOperations.All);
   9:         this.CreateEntitySet<PersonEvent>(EntitySetOperations.All);
  10:         this.CreateEntitySet<PersonName>(EntitySetOperations.None);
  11:         this.CreateEntitySet<Reminder>(EntitySetOperations.All);
  12:         this.CreateEntitySet<TelecomAddress>(EntitySetOperations.None);
  13:         this.CreateEntitySet<WebPageAddress>(EntitySetOperations.None);
  14:     }
  15: }

If there are methods that match the Create, Update and Delete pattern for a certain entity that it gets the EntitySetOperations.All enum value, otherwise it can allow a subset of these. You can for instance prevent an entity from ever being deleted by not adding a method that matches the Delete pattern. If I go into my code and comment the delete method for Person the enumeration will change to EntitySetOperations.Add | EntitySetOperations.Edit.

And a method to create the query that can get persons from the server:

   1: public EntityQuery<PersonEvent> GetEventsByPersonQuery(Guid personId)
   2: {
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("personId", personId);
   5:     this.ValidateMethod("GetEventsByPersonQuery", parameters);
   6:     return base.CreateQuery<PersonEvent>("GetEventsByPerson", parameters, false, true);
   7: }

Note that the parameters that are used to make the call are validated. This topic his handled bellow.

The EntityQuery holds the state of a LINQ query made to a collection of entities. In practice what this means it that it holds what is needed to convert the LINQ query into a ServiceQuery and a ServiceQueryPart and return it over the wire. I typed return and it is not a mistake. What is really done is to issue a request message to the server with the query method parameters and returning a serialized query. The query is only executed when it is enumerated. It is once again serialized, then sent to the server where it gets interpreted, in most cases it gets compiled into a SQL statement from where the entities are extracted and sent back to the client with the shape of a QueryResult.

Domain Service Operations Annotations

When the conventions are not enough we can use the Insert, Update, Delete and Query attributes to indicate what CRUD pattern the method follows. Besides this we can use the Invoke attribute to add custom methods  to the service:

  • We can define one method that returns one entity and takes scalars as input.
  • We can define one method that returns a scalar a receives one or more scalars.

These scenarios are documented bellow with practical examples.

Important Notes:

  • Overloads are not permitted. You have no name the operations differently.
  • You can have methods that are hidden from the client. Place the Ignore attribute on them.
  • You can force the user to be authenticated placing the RequiresAuthentication attribute. It is also possible to demand a role with the RequiresRole attribute.

Method parameters can be validated automatically in WCF RIA Services

You can use the same attributes that you use to annotate business entities to annotate operation parameters. Also the entities received as arguments of the method will get validated.

If you look at the OperationBase class documentation it also as an enumerable of ValidationResult:

   1: //
   2: // Summary:
   3: //     Gets the validation errors.
   4: public IEnumerable<ValidationResult> ValidationErrors { get; }

As any operation in RIA it will run asynchronously. Once completed you can get the one entity in the Value property of the returned InvokeOperation instance.

Returning exactly one entity with RIA

To return one entity in RIA you simple mark the server side method with the Invoke attribute like this:

   1: [Invoke]
   2: public Person ReadOnePerson(string nationalId)
   3: {
   4:     return new Person() { Id = Guid.NewGuid() };
   5: }

An in the client side it gets this shape (and do note that there are overloads that allow callbacks and so one but this is the base one). The first thing that is done after wrapping the input parameters is validating the method call.

   1: public InvokeOperation<Person> ReadOnePerson(string nationalId)
   2: {
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("nationalId", nationalId);
   5:     this.ValidateMethod("ReadOnePerson", parameters);
   6:     return ((InvokeOperation<Person>)(base.InvokeOperation("ReadOnePerson", typeof(Person), parameters, true, null, null)));
   7: }
   8:         

 

Returning one scalar value with RIA

It is also possible to return a scalar value with RIA. Again it is necessary to mark the operation with  the Invoke attribute. The generated code is similar to the code generated when returning one entity. The generic argument of the InvokeOperation class is the Type of the method return. The following snippet shows the method at the server:

   1: [Invoke]
   2: public string ReadOnePersonName(string nationalId)
   3: {
   4:     return "Person1";
   5: }

This gets projected into the client as shown bellow. Notice the string Type in the generic argument of the return.

   1: public InvokeOperation<string> ReadOnePersonName(string nationalId)
   2: {
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("nationalId", nationalId);
   5:     this.ValidateMethod("ReadOnePersonName", parameters);
   6:     return ((InvokeOperation<string>)(base.InvokeOperation("ReadOnePersonName", typeof(string), parameters, true, null, null)));
   7: }

To consume this operation begin by adding a new view to your project or add the controls to an existing one.

   1: <Grid x:Name="LayoutRoot">
   2:     <Grid.RowDefinitions>
   3:         <RowDefinition Height="40"/>
   4:         <RowDefinition Height="40"/>
   5:         <RowDefinition Height="*"/>
   6:     </Grid.RowDefinitions>
   7:     <TextBlock Text="{Binding Path=PersonNameText}"/>
   8:     <Button Grid.Row="1" x:Name="btLoad" Click="OnLoadClicked" Content="Load"/>
   9: </Grid>

The code behind of this view has the following logic.

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     this.DataContext = new OnePersonNameViewModel();
   4: }
   5:  
   6: private void OnLoadClicked(object sender, RoutedEventArgs e)
   7: {
   8:     var viewModel = (OnePersonNameViewModel)this.DataContext;
   9:     viewModel.Load("demo");
  10: }

And the ViewModel is coded like in the following snippet.

   1: public class OnePersonNameViewModel : ViewModelBase
   2: {
   3:     private string personNameText;
   4:     private bool isBusy;
   5:     private ContactsManagementContext domainContext;
   6:  
   7:     public OnePersonNameViewModel()
   8:     {
   9:         this.domainContext = new ContactsManagementContext();
  10:     }
  11:  
  12:     public string PersonNameText
  13:     {
  14:         get
  15:         {
  16:             return this.personNameText;
  17:         }
  18:  
  19:         set
  20:         {
  21:             if (this.personNameText != value)
  22:             {
  23:                 this.personNameText = value;
  24:                 this.NotifyPropertyChanged("PersonNameText");
  25:             }
  26:         }
  27:     }
  28:  
  29:     public bool IsBusy
  30:     {
  31:         get
  32:         {
  33:             return this.isBusy;
  34:         }
  35:  
  36:         set
  37:         {
  38:             if (this.isBusy != value)
  39:             {
  40:                 this.isBusy = value;
  41:                 this.NotifyPropertyChanged("IsBusy");
  42:             }
  43:         }
  44:     }
  45:  
  46:     public InvokeOperation Load(string nationalId)
  47:     {
  48:         this.IsBusy = true;
  49:         var operation = this.domainContext.ReadOnePersonName(nationalId);
  50:         operation.Completed += (o, e) =>
  51:             {
  52:                 this.PersonNameText = operation.Value;
  53:                 this.IsBusy = false;
  54:             };
  55:  
  56:         return operation;
  57:     }
  58: }

The IsBusy flag is there to be bound to an BusyIndicator control that was omitted for simplicity.

External Reference Attribute

There are cases where an entity that is projected in a DomainContext say CustomerManagementContext requires another entity say Country. It would be nice if a property of type Country was projected in the entity Customer in order for us to use data binding to build the View.

In the following snipped I create a service to cause the error “The entity type 'X' is exposed by multiple DomainService types. Entity types cannot be shared across DomainServices.”

   1: public class Class1 : ModelBase
   2: {
   3:     [Key]
   4:     public string Class1Key
   5:     {
   6:         get;
   7:         set;
   8:     }
   9:  
  10:     [Include]
  11:     [Association("A", "PersonId", "Id")]
  12:     public Person Person
  13:     {
  14:         get;
  15:         set;
  16:     }
  17:  
  18:     public Guid PersonId
  19:     {
  20:         get;
  21:         set;
  22:     }
  23: }
  24:  
  25: // TODO: Create methods containing your application logic.
  26: [EnableClientAccess()]
  27: public class ErrorCauserService : DomainService
  28: {
  29:     public IQueryable<Class1> GetClasses1()
  30:     {
  31:         return null;
  32:     }
  33: }

The solution is to mark the property with the ExternalAttribute annotation instead of Include.

   1: [ExternalReference]
   2: [Association("A", "PersonId", "Id")]
   3: public Person Person
   4: {
   5:     get;
   6:     set;
   7: }

This will change the way code is generated and will generate the property like this:

   1: [Association("A", "PersonId", "Id")]
   2: [ExternalReference()]
   3: public global::Pedrosal.HomeManagement.Models.Person Person
   4: {
   5:     get
   6:     {
   7:         if ((this._person == null))
   8:         {
   9:             this._person = new EntityRef<global::Pedrosal.HomeManagement.Models.Person>(this, "Person", this.FilterPerson);
  10:         }
  11:         return this._person.Entity;
  12:     }
  13: }
  14:  
  15: private bool FilterPerson(global::Pedrosal.HomeManagement.Models.Person entity)
  16: {
  17:     return (entity.Id == this.PersonId);
  18: }

In practice it means that the entity is able to get the other side of an association if the FK is correctly set and the external entity set has the entity that matches the given id. To make it work one must create an instance of the dependent DomainContext and call AddReference to set the instance of the external context to use:

   1: public class EntryViewModel : ViewModelBase
   2: {
   3:     private PersonManagementServiceContext personsContext = new PersonManagementServiceContext();
   4:  
   5:     protected override void Initialize()
   6:     {
   7:         base.InitializeContext();
   8:  
   9:         this.Context.AddReference(typeof(Person), personsContext);
  10:     }
  11:  
  12:     public void LoadDependents()
  13:     {
  14:         var personQuery = (from pt in this.personsContext.GetPersonsQuery()
  15:                                 orderby pt.PersonName ascending
  16:                                 select pt).Take(1);
  17:  
  18:         var personLoadOperation = this.personsContext.Load(personQuery);
  19:         personLoadOperation.Completed += (o, e) =>
  20:             {
  21:                 this.BlogEntry.PersonId = this.personsContext.Persons.First().Id;
  22:             };
  23:     }
  24: }

Note that this is aligned with using the FK feature of entity framework beta 2.

Have fun,

2 comments: