Monday, 6 August 2012

2. Silverlight + RIA Services – Services

What tooling is doing – LinqToEntitiesDomainService<T> based on Entity Framework model derived from DomainService where T is our LINQ to Entities ObjectContext.
Now let’s dig into this - DomainService is an abstract class with no abstract methods but lots of things to override;
Note - Sometimes you don't want to give the possibility to instantiate a class but you need this class as a base class for other classes. So we can have abstract class without any abstract method.



There’s quite a lot of functionality in there relating to ChangeSets such as;

ChangeSet property 
AuthorizeChangeSet() 
ExecuteChangeSet() 
PersistChangeSet() 
Submit() 
ValidateChangeSet() 
There’s functionality that looks to relate to querying such as;

Query() 
Count() 

and then there’s the notion of “invoking” something;

Invoke() 

and then some additional pieces around all of this like;

Initialize() 
OnError() 
and various pieces of contextual information such as ServiceContext, ServiceDescription, ValidationContext, AuthorizationContext 

and so there’s quite a lot in this one class.

If I create DomainService like my ExampleDomainService here; We get the following DomainService


 along with the necessary references ( System.ServiceModel.DomainServices.* at least ) to make this build. In my web.config file I also noticed that a module is added to the pipeline – DomainServiceHttpModule - and ASP.NET compatibility is turned on for System.ServiceModel.

So, what’s the role of the DomainServiceHttpModule? It uses a VirtualPathProvider in order to dynamically make available service(s) that it constructs from DomainService derived types that it finds in the referenced assemblies of the application.

You can see this ( if you’re following along ) by visiting the equivalent of;
http://[VROOT]/Services/WebApplication1-ExampleDomainService.svc

where VROOT for me was localhost:27544 because I was serving up my web application project via Cassini on port 27544.

That SVC file is dynamically generated rather than being something that has to physically exist in the project and the dynamically generated SVC file ends up looking something like;

<%@ ServiceHost Factory=”DomainServiceHostFactory” Service=”ExampleDomainService” %>

and so this factory is used to bring in a custom service host for WCF ( DomainServiceHost ).

The DomainServiceHost knows how to perform a few tricks including;
1. Generate a service description for WCF from your DomainService 
2. Generate a set of endpoints for WCF from your DomainService 

In terms of the service description, the built-in way that this happens is by reflecting over your DomainService class looking for particular kinds of operations exposed publicly from that class. The set of operations that are looked for and the shape that they need to have are listed up here but the basic idea is that the framework code is looking for;

Query operations 
Insert operations 
Update operations ( these fall into 2 categories of “Update” or “Custom” ) 
Delete operations 
Invoke operations 

and then building a service description based upon what it finds. 

In terms of the endpoints, the DomainServiceHost makes available a default endpoint over HTTP (or HTTPS) using a binary encoding and it also adds in an authentication scheme based on what it sees being used by the default webHttpBinding that you have configured. That is – as an example, if webHttpBinding is using “Windows” authentication by default then the default endpoint will do the same.

My request for WebApplication1-ExampleDomainService.svc above was met with an error;
“ContractDescription 'ExampleDomainService' has zero operations; a contract must have at least one operation.”
which makes sense in that my DomainService has no functionality available on it. 

The simplest thing I could do is to add an operation to it and the simplest kind of operation that I can add is an Invoke operation because those are closest to standard WCF service operations in that they are calls from client to service that are simply executed ( asynchronously ).

I could add an “Invoke” operation;



and then my service no longer complains when I visit its URL at WebApplication1-ExampleDomainService.svc;




Now – I can’t get WSDL for this endpoint even though there’s a link there. That’s not the intention for this default endpoint.

This default endpoint is for easy access from a WCF RIA Services client. It’s possible to expose other endpoints for access from other clients ( I’ll return to this ) but this endpoint is the default one and is targeted at easy, efficient calling from a WCF RIA Services client.
On the client-side the generation process will make a corresponding ExampleDomainContext class that knows how to “talk” back to my service with public members on that class that cause the invocation of my Add method server side. That means that I can write code such as ;



and that all works fine and calls my service-side operation for me without any additional steps.
It’s interesting to note what kinds of interaction this call from the client drives with my ExampleDomainService class on the service-side here. If I were to override the methods Initialize() and Invoke() then I’d see them being called as these pictures from the debugger shows;




