|
|
Alla Taggar
All Tags » RIA (RSS)
-
Note: The examples in this blog post uses WCF RIA Services PDC Beta, and changes can be made in the future to the framework.
In several of my blog posts about WCF RIA Services I have used the Repository pattern to return an IEnumerable<T>, for example:
public class CustomerRepository : ICustomerRepository
{
public IEnumerable<Customer> GetCustomers()
{
//...
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
And in the DomainService:
[EnableClientAccess()]
public class CustomerService : DomainService
{
private ICustomerRepository _customerRepository;
public CustomerService(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public IEnumerable<Customer> GetCustomers()
{
//Use _customerRepository
}
}
Here is the client-side code which Loads the DTO/”Presentation Model” into the DomainContext:
var customerContext = new CustomerContext();
customerContext.Load<Customer>(customerContext.GetCustomers());
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The Load method takes a Query object as an argument, by doing so, it knows which method in the DomainService it should call to load the objects. We can on the client-side specify our own query and pass it down to the server, and WCF RIA Services will make sure the execution of the Query will take place on the server side, this will reduce the data passed over the wire to the client. BUT! There is a problem of using IEnumerable<T>. The query will take place on top of the IEnumerable<T>, which means that our Repository will return ALL customers from the database, and this can affect performance (I talked to a well known SQL Server MVP about retrieving too much data from the database, he told me that he never notice a problem of retrieving to much data from a database, there are other issues that can affect performance badly.). So in that case, we don’t need to be worry?! I still think we should try to avoid retrieving data we don’t need from the database.
How can we solve the above issue with IEnumerable<T>?
We can for example get the query passed down to our DomainService and pass it to our Repositories, in that case we can use it as a Query Object. We can get the Query information by overriding the Query method of the DomainService class. Another solution is to create our own Query Object and pass it to the server as an argument to the GetCustomers method or just simply use the IQueryable<T> interface.
How can IQueryable<T> be used?
When using Entity Framework or Linq To SQL etc, we can create a query, this query will only be executed when we tell it to, for example calling the ToList method, Single etc of the query object:
var customersQuery = from customer in myContext.Customers
where customter.Country == "Sweden"
orderby customer.CompanyName select customer;
var results = customersQuery.ToList(); //Execute the query here
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
As long as we don’t execute the customerQuery, we can extend the query, when we are done we execute it. When WCF RIA Services uses the EF or L2S Domain Service classes, it will only return the query (in this case use the IQueryable<T>) and not a list of objects, so when we use the Load method and pass down a query, the query will extend the returned query, and then WCF RIA Services will invoke the query and EF and L2S will turn the query into T-SQL and execute it against the database. In that way we can reduce the data returned from our database to the DomainSerivce.
This sounds great, let us always return IQueryable<T>!
But! and a big one, we can’t use this solution in a RIA where business logic should be on the server-side. In some cases we may need to perform some business logic based on the data retrieved from the database, in this case we need to execute the query before returning our data to the client. So in this case we need to use IEnumerable<T>. But (a lot of but here), if we don’t need to perform any business logic on the server-side before we return our objects to the client, we can use the IQueryable<T>, I think it’s a nice solution for small client/server apps. It requires that the objects we want to send to the client are well designed and with the View in consideration. If we use the IQueryable<T>, we can’t use the Repository pattern, instead we can use a Query provider. So the Repository turns into a query provider:
public class CustomerQueryProvider : ICustomerQueryProvider
{
public IQueryable<Customer> GetCustomers()
{
//...
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The CustomerQueryProvider can for example use EF or L2S to return an IQueryable<T> of customers:
public IQueryable<Customer> GetCustomers()
{
_myObjectContext.Customers;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The DomainService will look like this:
[EnableClientAccess()]
public class CustomerService : DomainService
{
private ICustomerQueryProvider _customerQueryProvider;
public CustomerService(ICustomerQueryProvider customerQueryProvider)
{
_customerQueryPrivider = customerQueryProvider;
}
public IQueryable<Customer> GetCustomers()
{
//Use _customerQueryProvider.GetCustomers()
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The reason why a Query Provider is used here, is to remove dependencies to a specific detail. Now we can have different Query Providers as long as they implements the ICustomerQueryProvider interface and the infrastructure used in the Query Provider supports the IQueryable<T> interface.
Summary
For small database driven RIA where almost not business logic is needed, we can get a lot of benefits of using the IQueryable<T> interface instead of the IEnumerable<T>. But in a enterprise RIA where business logic should take place on the server-side, the IEnumerable<T> can be a better choice. But it depends! There are so many factors that must be known before we can know which design will fit best.
If you want to know when I publish new blog posts, you can follow me on twitter: http://www.twitter.com/fredrikn
|
-
Today Microsoft released the WCF RIA Services Beta, it’s now on top of WCF and by default uses binary data end points and data contract serialization. By using binary data end points we will get better performance and make the data sent over the wire smaller (The other preview versions uses pure JSON). WCF RIA Services is a great framework for Rapid Application Development (RAD) of 2-tier applications. Based on the new changes to the WCF RIA Services it’s defiantly ready for the Intranet and small applications, but how about the Enterprise. First of all if you uses the Entity Framework or Linq to SQL Domain Services, it’s not ready for the Enterprise and will never be, IMO. They are useful for small data driven applications. BUT! WCF RIA Services with the DTO (Data Transfer Object) support will make it closer to be ready for the Enterprise, but is that enough?
It’s complex to build RIA (Rich Internet Application) and require a lot of plumbing. This is something WCF RIA Services is trying to solve. Just remember there is a reason why things are complex, and what every framework developers are trying to do, is to hide the complexity by adding a new level of abstraction. The thing is, the complexity is still there. At first I was skeptic about WCF RIA Services, but it was because the very early previous wasn’t that good, it was ok, good ideas but works had to be done. Now after almost 2 years it starts to look like something promising.
I have notice several of developers using .WCF RIA Services and because it’s so easy to use, they seems to forgot about the network. What the WCF RIA Services Team have struggle with, is to make sure developers should be aware of the network. I know that even how much they try, some developers will use it in a wrong way. That is nothing they can do anything about. But is that so bad? NO! It gives the people that use it in the right way, job opportunists ;)
I wrote that WCF RIA Services with its DTO support will make it closer to be ready for the Enterprise and the uses of EF and L2S Domain Services are not a good choose when it comes to build Enterprise RIA. There are actually one advantage of using the EF and L2S DomainServices, and that is the way of passing a query from the client to the server. The query will be used when EF and L2S are querying the database (as far as I know, I hope it does at least ;)). It will results in less data sent from the data source. When using DTO the query will take place after we retrieve all the data from our DomainService query methods. But still on the server. But the good thing is that there is a solution to make sure the query also takes place when we query our data source and that is by overriding the DomainService Query method, it has the description of the query passed from the Client. But we had to interpret it and transform it to a query which can query our domain model.
It looks like developers loves the data annotations added to WCF RIA Services. The validation part is great when it comes to the uses of EF and L2S Domain Services, because the DATA objects are generated from the tools (hmm, thinking of the Devils is in the tools ;)), and it’s “hard” to add validation to it. In that case with WCF RIA Services we can use annotation to add validations, which will be available both on the client and the server. Isn’t that great? DTO also support validations buy adding annotations. You have to have in mind, that this validation can’t be tested with Unit Test, it’s the Framework which will execute it. But you also have to remember that DTO is just what is says an object which transfers data. On the client-side we may use a ViewModel which will handle the validation instead.
Back to the main question, is WCF RIA Services ready for the Enterprise? If I say Yes, it may be a lie, if I say No, it’s definitely a lie. I think it’s ready, or at least I would give it a try.
|
-
Note: This post is based on the July preview of the .NET RIA Services, so changes may happen in a future release of the framework
At the moment I’m updating a code example for my Silverlight 3.0 course and have added some exception handling to the code. I also got a question about how to handle exception while calling the Load operation of the DomainService class. So I decided to write a blog post about it, maybe someone more have the same question.
By default if you make a call to a DomainService query method, you will not get an exception on the client-side, if you don’t adding some extra code. Lets pretend that the following code is your DomainService and Query method: public class MyDomainService : DomainService
{
public IEnumerable<Customer> GetCustomers()
{
throw new ApplicationException("Error!");
}
}
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
The method above will throw an exception. If you on the client-side want to “call/use” the GetCustomers method, you will use the generated DomainContext class’s Load method:
var d = new MyDomainContext();
var loadOperation = d.Load<Customer>(d.GetCustomersQuery(), MyCallBack, null);
LayoutRoot.DataContext = d.Customers;
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
When this code is executed, you will not get any exception, so the user or you may think that no exception have occurred. But that isn’t the case. So what I’m going to write about now, is different way you can check if an exception occurred (Wouldn’t it be nice if we only could get an exception instead of adding extra code to check if an exception is thrown ;)).
The LoadOperation class has two properties we can use, HasError and Error. To check if we got an exception we can use the HasError property, if we want to get information about an exception, we can use the Error property. The first example I’m going to use, will only display an exception for the client.
On the client-side I have a StackPanel called myErrorPanel, it also have a TextBlock where the Text property is bound to a property path “Error.Message”. <StackPanel x:Name="myErrorPanel">
<TextBlock Text="{Binding Path=Error.Message}"></TextBlock>
</StackPanel>
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
In the code-behind we can now write the following code which will bound the LoadOperation to the myErrorPanel:
var d = new MyDomainContext();
var loadOperation = d.Load<Customer>(d.GetCustomersQuery()); //, MyCallBack, null);
LayoutRoot.DataContext = d.Customers;
myErrorPanel.DataContext = loadOperation;
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
If the Load method of our DominContext has an error, the Error Message will be displayed for the User. By using this solution, we will not have any idea if an error has occurred during the Load operation, if it does it will only be shown for the User. The StackPanel will also take up some space on the user interface, so to solve it we can for example add a Converter, and bound the HasError to the StackPanel’s Visibility property. The following two other solution will instead show us, how we can use a callback method and the LoadOperation’s Complteted event to have a better control if an exception is thrown during the Load operation.
The DomainContext’s Load method can take a callback as an parameter, by using a callback, we will get a notification when the Load operation is completed, and we can also see if there was any error during the Load operation. The following code will use a callback and check if an error has occurred:
var d = new MyDomainContext();
var loadOperation = d.Load<Customer>(d.GetCustomersQuery(), MyCallBack, null);
...
void MyCallBack(LoadOperation<Customer> loadOperation)
{
if (loadOperation.HasError)
throw new Exception("My Error!");
}
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
The following solution will use the LoadOptions’s Completed event to check if an error has occurred:
var d = new MyDomainContext();
var loadOperation = d.Load<Customer>(d.GetCustomersQuery());
loadOption.Completed += new EventHandler(loadOption_Completed);
...
void loadOption_Completed(object sender, EventArgs e)
{
if (((LoadOperation)sender).HasError)
throw new Exception("Error");
}
.csharpcode {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
MARGIN: 0em
}
.csharpcode .rem {
COLOR: #008000
}
.csharpcode .kwrd {
COLOR: #0000ff
}
.csharpcode .str {
COLOR: #006080
}
.csharpcode .op {
COLOR: #0000c0
}
.csharpcode .preproc {
COLOR: #cc6633
}
.csharpcode .asp {
BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
COLOR: #800000
}
.csharpcode .attr {
COLOR: #ff0000
}
.csharpcode .alt {
BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
COLOR: #606060
}
If you wonder what solution I prefer, it would be the one that uses a callback. I do want to see some changes to the .NET RIA Services Load operations, like specifying a callback just for handling exceptions, and a default generated callback added to the DominaContext, which will be called if no callback is used, by doing so, we could get an exception when the Load fails without adding some extra code.
|
-
Now I’m back from my 4 week long vacation. I have spend a lot of time with my new apartment, took about 2 month to get most of it ready, I still have a lot of things to do, but all the painting and stuff are done :) I got a question on my e-mail regarding .NET RIA Services and the Business Application project’s UserRegistartionService. It was about the AddUser method in the Service and when and where it’s called. I thought it would be great to write a post about it. Something that is good to have in mind is that the .NET RIA Services is all about code generation. The Business layer we are creating by following the .NET RIA Service business layer pattern and guide lines, will be used to generate proxy classes on the client-side to help us communicate to the server in a easy way. The proxy class on the client-side will use “Unit of Work” and Track changing etc. To use those features in a proper way, we have to use a specific “pattern” when we write our Business logic layer. Note: This post is based on the July Preview of the .NET RIA Services, and I also assume you have some basic knowledge about the .Net RIA Services. I will start with a custom DomainSerivce and how it works. Here is an example of a custom DomainService:
[EnableClientAccess()] public class MyDomainService : DomainService { private List<Customer> _customers = new List<Customer>() { new Customer() { ID = 1, Name = "John Doe" }, new Customer() { ID = 2, Name = "Jane Doe" } };
public IEnumerable<Customer> GetCustomers() { return _customers; }
public void AddCustomer(Customer customer) { _customers.Add(customer); }
public void DeleteCustomer(Customer customer) { _customers.Remove( _customers.Find(c => c.ID == customer.ID)); }
public void UpdateCustomer(Customer customer) { var customerToUpdate = _customers.Find(c => c.ID == customer.ID); customerToUpdate = customer; }
}
public class Customer { [Key] public int ID { get; set; }
public string Name { get; set; } }
The code has a MyDomainService class and a Customer entity with two properties, ID and Name. The MyDomainService has four methods, GetCustomers, AddCustomer, DeleteCustomer and UpdateCustomer. When we build the code, the .NET RIA Services will generate our proxy classes (DomainContext) to help use communicate to the DomainService on the server-side. The generated proxy class will be add to our client project.
Here is the generated proxy classes (I have removed some code to make it smaller):
public sealed partial class Customer : Entity { private int _id; private string _name; public Customer() { ... } [DataMember()] [Key()] public int ID { get { return this._id; } set { ... this._id = value; ... } } [DataMember()] public string Name { get { return this._name; } set { ... this._name = value; ... } } public override object GetIdentity() { return this._id; } } public sealed partial class MyDomainContext : DomainContext { ...
public EntityList<Customer> Customers { get { return base.Entities.GetEntityList<Customer>(); } } public EntityQuery<Customer> GetCustomersQuery() { return base.CreateQuery<Customer>("GetCustomers", null, false, true); }
... }
Note: The code I have removed is not important for this blog post.
If you look at the generated code, you can see the Customer entity and a class with the name MyDomainContext. The MyDomainContext is the class that will communicate to our MyDomainService. As you can see, there is only two public members generated, Customers and GetCustomerQuery. The GetCustomer, AddCustomer, DeleteCustomer and UpdateCustomer are not added to the MyDomainContext class. So how can we call the Add-,Delete- and UpdateCustomer from the generated proxy class?
As I mentioned before the.NET RIA Services have a track changing and an “Unit of Work” feature. The MyDomainContext class will have a method called SubmitChanges, which we can use to submit all changes, like updating entities, removing and adding etc. We can’t call the Add-, Delete- or UpdateCustomer method from the client-side directly, we must use the DomainContext “Unit or Work” feature.
The Add, Delete and Update methods in the DomainService is needed if we want to be able to add, update or remove an entity. If we don’t add those methods to the DomainService, we aren’t allowed to Add, Delete or Update Entities on the client-side. So how can we call the Add, Update and Remove methods from the client-side by using the DomainContext’s Unit of Work.The following is an example:
public partial class MainPage : UserControl { MyDomainContext _myDomainContext = new MyDomainContext();
public MainPage() { var loadOperation = _myDomainContext.Load<Customer>(_myDomainContext.GetCustomersQuery()); loadOperation.Completed += new EventHandler(loadOperation_Completed);
}
void loadOperation_Completed(object sender, EventArgs e) { var lastCustomer = _myDomainContext.Customers.Last();
lastCustomer.Name = "Last Doe";
_myDomainContext.Customers.Add(new Customer() { ID = 3, Name = "Jones Doe" });
_myDomainContext.Customers.Remove(_myDomainContext.Customers.First());
_myDomainContext.SubmitChanges(); }
In the constructor of the MainPage, the Load method of the MyDomainContext class is used. Because our generated DomainContext proxy inherits from the DomainContext base class, we have access to some methods, for example the Load and SubmitChanges method. The Load method will make an async call to the MyDomainSerivice’s GetCustomers method. When the DomainContext gets the results from the server, the loadOperation_Completed event will be “executed”. By using the MyDomainContext’s Customers property (generated by .Net RIA Services) we will have access to all the Customers that are loaded. The Customers property returns a EntitList. A DomainContext has an Entity Container, where all entities are stored. A Entity Container managed a set of EntityLists and have the responsibility to handle the changing tracking and identity management on the client.
In the code above you can see that the Name of the last customer is set to “Last Doe”. When we update an Entity, an Update operation is added to the DomainContext’s “Unit of Work”, handled by the Entity Container. No updates will take place until we tell the Entity Container to process all of the changes. The code above will also call the Add and Remove method of the Customers property. When doing so, an Add and Remove operation is added to the list of work that should be processed when we call the SubmitChanges. When the SubmitChanges is called the changes are all sent to the service all together.
Note: When we call the Add and Remove method etc, no calls to the server will take place. It will only take place when we call the SubmitChanges.
So when the SubmitChanges is called, the DomainCotnext Unit of Work will be handled. We have Updated a Entity, added an Entity and also Removed and Entity. So the SubmitChanges will now do an async. call to the DomainService, and call the methods that will handle the operations, in this case the MyDomainService’s AddCustomer, RemoveCustomer and UpdateCustomer method will be executed on the server-side.
There is a naming convention which can be used for the CRUD methods, here is a list form the .NET RIA Services documentation:
Insert
“Insert”, “Add”, “Create” prefixes
Method signature : void InsertX(T entity), where T is an entity Type
Update
“Update”, “Change”, “Modify” prefixes
Method signature : void UpdateX(T current), where T is an entity Type
Delete
“Delete”, “Remove” prefixes
Method signature : void DeleteX(T entity), where T is an entity Type
Query
Method signature returning a singleton, IEnumerable<T> , IQueryable<T> where T is an entity Type, and taking zero or more parameters
Resolve
“Resolve” prefix
Method signature : bool ResolveX(T curr, T original, T store, bool isDelete) where T is an entity type
We can also use attributes (InsertAttribute/UpdateAttribute/DeleteAttribute/QueryAttribute/ResolveAttribute) if we don’t want to use the “reserved” words:
[Delete] public void DestoryCustomer(Customer customer) { _customers.Remove( _customers.Find(c => c.ID == customer.ID)); }
Now when you know why some methods added to a Service isn’t available on the Client-side, we can go on with the Business Application project template’s UserRegistartionService
If we take a look at the Business Application project template’s UserRegistartionService. We have the following methods:
[EnableClientAccess] public class UserRegistrationService : DomainService { public void AddUser(UserInformation user) { MembershipCreateStatus createStatus;
Membership.CreateUser(user.UserName, user.Password, user.Email, user.Question, user.Answer, true, null, out createStatus); if (createStatus != MembershipCreateStatus.Success) { throw new DomainServiceException(ErrorCodeToString(createStatus)); } }
public IEnumerable<UserInformation> GetUsers() { return null; } }
We can’t from the client-side call the AddUser method, is not added to the UserRegistartionContext class. To add a new user we must add it to an EntityList, in this case the generated DomainContext will have a EntityList called UserInformations. This is generated out from the GetUsers query method. If we don’t have a query method, there is no way we can have access to an EntityList.
In the Business Application template’s LoginWindow.xaml.cs file, the generated DomainContext is created in the constructor:
private UserInformation _userInformation = new UserInformation(); private UserRegistrationContext _registration = new UserRegistrationContext();
public LoginWindow() { ...
this._registration.UserInformations.Add(this._userInformation); this.registerForm.CurrentItem = this._userInformation; }
As you can see, a new instance of the UserInformation entity (_userInformation) is added to the UserRegistartionContext’s UserInformations EntityList. At this case an Add operation is added to the UserRegistartionContext’s “Unit of Work”. The _userInformation is then bounded to a DataForm, which uses a two-way binding. So every changed done to the DataForm, will be done to the _userInformation object. When the Register button of the LoginWindow.xamls is called, the UserRegistrationService’s SubmitChanges method will be called, which will make sure the AddUser method of the UserRegistartionService will be executed and the _userInformation object will be passed as an argument to the AddUser method.
private void RegisterButton_Click(object sender, RoutedEventArgs e) { ...
_regOp = _registration.SubmitChanges();
... }
I hope this post gave you some understanding about how the DomainContext and DomainSerice handle the CRUD methods etc.
|
-
Some of you have probably heard about the Microsoft .Net RIA Services, if not you can find some info here. In this post I will show you a different view of the .Net RIA Services, how it can be used to create a RESTfull Service, or at least try ;)
REST Services is about “nouns” (resources, like friends, customers etc) with few “verbs” for example create, read, update and destroy. We can easily create a “resource”, where the resource can be in different kind of formats like JSON, XAML, RSS etc. It’s more or less up to us what kind of formats we want to use. If we take a look at Twitter, the REST API they use can return a resource in different formats. When we create the resource, we need to create the response value (A Response value is the value returned from a REST API, it’s a resource in a specific format). If we want to get all of our Followers by using one of the Twitter’s REST API, we simply use the following URL: http://twitter.com/statuses/followers.xml the .xml at the end specify the resource “followers” format. The response value is something like this:
<?xml version="1.0" encoding="UTF-8" ?>
<users type="array">
<user>
<id>1111111</id>
<name>John Doe</name>
<location>Stockholm</location>
<description />
<profile_image_url></profile_image_url>
...
</user>
...
</users>
The resource is something the developers of the API have decided. If we want to use the .NET RIA Services we can simply define the resource of the response value like a simple POCO class. Like this:
public partial class User
{
[Key]
public string ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Note: The KeyAttribute is needed to specify a way to specify a property which will work as an identity for the “entity”. When using the .Net RIA Services the the classes we can create are referred as a Entity in a model. So we create a “model” more or less, but don’t see the User class as an entity in a Model, instead as a resource used by a specific REST API.
By default .NET RIA Services will “serialize” the above class to a JSON format similar to this:
{
"__type":"User:http://schemas.datacontract.org/2004/07/SilverlightApplication8.Web.DataModel",
"ID":"1",
"Name":"John Doe",
"Description":"John Doe\u0027s description"
}
Remember that this post is a different view of the .Net RIA Services, so once again try to see the POCO as a simple resource, not as an object which we will distribute (We all know that we shouldn’t distribute objects).
Now when the resource is created, we can simply create a “fake” object which will return a list of it.
public class UserDataService
{
public IEnumerable<User> Users
{
get
{
return new List<User>()
{
new User()
{
ID = "1",
Name = "John Doe",
Description = "John Doe's description"
},
new User()
{
ID = "2",
Name = "Jane Doe",
Description = "Jane Doe's description"
}
};
}
}
}
Note: I couldn’t find a good name for this class, it looks like its a some kind of data access class but it isn’t. It’s more like taking a existing model and transform it to a resource.
The next step is to create our REST API, in this case we can for example call it Followers, the same name as Twitter uses to get Followers. We can for example use ASP.NET MVC or a normal WebForm as a REST Service to get our resource.
The following is a REST Service where ASP.NET MVC is used with a Controller named UserController with and an Action method called Followers.
public class UserController : Controller
{
public ActionResult Followers()
{
var userDataService = new UserDataService();
return View(userDataService.Users);
}
}
Followers.aspx
<%@ Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<List<Models.User>>" %>
<users type="array">
<% foreach (var user in ViewData.Model) { %>
<user>
<id><%= Html.Encode(user.ID)%></id>
<name><%= Html.Encode(user.Name)%></name>
<description><%= Html.Encode(user.Description)%></description>
</user>
<% } %>
</user>
</users>
We can get the Followers by simply use the following URL: /User/Followers">/User/Followers">http://<servername>/User/Followers
To call the REST Service and it’s API, we can for example use the Silverlight’s WebClient class. We can also use the XDocument to get the data out from the resource:
public MainPage()
{
InitializeComponent();
var wc = new WebClient();
wc.OpenReadCompleted += wc_OpenReadCompleted;
wc.OpenReadAsync(new Uri("/User/Followers", UriKind.Relative));
}
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
XDocument xmlUsers = XDocument.Load(e.Result);
var users = from user in xmlUsers.Descendants("user")
select new
{
ID = (string)user.Element("ID").Value,
Name = (string)user.Element("Name").Value,
Description = (string)user.Element("Description").Value
};
}
If we use the above solution we need to do some plumbing. We first need to do a request to our URL to get the followers, then we need to read from the XML and get the values and map it to a type (if we want to work with a typed result). In this case wouldn’t it be nice to avoid some plumbing and instead use a solution where we simply use a typed API (not a string), and also get the result as typed classes with properties?
Instead of doing all the plumbing, we can use the .Net RIA Services to create a REST Service. To create and implement our REST Service and API, we create a DomainService. A DomainService within the .NET RIA Services is a class where we add our CURD API and we can also add shared domain methods etc. To create a DoaminService we create a new class which will inherits from the .NET RIA Service’s DomainService base class. We will also add our Followers API to the DomainService:
[EnableClientAccess()]
public class UserService : DomainService
{
private UserDataService _userDataService = new UserDataService();
[Query(PreserveName=true)]
public IEnumerable<User> Followers()
{
return this._userDataService.Users;
}
}
Note: The EnableClientAccessAttribute is only used to make sure .NET RIA Services creates a client-side proxy for our DomainService, this proxy is called a DomainContext. The QueryAttribute with the property PreserverName, will make sure the generated DomainContext for the DomainService will preserver the name Followers. Normally we should add a method called “GetFollowers”, and the .NET RIA Services code generation will generate a method for the DomainContext called “LoadFollowers”.
The Followers method will use the UserDataService’s Users method to get all the followers and return them.
When we build our solution, the .NET RIA Services will generate a DomainContext for our Silverlight app, a proxy class which will make it easier for us to “call” the DominService. The generated DomainContext will communicate to the service through an AXD called DomainService.axd using REST. After we have created our DomainService, we can if we wants to, do a direct call to the DomainService.axd and use it as a REST Service:
http://<servername>/DataService.axd/SilverlightApplication8-Web-DataModel-UserService/Followers
When we enter the above URL we will get a response value in a JSON format like this:
{
"__type":"DataServiceResult:DomainServices",
"IsDomainServiceException":false,
"Results":
[
{
"__type":"User:http://schemas.datacontract.org/2004/07/SilverlightApplication8.Web.DataModel",
"ID":"1",
"Name":"John Doe",
"Description":"John Doe\u0027s description"
},
{
"__type":"User:http://schemas.datacontract.org/2004/07/SilverlightApplication8.Web.DataModel",
"ID":"2",
"Name":"Jane Doe",
"Description":"Jane Doe\u0027s description"}
],
"TotalCount":-2
}
Note: The DomainService.axd will by default using the JavaScriptSerializer.
The DomainSevice.axd will take the first “parameter” “SilverlightApplication8-Web-DataModel-UserService” and replace “-“ to “.” to get the DomainService to instantiate. In this case it will be the UserService. It will then call a method with the same name as the last “parameter” of the URL, in this case “Followers” and use the JavaScriptSerializer to serialize the result of the Followers method.
By using a class we create our resource. We have used the .NET RIA Services to create a REST Service with the API Followers to return a list of Users. We can then use the DomainService.axd to call our REST API. To make it simple for us to call the REST API and get a list of Users, we can now use the generated client-side proxy class (DomainContext) in our Silverlight project. It will give us a typed API and also a typed resource. So we don’t need to use the WebClient or a JSON Serializer on the client-side, the generated DomainContext for our DominService, will handle that for us. The following code is the code-behind file of the MainPage.xaml in a Silverlight app:
public partial class MainPage : UserControl
{
UserContext _uc = new UserContext();
public MainPage()
{
InitializeComponent();
myGrid.ItemsSource = _uc.Users;
_uc.Followers();
}
}
To make a call to the Followers API, we simply instantiate the generated DomainContext class “UserContext” and make a call to the Followers API. Every “entity/resource” we creates and returns from the DomainService will be added to the DomainContext as properties. So to get the User class after we make a call to the Followers method, we can use the Users property. When we call the Followers method, an asynchronous call to the DomainService will be made. The DomainContext will use REST behind the scene and call the DomainService.axd.
Wouldn’t it be nice if we could instead of getting a whole list of Users, make it possible to define a query which will only returns the Users we want based on a criteria? It’s possible, and that is really cool:
public MainPage()
{
InitializeComponent();
myGrid.ItemsSource = _uc.Users;
_uc.Followers(_uc.Users.AsQueryable<User>().Where( c => c.ID == "1") , null);
}
By default all “Query” methods added to the DomainService, takes a IQueryable as an argument. So we can pass a query as an argument. So now we can use the DomainContext as a proxy to call a RESTful Service and also define a query.
In this post, I wanted to give you a “different” view of .NET RIA Services, more or less to make sure you know how it works behind the scene, and also how it can be used to define a resource and a RESTful Service, because behind the scene .NET RIA Services uses REST.
|
|
|
|