Having taken a look into the service side of WCF RIA Services, lets have a look into the client side. If I have the following DomainService –
public class MyDomainService : DomainService
then what we have at Client Side is the DomainContext. The build process leaves us with a generated class MyDomainContext on the client side which derives from a framework class DomainContext.
What’s a DomainContext? It’s quite a big class –
I brought in the DomainContext’s friends – DomainClient and EntityContainer onto that diagram.
The DomainClient
Of these, I think that the DomainClient is easiest to understand. From previous investigations we know that a RIA Services client submits 3 fundamental kinds of operations to a RIA services service;
• Query
• Invoke
• Submit
and it’s the DomainClient that knows how to do the client<->server communication for Query, Invoke, Submit asynchronously with cancellation. The DomainContext then is abstracted away from the details of how these operations are transmitted to the server side by relying on the DomainClient to do that work.
Now, the particular implementation of DomainClient that we find in the framework – WebDomainClient - is a specialisation of this class that knows how to communicate with a default RIA Service endpoint.
That is, one that’s using XML, binary encoded over HTTP/HTTPS.
If I wanted my client to communicate with another endpoint such as a SOAP endpoint then I’d be looking to write a DomainClient that knew how to do that and then I’d plug that implementation into a DomainContext. For me, this means that when I’m using a non-default DomainServiceEndpointFactory on the service side I should expect to be looking to write a DomainClient on the client-side.
The DomainClient needs some fairly complex data in order to be able to do its core Query, Invoke, Submit functionality. Taking a look at the signatures, Invoke looks like the simplest;
public IAsyncResult BeginInvoke(InvokeArgs invokeArgs, AsyncCallback callback, object userState);
where an InvokeArgs is a fairly simple class;
which “just” captures the operation that we’re trying to invoke server-side and the parameters that need to get passed to it.
What about Query? The signature looks like;
public IAsyncResult BeginQuery(EntityQuery query, AsyncCallback callback, object userState);
where the details of the query to be performed are captured in an instance of EntityQuery;
note that the Query property above is an IQueryable.
Finally, what about Submit? Submit in some ways feels the most complicated. The signature looks like;
public IAsyncResult BeginSubmit(EntityChangeSet changeSet, AsyncCallback callback, object userState);
and the “payload” here is the EntityChangeSet class;
where each of those [Added/Modified/Removed]Entities properties is collection of Entity ( more to come on that later ) whereas GetChangeSetEntries returns an enumeration of ChangeSetEntry;
that’s quite a complex class but I think it’s used both as an input to the Submit operation and as an output from the Submit operation in the sense that the EndSubmit method;
public SubmitCompletedResult EndSubmit(IAsyncResult asyncResult);
returns a SubmitCompletedResult which itself has an IEnumerable<ChangeSetEntry> called Results so the class kind of serves ( at least ) two purposes as it’s being used in the two different directions of data flow from client->server and then back again.
The EntityContainer
What about an EntityContainer? Right now I don’t have any entities in my project so the generated EntityContainer is a little on the “empty” side;
By the way – this MyDomainContextEntityContainer class ends up generated as a nested class inside of the MyDomainContext class and I also see that MyDomainContext has a generated override of CreateEntityContainer which looks like;
and so that all links up very logically.
What if I had some entities service-side. Let’s add one so that my service-side code looks like;
and now on the client-side the generation process spits out some different things. Firstly, I now see that I have a different EntityContainer;
internal sealed class MyDomainContextEntityContainer : EntityContainer
and the constructor is using the base class CreateEntitySet method to create an EntitySet<Person> and add it in to the EntityContainer.
I also get something derived from Entity – my Person class;
I pasted all the code in there as I think it’s worth looking at. We have these 3 classes – EntityContainer, EntitySet<T> ( where T : Entity ) and Entity working together on the client side.
EntityContainer
EntityContainer is largely a dictionary of <Type,EntitySet> and mostly delegates its work down to the EntitySets that it contains. So, you can walk up to it and ask for EntitySet<Customer> or EntitySet<Order> and so on.
It implements property changed notification and also IRevertibleChangeTracking. If I quickly derive my own EntityContainer like this one below;
then I can write code against the “change tracking” functionality as in;
but largely this is the work of the contained EntitySets being co-ordinated by the EntityContainer to work together, I don’t think there’s a huge amount of work that the container itself is doing here.
In terms of how an EntitySet gets into the container – I think the only way is to call EntityContainer.CreateEntitySet() and that’s protected so you would need to derive an EntityContainer like I did with my example PeopleContainer there in order to do that.
Normally, there’s no need to do that because that’s what the tooling does for the entity sets that it “sees” exposed by the server-side when it code-gens an EntityContainer derived class for the client-side.
The EntityContainer also has methods called LoadEntities where you can pass a whole collection of Entity ( of mixed types ) and the EntityContainer will loop through and make sure that each Entity gets dropped into the EntitySet<T> for that particular Entity type.
As part of loading you can opt for whether the Entity that’s being loaded will;
• be ignored if an Entity with the same ID is already present in the EntitySet
• overwrite an existing Entity with the same ID in the EntitySet even if that Entity has been modified
• overwrite only unmodified properties of an existing Entity with the same ID in the EntitySet
EntitySet
The EntityContainer contains a bunch of EntitySets. There’s the EntitySet class and its derived class EntitySet<T>. As the name suggests, this is a set of Entity of a particular type that also supports property change notification and revertible change tracking along with collection changed notification.
An EntitySet can be associated with an EntityContainer and ( so far as I can work out so far ) the only way to do that is to have the EntityContainer create the EntitySet via the EntityContainer.CreateEntitySet<T> method which creates the EntitySet<T> and sets its EntityContainer property to the right value ( i.e. the owning EntityContainer );
I guess the properties/methods largely speak for themselves with the possible exception of Attach/Detach which feel familiar to me from [LINQ to SQL/LINQ to Entities]. If Add() means “treat this as a newly created entity” then there needs to be a method that means “treat this as an entity that already exists” and Attach() looks to have those semantics.
Entity
EntitySet contains a bunch of Entity objects. Entity is quite an interesting class – it’s an abstract class so the intention is that you derive from it and it implements a whole slew of interfaces;
and so an Entity is an object that supports;
• property change notification ( fairly standard )
• being an editable object ( fairly standard too – I’ve used this before in combination with the DataForm )
• the new validation interface from Silverlight 4 – INotifyDataErrorInfo ( fairly standard when you’ve seen it but a little bit painful to implement )
• the notion of change tracking via IChangeTracking and the idea of reversing those changes via the derived IRevertibleChangeTracking
and so I can write code that makes use of the ability of the object to track changes and so on such as;
and it’s clear that the Entities involved here go through states represented by their EntityState property depending upon what’s been done to them and there’s also the notion of the original values being accessible ( for the entity that I Attached because it perhaps doesn’t really make sense for the Entity that I Added ) and then being able to Accept or, in this particular case, RejectChanges to get back to where I started.
Entity manages to do this kind of change tracking because it has methods RaiseDataMemberChanging and RaiseDataMemberChanged so it’s possible for the base class implementation to be aware of before/after values and track the changes being made.
What else can Entity do?
Validate
It has capabilities for validation. By default ( i.e. if you don’t override ) this is based on the System.ComponentModel.DataAnnotations attributes ( and custom variants of those ) so if I updated my Person entity on the server side to be something like;
then I might write a little client-side code;
and, because the property setter for FirstName includes a call to the ValidateProperty() method, that causes validation to fire and so I have validation errors once that property set has completed. I could find out more about those errors with code like;
and they also surface via the Entity class implementing INotifyDataErrorInfo so I could use that interface to determine similar information.
Validation capabilities in RIA Services are not tied to only the built-in attributes from System.ComponentModel.DataAnnotations – you can do a whole bunch more around custom validation at both the property and the entity level.
I’ll follow up on this in a later post but as an example if I wanted to do a little cross-field validation on my entity by adding this code on the server side ( to a file called PersonValidator.shared.cs in order to share that code with the client-side );
and then applying that attribute to my Person entity on the server-side;
then I can write client-side code to exercise this a little as in;
and so the Entity class clearly has validation capabilities.
Invoke A Custom Update Method
The Entity class also has these intriguing methods/properties (protected) called InvokeAction, IsActionInvoked, CanInvokeAction and an enumerable EntityActions property.
What’s this about? It looks to come down to the ability to specify custom update methods to be called server-side at the time of a SubmitChanges call.
As an example, I can write some method called Foo on the server-side like this one added to my domain service;
and one of the ways that manifests itself on the client-side is by these additional generated methods on the Person class;
So, the method call Foo() that we make is turned into a call onto the base-class Entity.InvokeAction() method.
The base-class effectively captures the details of the method call made ( including parameters if we had any ) such that those details can later be retrieved from the Entity.
In the usual scenario this is used by the DomainContext in order to defer the server-side invocation of the method Foo until DomainContext.SubmitChanges() is called when the corresponding server-side functionality will be invoked as part of the whole SubmitChanges cycle.
Sticking with my made-up PeopleContainer class on the client-side I can write a little code against this functionality such as;
and we can see that by calling Foo what I’ve actually done is to set a flag to say that Foo has been invoked, that it cannot be invoked again ( right now ) and the framework has stored the fact that I called Foo into the EntityActions collection.
Whilst EntityActions is a collection, as far as I can tell it only supports the notion of a single custom invocation being outstanding at any one time.
So, we have this notion of “capturing” a method call on an Entity instance along with the parameters that it was invoked with ( not shown here ) such that the other componentry can later invoke the corresponding server-side functionality.
All in all, EntityContainer, EntitySet and Entity are pretty useful classes to have around on the client side providing a lot of functionality for us that we’d otherwise have to write.
The DomainContext
What’s left for the DomainContext then, given that;
• the EntityContainer deals with storing all the Entity instances into neat little organised sets and has capabilities for change tracking, validation and so on
• the DomainClient deals with the logistics of making sure that Query, Update, Invoke operations get from the client-side to the server-side and back again.
Largely – I think it glues these other two types together.
It does some work in its Load methods to use the DomainClient in order to load the entities for the EntityQuery from the server-side and it takes the results of that operation and pushes them into the EntityContainer via its LoadEntities method.
It also does a bunch of work around its SubmitChanges method to communicate with the EntityContainer and determine what changes have been made to the data before building up an EntityChangeSet to pass through into the DomainClient and get those changes submitted across to the server-side.
It also provides a place for the code-generation tools to work upon in the sense that if I have a domain service;
and the generation process does quite a lot to take MyDomainContext and derive it from DomainContext.
It’s reasonably clear that the Persons property is just reaching into the EntityContainer for the right EntitySet<T> and that when we perform a MyDomainContext.Load() using the results of MyDomainContext.GetPeopleQuery() then the DomainContext will use the DomainClient to grab the data from the server-side and then drop the results into the EntityContainer again.
No comments:
Post a Comment