Amazon.com Widgets

RIA Services: A DomainService IS A WCF Service – Add Service Reference

I made the fairly bold statement at my PDC09 talk that a DomainService IS A WCF Service.  That is, everything you know about a WCF service should be true of a DomainService.  I didn’t have time to get into this in my talk, so I thought I’d hit the highlights here.  And in the process show how to consume a DomainService from a WinForms.    You can also see more examples at: http://code.msdn.microsoft.com/RiaServices 

You need:

 

You can download the completed solution as well.  and be sure to check out the full talk

 

1. Getting to the Service

The first thing we need to do is get at the data underlying service.  In the mainstream Silverlight case this is all handled for you by the implicit link between the Silverlight client and the ASP.NET server.  However, in the vanilla WCF case, you get the full control.  The URL to the service is of the following format:

http://[hostname]/[namespacename]-[classname].svc

so in my case that is:

http://localhost:30335/Services/MyApp-Web-DishViewDomainService.svc

Hitting that URL in the browser gives you the very familiar WCF proxy help screen:

image

And tacking on the ?wsdl gives you the WSDL for this service

http://localhost:30335/Services/MyApp-Web-DishViewDomainService.svc?wsdl

 

image

 

The rest is easy for anyone halfway familiar with WCF… Create a new WinForms project and select Add Service Reference.  Enter the URL (note discover doesn’t work for this sort of service yet)…

image

The you have a service! 

 

 

2. Querying for the Data

Now, we have a service, let’s look at actually getting data out of it.  In this case I already have a WinForms DataGridView on my form.  So getting data into it should be no problem. 

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     var context = new DishViewDomainServiceClient("BasicHttpBinding_DishViewDomainService");
  4.     var plates = context.GetPlates(4);
  5.     this.dataGridView1.DataSource = plates.RootResults;
  6.     foreach (DataGridViewRow row in dataGridView1.Rows)
  7.     {
  8.         PlatesListOriginals.Add(ToPlate(row));
  9.     }
  10.     dataGridView1.CellEndEdit += dataGridView1_CellEndEdit;
  11.     dataGridView1.SelectionChanged += dataGridView1_SelectionChanged;
  12. }
  13.  

In line 3, we create a new instance of the web service client and point it at the right binding.    The service exposes a couple of different bindings as you can see in the app.config file for the WinForms app:

  1. <endpoint address="http://localhost:30335/Services/MyApp-Web-DishViewDomainService.svc/soap"
  2.     binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_DishViewDomainService"
  3.     contract="ServiceReference1.DishViewDomainService" name="BasicHttpBinding_DishViewDomainService" />
  4. <endpoint address="http://localhost:30335/Services/MyApp-Web-DishViewDomainService.svc/binary"
  5.     binding="customBinding" bindingConfiguration="BinaryHttpBinding_DishViewDomainService"
  6.     contract="ServiceReference1.DishViewDomainService" name="BinaryHttpBinding_DishViewDomainService" />
  7.  

In line 4, we call the service to get our list of Plates… in this case we are doing things synchronously.. you could of course do it async if you’d like.

In line  5, we bind the DataGridView to the results of this call.

In lines 6-9, we are saving off the “original” values..  for each item we got.. this will help us when we do updates.

In line 10, we handle the cell edit event, we will come back to look at that later.

in line 11, we sign up for the selection changed event so we can initialize the picture…

  1. void dataGridView1_SelectionChanged(object sender, EventArgs e)
  2. {
  3.      Plate currentPlate = ToPlate(dataGridView1.CurrentRow);
  4.      this.pictureBox1.ImageLocation = "http://hanselman.com/abrams/Images/Plates/" + currentPlate.ImagePath;
  5. }
  6.  

Be patient with this one… sometimes it takes a while load a picture.  it is using hanselman’s server which gets slammed sometimes ;-)

image

Now we have our data, we can scroll through it and view the pretty pictures. 

 

3. Updating the Data