you can see that I’m authenticated ( interesting! I’ll return to this ) and that the operation type is an Invoke. We next arrive in my override of the Invoke method;


which is being called with the method that the client side wants to invoke and the parameters for that method. Then my actual Add method would be invoked, the return value gathered and sent back to the client. So, the DomainService has a sort of “life cycle” for handling client-side calls and here the invocations coming from the client cause the service-side code to go through a cycle of Initialize->Invoke->MyMethod.

Now, whilst this ease of calling without an explicit service reference has some advantages over just using raw WCF it’s more likely that we’d want to expose sets of entities for the client to operate on and we see a lot more benefit in doing that. So, if I have an entity such as; it feels like I should be able to write a method that matches the pattern for a query operation such as;



but that won’t work even with my made up test entities being returned. The framework needs an entity to have a Key defined to tell it how to identify an instance ( not unreasonable ) and so I might do; and then make sure that the Key is set up ( notice that I keep returning the same keys here for repeat invocations );


and then I can query against that from the client side – for example;




It’s kind of interesting to see what flows across from client to service in this instance – taken from Fiddler;


where we can see that the request was encoded into the query string and the response;



was binary encoded but the important point is around the client’s automatic ability to communicate with the service – not the particular protocols that it uses to do it.
It’s also interesting to see the invocations made onto the DomainService and, once again, if I override a few methods and use the debugger I can see that when the client runs a query I see a call to Initialize() on the newly constructed DomainService and then;



a call arrives into the Query method and you can see that the Method is specified and the Query is specified and IncludeTotalCount is specified – it’s all come into the call.
Then the base class implementation here will take that and route it to my method GetPeople() to actually get the data that is then used as the basis of the query which can then be executed and the results returned to the client. What about if we want to modify data? At the moment, my ExampleDomainService doesn’t support the idea but I could add a few empty methods to it to at least give the impression that it can support modifications;



Ok, so now when I rebuild I see a change on the client side.
Previously, I noticed that the DomainContext on the client side has a generated override called CreateEntityContainer and in my case this is creating an instance of a generated class called ExampleDomainEntityContainer.
Now, prior to my addition of those 3 new methods service-side that class looked like;


but after the additions I now see;



and so that EntityContainer is allowing modifications to my Person entity set so I can write code on the client side that attempts inserts/updates/deletes such as;


where we are doing a single delete, a single update and a single insert. Note that nothing crosses the client/service barrier until we hit the SubmitChanges call on line 19 above. At that point, we see the service-side ExampleDomainService get instantiated and then its Initialize() method is called as we’ve come to expect. From there, the calls are;



and so you can see that we get a call to Submit() and what’s been sent from the client side is a ChangeSet and the debugger clearly shows that the ChangeSet contains one insert, one update, one delete. So, the methods that we’ve written ( InsertCustomer, UpdateCustomer, DeleteCustomer ) are not “directly” called from the client side but, instead, are routed through Update which drives a “life cycle” of method calls - next we see a call to AuthorizeChangeSet;




and you can see that the ChangeSet in progress is represented in this.ChangeSet for access. There’s also the AuthorizationContext property which is null in this case ( more on that in a later post ).From there, we move to ValidateChangeSet;



and from there to;



and then the base class implementation causes my InsertPerson, UpdatePerson, DeletePerson methods to be called to do their work before making these changes final by calling;


so there’s a whole set of interactions driven by the arrival of our ChangeSet into the Submit method on our ExampleDomainService and the DomainService is really offering three things here as service operations;

Query 
Invoke 
Submit 

There’s another aspect to Update in that there’s a notion of a named update operation. If I was to add a named update operation to my service such as;


then that can be called on the client side via one of two mechanisms – it shows up on the generated ExampleDomainContext as you’d expect but it also shows up as an instance 
method on the generated Person entity on the client side so that I can change my client code to add;


adding that call to OperationOnAPersonWithNoSpecialNamePrefix on lines 17/18 and then the service-side interactions from this are;

ExampleDomainService::ctor 
ExampleDomainService::Initialize() 
ExampleDomainService::Query() 
and that satisfies the query and then for the Submit call we see;
ExampleDomainService::ctor 
ExampleDomainService::Initialize() 
ExampleDomainService::Submit() 
ExampleDomainService::AuthorizeChangeSet() 
ExampleDomainService::ValidateChangeSet() 
ExampleDomainService::ExecuteChangeSet() 
InsertPerson(), UpdatePerson(), DeletePerson() 