But how do we update the data… well, let’s take a look at CellEditEnd event handler…

  1. void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
  2. {
  3.     var context = new DishViewDomainServiceClient("BasicHttpBinding_DishViewDomainService");
  4.     Plate currentPlate = ToPlate(dataGridView1.Rows[e.RowIndex]);
  5.     ChangeSetEntry[] changeSet = new[] {
  6.         new ChangeSetEntry{
  7.          OriginalEntity = PlatesListOriginals[e.RowIndex],   
  8.          Entity = currentPlate,
  9.          Operation = DomainOperation.Update
  10.         }
  11.     };
  12.     context.SubmitChanges(changeSet);
  13. }
  14.  

 

In line 3, we are creating a new context.  we could be sharing with the load method, but I thought this would be cleaner to follow.

In line 4, we save off the currently selected plate.

In lines 5-10 we are building up a changeset to send to the server. 

Notice we need to give it the original values we saved off in the load method.    Getting the original values right is the likely the hardest part here.  Keep in mind that assignment in C# (and VB) is by default by reference.  So you can’t just store off a reference, you must actually make a copy of the original values.

Then in line 12, we submit the changes. 

Make a change, tab off it..  This will call the server and post your update.   Re run the app to see that it took.

image

Notice here we are sending one item in the change set.  You could of course build up a change set on the client with many entries and then send them as a batch. 

 

I hope that helps to make it clear how a DomainService IS A WCF Service… You can download the completed solution as well.  and be sure to check out the full talk

Published 22 November 09 11:57 by BradA
Filed under: , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Abhilashkk said on November 23, 2009 9:25 PM:

Is there a way to use dynamically loaded assemblies for domain services ? If so how to do that?

# nanhuacrab said on November 24, 2009 12:17 AM:

MyApp.Web project References dot find System.Web.DomainServices.EntityFramework, why?

i use vs2010beta2, silverlight4beta.

# Alenas said on November 24, 2009 8:35 AM:

I have a question about custom properties and I can not find an answer anywhere.

For example imagine we have a Route class, and Route class has an array of coordinates.

I serialize that array of coordinates to DB as Binary (byte[]) and Deserialize from DB Binary to Coords[]

I use LinqToSql for this and this works fine, but I can not find a way to send this Route class over to my Silverlight application. I tried WCF RIA and ASP.Net data services.

I think my problem is that Coords class is not being considered as an entity (cause it is my custom class). Or maybe I need to add some attribute to Route.Points custom property, so RIA would understand that this property needs to be DataMember (but this property is NOT mapped to DB column).

It is pretty weird, that this does not work out of the box, cause it even works with AJAX and AJAX is not such a powerful technology as RIA services. I could implement this with WCF webservice, but maybe there is a way to do this with RAI?

# DevKaiser said on November 25, 2009 12:48 PM:

Thanks for the update Mr. Abrams.

I'm wondering if you could give us a quick lesson on how to implement Inserting and not just updating? I've been looking around but I haven't figured out how to tell to my DataGrid or Details control to add a new record.

# Ben said on December 15, 2009 3:20 PM:

Is there a way to hide the WSDL so that it is not served up to prying eyes?  We have "RequiresAuthentication" decorated on many of our methods and it would be nice if users didn't even know what the names of the methods were in the first place.  Any way to hide the WSDL or specific methods in the WSDL?

# BradA said on December 15, 2009 10:51 PM:

Good point Ben, we are looking at turning off the metadata by default.. Love to have feedback on that.  

For now, you’ll have to plug-in a custom DomainServiceHost and override AddDefaultBehaviors:

               base.AddDefaultBehaviors();

               this.Description.Find<ServiceMetadataBehavior>().HttpGetEnabled = false;

# Ben said on December 16, 2009 1:58 PM:

Great, that should work for now.  My initial thought is that it would be great to have a Web.Config setting that could be toggled to expose or hide globally all public methods in the RIA / WCF services.  Maybe a setting called "isExposeAsWebServices" with a boolean value?

That would make it really easy to toggle at least the basic security behaviour.