and then my named update operation is called;


followed up with a call to ExampleDomainService::PersistChangeSet() at the end of the cycle.
Up until now, my service has only offered the single entity set Person but it’s pretty likely that we’d have related entities in the model that I’d want to expose. Perhaps Person can have a set of addresses.


Note that in order to relate entities, I have the Address.Id property but also the foreign key of Address.PersonId which links back to the “owning” Person entity. There’s also an attribute 
[Association] which I’ll apply to my Person entity which tells the framework which properties to look at in order to determine these key values.

The framework also needs a bit more help in the sense that it needs to know whether the Addresses belonging to a Person are there for server-side programming or to be included 
down on the client-side. This is done via an IncludeAttribute. 

With that said, I can modify my Person entity in order to add the [Include] attribute, the [AssociationAttribute] and to make sure that each Address instance has the right PersonId 
foreign key set on it;


you might also notice one other attribute has crept in – the CompositionAttribute. What’s that for? Well, there’s a very good explanation here but what it’s essentially saying is that (in this model) the Person owns the Address and so (e.g.) removing the Address from the Person means the Address needs to be deleted – it doesn’t exist in its own right.

If I then add some CRUD operations to my ExampleDomainService for my Address entity;


and make sure that my test entities returned from my GetPeople() query are created with the right flag such that they create dummy Address data;


then I can code against it on the client-side – iterating through the Persons/Addresses in the standard manner;


where it’s worth noting that this is one query to the service rather than one for the Persons entity set and then one for each set of Addresses belonging to them ( i.e. Addresses are not being lazily loaded here ).


And I can also do some inserting, updating, deleting from the client side – for example;


and so this is removing 1 address, updating a second address and then adding a new person with a new address. On the service-side when we hit  ExampleDomainContext::ExecuteChangeSet I see calls to my methods;
InsertPerson ( for the new Id = 6 with the Addresses property populated to the created Address on line 12 with Id 61 ) 
InsertAddress for that Address 
UpdatePerson for the Person with Id = 1 which has 2 Addresses attached to it because 1 was deleted client side 
UpdateAddress for the Address which was updated on line 4 above 
DeleteAddress for the Address which was removed on line 3 above 
so my client-side work gets nicely replayed to my service-side and if I had a proper data access layer under these classes then it would do something with my data-store to actually 
make these changes live.


This post has got long but there’s one last thing I wanted to squeeze in. The RIA Services Client<->Service communication is largely automatic and the focus is on getting the functionality built rather than worrying about protocols and so on underpinning it.
However, that doesn’t mean that you can’t access these services from elsewhere and there’s a few ways of doing it. Within the RIA Services framework itself, there’s support for 
offering access over OData and you can do that by altering your configuration file. If I modify my config file to add a custom configuration section for domainServices within system.serviceModel as in;


and then also use that configuration section in order to request an OData endpoint;


then I can visit my service at WebApplication1-ExampleDomainService.svc/odata/ ( notice that trailing slash there because it seems to matter a lot ) and I see;


so – no collections exposed there but in order to expose collections such as my Person data I need to tell the framework which is the default query for that entity type which involves 
changing my ExampleDomainService class; and now when I visit my service I can see;


and I can then navigate into that dataset as in;



but I’m not sure that I can take that further by adding ( e.g. ) additional parts to the query such as indexing into the data by primary key or starting to build up expressions including $top, $skip, $filter, and so on.

An OData endpoint is just an example here though. It’s possible to expose other kinds of endpoints for other kind of clients. There are two more possibilities within the RIA Services Toolkit ( SOAP and JSON ) or you can write your own.
So, to offer up a SOAP endpoint I can dig into my RIA Services Toolkit and change my configuration;

and now I’ve got a SOAP endpoint that I can hit against and if I visit my service for a WSDL file which in my case involves hitting;
http://localhost:27544/WebApplication1-ExampleDomainService.svc?wsdl
then I see WSDL;


and I can go and do an Add Service Reference against that from Visual Studio (or some other IDE or tool) and start to work with my service in that way.

No comments:

Post a Comment