Oh, while I'm on the subject of making suggestions, it would be SO COOL if we could have a similar setting for the default security setting for calling RIA / WCF methods with or without authentication.  We want all our methods secured and it is error-prone and time consuming to have to decorate all our methods with the RequresSecurity attribute.  It would be nice (and more secure by default) to have a setting that toggled all methods secure or exposed by default.  

Then you could keep the great convenience you have with getting up and running with RIA / WCF out of the box but could avoid the accidental proplems of a developer forgetting to put the requires security attribute on a method that returns a collection of secret data and it accidently goes to protection totally open and exposed.  That's my big worry and we are spending a lot of time code reviewing just to make sure we don't miss this.

# nappisite said on December 18, 2009 2:11 PM:

Can you provide an example of calling a domain service from ajax?

# Bryan Reynolds said on December 19, 2009 3:41 PM:

Is there a way to use the DomainDataSource as well?  Meaning over WCF.

# Pragati said on December 23, 2009 6:22 AM:

Hi,

I've done the same way as per blog. But, I used the service asynchonously like -

private CustDomainService.CustDomainServiceClient objClient = new prjTestRIAservice.CustDomainService.CustDomainServiceClient("BasicHttpBinding_CustDomainService");        

       public MainPage()

       {

           InitializeComponent();            

           objClient.GetCustomersAsync();

           objClient.GetCustomersCompleted += new EventHandler<prjTestRIAservice.CustDomainService.GetCustomersCompletedEventArgs>(objClient_GetCustomersCompleted);

       }

       void objClient_GetCustomersCompleted(object sender, prjTestRIAservice.CustDomainService.GetCustomersCompletedEventArgs e)

       {

           var lstdata = e.Result;

           grdTest.ItemsSource = lstdata.RootResults;

       }

*******************************************

I'm getting error like -

'prjTestRIAservice.CustDomainService.QueryResultOfCustomers' does not implement inherited abstract member 'System.Windows.Ria.Services.QueryResult.GetIncludedResults()'

and

'prjTestRIAservice.CustDomainService.QueryResultOfCustomers' does not implement inherited abstract member 'System.Windows.Ria.Services.QueryResult.GetRootResults()'

# rjacobs said on December 25, 2009 3:48 AM:

I'm also running up against the same problem as pragati.  My situation requires mt tohhost the domain services in a completely seperate web site from the silverlight app.  When I try to add the domain service as a service reference to the silverlight app, I get the above mentioned errors.  The domain service work fine in a traditional asp page amd wcf service when added as a service reference.

# Pragati said on December 27, 2009 11:25 PM:

Hi rjacobs, please let me know the solution if you get any. My email id is - pragati.dukale@revalanalytics.com

I'm still with the same problem for SL appln.

# Andréi TV said on December 30, 2009 1:43 AM:

Hi,

Is there a way to host a domainService in a console app or a windows service.

I've been through all the web to find something about this topic.

thank you.

# Andréi TV said on December 30, 2009 1:43 AM:

Hi,

Is there a way to host a domainService in a console app or a windows service ?

I've been through all the web to find something about this topic.

thank you.

# Paul P said on December 31, 2009 11:53 AM:

I'm getting that problem that results in this error:

An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:

System.NullReferenceException: Object reference not set to an instance of an object.

  at System.ServiceModel.Description.ServiceMetadataBehavior.MetadataExtensionInitializer.GenerateMetadata()

According to Google, I'm not the only one.  No one has posted what they've done to solve that though.  Any ideas?

# Pragati said on January 4, 2010 2:07 AM:

Hi rjacobs,

Plz chk the link & the changes done by Konkani in crossdomain file  -

http://forums.silverlight.net/forums/p/43293/313526.aspx

My issue has got solved with this, hope the same for you.

Pragati

# Evan M said on February 2, 2010 4:57 AM:

Great article Brad. One quick question. When decorating the service with RequiresAuthentication, how do you authenticate a client?

Thanks,

Evan

# Aggelos Mpimpoudis said on February 5, 2010 12:45 AM:

How is possible to create references in ChangeSetEntries in order to submit a simple hierarchy like Report (1) -> ReportEntry (n), back to server?

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

Search

This Blog

Syndication

Page view tracker