How to open a WCF RIA Services application to other type of clients: the SOAP endpoint (3/5)
We’ll see here how to open the application used in the 2 previous articles with a SOAP endpoint that will offer us “classical” WCF calls. We’ll then see how to use this new endpoint from a WPF client that will add and delete items. We’ll do the same operations from a Windows Phone 7 client also. The tricky point to be resolved in both cases will be linked to the authentication cookie to handle. It’s needed to pass-through the forms based authentication in place and to be able to execute actions restricted to authenticated users and/or members of a specific role. You will be able to download the Visual Studio 2010 solution containing the job done during these 3 articles (SL Business App, WPF client & Windows Phone 7 application) at the end of this post. You’ll be able also to view these 3 clients in action thanks to a HTML5 video.
This article is the third one out of 5:
1 – Review of the initial application
2 – How to expose the service as an OData stream consumed by Excel and from a web application built with WebMatrix
3 – How to expose the service as a “standard” WCF Service used from a WPF application and a Windows Phone 7 application (this article)
4 – How to expose the service as a JSON stream to be used by a HTML5/jQuery web application
5 – Step by step procedure to publish this WCF RIA Services application to Windows Azure & SQL Azure
Enabling the SOAP endpoint on a WCF RIA Services DomainService
This will be done of course inside the web.config file. However, the Silverlight 4 tools for Visual Studio 2010 doesn’t install by default the required assemblies needed to enable this endpoint. You need to download & install the WCF RIA Services toolkit for that.
Once the toolkit installed, add a reference to the Microsoft.ServiceModel.DomainServices.Hosting assembly and open the web.config file. Under the OData endpoint, add this new endpoint:
<add name="Soap" type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory,
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
Once done, you’ll be able to enter and test this URL in your browser:
https://nameofyourserver:port/ClientBin/NameOfYourSolution-Web-NameOfYourDomainService.svc
And you can use it also to generate the client proxy thanks to the “Add Service Reference…” action under Visual Studio. For instance, you can add a reference to my service deployed in Azure here:
https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc
Using the SOAP endpoint from a WPF client
Let’s see how to call the remote methods exposed in our DomainService from a WPF client using a very light UI (== ugly). The purpose is to concentrate ourselves on the essential part. Still, in order to have something not too ugly, I’ve used the free themes available on codeplex here: https://wpfthemes.codeplex.com/
1 – To simplify also the logic, we’ll do read/write operations on the category entities rather than on the books. As the code source shared with the first article only contains the reading logic inside the DomainService to return the categories, we need to update it. To add support for add, update and delete, use this code inside the BookClubService class:
[RequiresRole("Admin",
ErrorMessage = "You must be part of the Administrator role to insert, update or delete a category.")]
public void InsertCategory(Category category)
{
if ((category.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(category, EntityState.Added);
}
else
{
this.ObjectContext.Categories.AddObject(category);
}
}
[RequiresRole("Admin",
ErrorMessage = "You must be part of the Administrator role to insert, update or delete a category.")]
public void UpdateCategory(Category category)
{
this.ObjectContext.Categories.AttachAsModified(category, this.ChangeSet.GetOriginal(category));
}
[RequiresRole("Admin",
ErrorMessage = "You must be part of the Administrator role to insert, update or delete a category.")]
public void DeleteCategory(Category category)
{
if ((category.EntityState == EntityState.Detached))
{
this.ObjectContext.Categories.Attach(category);
}
this.ObjectContext.Categories.DeleteObject(category);
}
These 3 methods allow us to add, update and delete a category. They can only be called from authenticated users members of the “Admin” group. We then ask to the ASP.NET layer (used by WCF RIA Services) to check the authentication level of the incoming requests.
2 – The next step is to add some references to the 2 services that will be available as SOAP after adding the new endpoint. The first one gives you access to the books & categories entities via the URL I’ve just shared you above. The 2nd one handles the authentication part natively available in each Silverlight Business Application with this code:
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User> { }
In my case, it’s available in Azure on this URL: https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-AuthenticationService.svc
Name these 2 references BookClubService and BookClubAuthService.
3 – Once these references added, we will be able to start querying your RIA Services from your WPF client. Add these 3 private members and change the default constructor of the main windows of your WPF application by this:
BookClubAuthService.AuthenticationServiceSoapClient authClient;
BookClubService.BookClubServiceSoapClient bookClubClient;
BookClubAuthService.User currentAuthUser;
public MainWindow()
{
InitializeComponent();
ThemeManager.ApplyTheme(this, "ShinyBlue");
authClient = new BookClubAuthService.AuthenticationServiceSoapClient();
bookClubClient = new BookClubService.BookClubServiceSoapClient();
}
We’re instantiating the 2 client proxies. If you’d like to return all the available categories and to display them inside a ListBox control, here the code-behind to use:
var categories = bookClubClient.GetCategories();
lstCategories.ItemsSource = categories.RootResults;
And here is the piece of XAML to use:
<ListBox Name="lstCategories">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CategoryName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Please note that I’m using the synchronous methods generated for the WPF proxy. Inside a Windows Phone 7 or Silverlight client, the proxy generation will only offer you some asynchronous calls. Finally, this first logic gives us this kind of output:
4 – Fine. Up to now, everything was very simple and we had the same results using the OData endpoint. If we want to go a bit further to update the categories’ entities, we need to be authenticated and member of the “Admin” group. For the authentication part, this is very straightforward. You just have to use the 2nd proxy built on the authentication service which gives us the Login() and Logout() method. Then use the following code:
BookClubAuthService.QueryResultOfUser result =
authClient.Login(txtUsername.Text, txtPassword.Password, true, "");
if (result.RootResults != null && result.RootResults.Count() > 0)
{
currentAuthUser = result.RootResults.First();
loginStatus.Content = "Welcome " + currentAuthUser.DisplayName;
imgUser.Source = new BitmapImage(new Uri(currentAuthUser.PictureURL));
}
else
{
loginStatus.Content = "Error while logging " + txtUsername.Text;
}
We can even retrieve the user’s picture he had inserted with its webcam via the main Silverlight 4 application. You’ll then have this kind of output:
You may think at this stage that the job is over as:
- We were properly authenticated by the BookClubAuthService service
- We should then be able to do some protected calls to the BookClubService ?
This is not the case. Indeed, when you go through the FBA (Forms Based Authentication) mechanism, a special cookie is built and sent back in every requests in the HTTP headers to guarantee that the callers is the user having the appropriate rights. This header is hidden and you don’t have to care about it when you’re building a “Silverlight Business Application”. The tooling and the framework handle it for you.
So, we need to find a way to share this cookie between the authentication service and the books & categories entities services.
Here is a first way to do that:
string sharedCookie;
BookClubAuthService.AuthenticationServiceSoapClient authClient =
new BookClubAuthService.AuthenticationServiceSoapClient();
using (new OperationContextScope(authClient.InnerChannel))
{
authClient.Login("theuser", @"thepassword", true, "");
// Extract the cookie embedded in the received web service response
// and stores it locally
HttpResponseMessageProperty response = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties[
HttpResponseMessageProperty.Name];
sharedCookie = response.Headers["Set-Cookie"];
}
BookClubServiceSoapClient client = new BookClubServiceSoapClient();
QueryResultOfCategory result;
using (new OperationContextScope(client.InnerChannel))
{
// Embeds the extracted cookie in the next web service request
// Note that we manually have to create the request object since
// since it doesn't exist yet at this stage
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers["Cookie"] = sharedCookie;
OperationContext.Current.OutgoingMessageProperties[
HttpRequestMessageProperty.Name] = request;
result = client.GetCategories();
}
This sample code shows you how to do an “authenticated” calls to the GetCategories() method. This means that if you’ve set some restrictions by attribute like the RequiresAuthentication on the GetCategories() method on the WCF RIA Services part, you’ll be able to goes through thanks to this approach. However, this forces us to use an important amount of additional code to transfer the cookie during each methods call!
To avoid that, we have to find a way to automatically extract the cookie and re-inject it between incoming and outgoing HTTP requests whatever the remote WCF service is called. For that, you can use the “messages inspector” approach of WCF. This one lives between the end of the tunnel and our user code. It can then analyze the stream before sending it on the network and it can also have a look to the answer back from the network before giving it back to the client. The idea is then to build something similar than this diagram:
I’m then highly recommending you reading the excellent Enricon Campidoglio’s article: Managing shared cookies in WCF. I’ve simply implemented his solution. Here are the steps to use it.
First, you need to copy these 3 files from his solution: CookieManagerBehaviorExtension.cs, CookieManagerEndpointBehavior.cs and CookieManagerMessageInspector.cs into your WPF project and potentially then change the namespace used in those files by your own. These files contain the code that will inspect every incoming/outgoing message to track any authentication cookie to extract it and re-inject if for you if needed.
To use it, go to the app.config file and under this tag:
<system.serviceModel>
Add those one:
<behaviors>
<endpointBehaviors>
<behavior name="EnableCookieManager">
<cookieManager />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="cookieManager" type="WpfBookShelf.CookieManagerBehaviorExtension, WpfBookShelf,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
Then, you need to say to the 2 endpoints that they have to use this endpoint’s behavior:
behaviorConfiguration="EnableCookieManager"
Which should normally give you this kind of XML file:
<endpoint address="https://bookclub.cloudapp.net/Services/BookShelf-Web-AuthenticationService.svc/Soap"
behaviorConfiguration="EnableCookieManager"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_AuthenticationServiceSoap"
contract="BookClubAuthService.AuthenticationServiceSoap" name="BasicHttpBinding_AuthenticationServiceSoap" />
<endpoint address="https://bookclub.cloudapp.net/Services/BookShelf-Web-Services-BookClubService.svc/Soap"
behaviorConfiguration="EnableCookieManager"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_BookClubServiceSoap"
contract="BookClubService.BookClubServiceSoap" name="BasicHttpBinding_BookClubServiceSoap" />
Once done, we’ll see that the code is much more readable and nice to review! For instance, here is a sample code that will logon the user, generate an add operation followed by a delete operation terminated by a logout:
//Using the first proxy to log in
BookClubAuthService.AuthenticationServiceSoapClient authClient = new BookClubAuthService.AuthenticationServiceSoapClient();
authClient.Login("adminuser", "password", true, "");
//Using the second proxy to manipulate the data
//exposed by RIA Services. The cookie retrieved by the first call
//to the Login() method will be reinjected for you by the WCF Message Inspector
BookClubServiceSoapClient client = new BookClubServiceSoapClient();
QueryResultOfCategory result;
ChangeSetEntry[] returnChangeSet;
ChangeSetEntry[] newItems = new ChangeSetEntry[1];
newItems[0] = new ChangeSetEntry();
//Adding a new category
Category newCategory = new Category();
newCategory.CategoryName = newCategoryName;
newItems[0].OriginalEntity = null;
newItems[0].Entity = newCategory;
newItems[0].Operation = DomainOperation.Insert;
returnChangeSet = client.SubmitChanges(newItems);
//Reloading the Category Set
result = client.GetCategories();
//Retrieving the just added category
var cToDelete = (from c in result.RootResults
where c.CategoryName.Equals(newCategoryName)
select c).FirstOrDefault();
//Deleting the just added category
newItems[0].OriginalEntity = cToDelete;
newItems[0].Entity = cToDelete;
newItems[0].Operation = DomainOperation.Delete;
returnChangeSet = client.SubmitChanges(newItems);
authClient.Logout();
Nice, isn’t it? In the little WPF sample client you’ll find in the source code to download, you’ll have this result. If I’m trying to update something without being authenticated or if I’m authenticated with a non-admin account:
The RIA Services layer has then properly done its job by checking if the user was properly authenticated and by checking if it was member of the “Admin” role before trying to let the add operation be executed. Once properly logged, I can now add a new category from my WPF client:
Once inserted, it’s of course immediately available in the main Silverlight application:
Well, you now know everything to be able to write your own WPF client on top of WCF RIA Services. Let’s now review the job to do on the Windows Phone 7 client.
Using the SOAP endpoint from a Windows Phone 7 client
Connecting to the WCF services exposed by RIA Services from Windows Phone will be very similar to what we’ve done with WPF. Still, there are 2 little differences due to the Silverlight nature of Windows Phone:
1 – The WCF client layer of Windows Phone is less rich than the complete version available with the full .NET Framework. For instance, we don’t have the "message inspectors” support on Windows Phone. We will then have to find another way to handle the authentication cookie.
2 – Every WCF calls are asynchronous in Silverlight and thus, under Windows Phone 7.
For the first point, this is hopefully very easy to handle under Windows Phone 7 thanks to the CookieContainer. To use it, you need to add inside the 2 bindings declared in the WCF XML configuration file this property:
enableHttpCookieContainer="true"
You’ll then have this kind of file:
<binding name="BasicHttpBinding_AuthenticationServiceSoap" maxBufferSize="2147483647"
enableHttpCookieContainer="true"
maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
<binding name="BasicHttpBinding_BookClubServiceSoap" maxBufferSize="2147483647"
enableHttpCookieContainer="true"
maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
Once done, this code should then work:
CookieContainer cookieContainer = null;
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
authClient = new BookClubAuthService.AuthenticationServiceSoapClient();
authClient.LoginCompleted +=
new EventHandler<BookClubAuthService.LoginCompletedEventArgs>(authClient_LoginCompleted);
status.Text = "Logging " + username + "...";
authClient.LoginAsync(username, password, true, "");
}
void authClient_LoginCompleted(object sender, BookClubAuthService.LoginCompletedEventArgs e)
{
if (!e.Cancelled)
{
if (e.Error == null)
{
if (e.Result.RootResults != null && e.Result.RootResults.Count() > 0)
{
BookClubAuthService.User user = e.Result.RootResults.First();
status.Text = user.DisplayName + " logged.";
cookieContainer = authClient.CookieContainer;
client = new BookClubService.BookClubServiceSoapClient { CookieContainer = cookieContainer };
client.GetCategoriesCompleted +=
new EventHandler<BookClubService.GetCategoriesCompletedEventArgs>(client_GetCategoriesCompleted);
client.GetCategoriesAsync();
}
else
{
status.Text = "Failed logging " + username + ".";
}
}
}
}
void client_GetCategoriesCompleted(object sender, BookClubService.GetCategoriesCompletedEventArgs e)
{
if (e.Result != null)
{
ObservableCollection<Category> list = e.Result.RootResults;
listBox1.ItemsSource = list;
}
}
This code is creating the following sequence:
1 – When the user clicks on the login button, we’re instantiating the WCF client proxy to the authentication service of RIA Services. We’re then subscribing to LoginCompleted event which will notify us if the authentication succeed or not. Finally, we’re launching the login process in an asynchronous manner via the call to the LoginAsync method.
2 – We will be call-backed inside the authClient_LoginCompleted method where we first check if everything worked as expected. If so, we’re saving the authentication cookie returned by the ASP.NET layer and available inside the CookieContainer object. We’re then using this special object inside the 2nd WCF client proxy that will allow us to do authenticated queries to the methods handling our books & categories.
The add, update & delete operations are done in the same way we’ve seen with the WPF client except that every operation are done asynchronously. For instance, here is my code that deletes a category and then checks what happened in the callback method called once the changes has been submitted:
public void DeleteCategory(string categoryNameToDelete)
{
try
{
ObservableCollection<ChangeSetEntry> deletedItems = new ObservableCollection<ChangeSetEntry>();
ChangeSetEntry newChangeSetEntry = new ChangeSetEntry();
Category categoryToDelete = (from c in _categories
where c.CategoryName.Equals(categoryNameToDelete)
select c).FirstOrDefault();
if (categoryToDelete != null)
{
newChangeSetEntry.OriginalEntity = categoryToDelete;
newChangeSetEntry.Entity = categoryToDelete;
newChangeSetEntry.Operation = DomainOperation.Delete;
deletedItems.Add(newChangeSetEntry);
bookClubClient.SubmitChangesAsync(deletedItems);
}
else
{
MessageBox.Show("Category not found.");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bookClubClient_SubmitChangesCompleted(object sender, SubmitChangesCompletedEventArgs e)
{
if (e.Error == null)
{
ObservableCollection<ChangeSetEntry> returnChangeSet = e.Result;
var operation = returnChangeSet.FirstOrDefault().Operation;
if (operation.Equals(DomainOperation.Insert))
MessageBox.Show("Category added successfully.");
if (operation.Equals(DomainOperation.Delete))
MessageBox.Show("Category deleted successfully.");
LoadCategoriesData();
}
else
{
MessageBox.Show(e.Error.Message);
}
}
Thanks to that, you can then quickly create this kind of application (source code available as download at the end of this article) using the template “Windows Phone Pivot Application” as a based.
Displaying all the categories and all the books available in my SQL DB:
Trying to insert or delete a category without being first authenticated properly:
Login screen displaying the user’s picture generated from the Webcam with the Silverlight 4 application. We then can add/delete items successfully:
You can watch the final result of this article in the video below. I show you how to create a new user using the Silverlight 4 application, how to promote him inside the “Admin” group and how to re-use its identity inside the WPF & the Windows Phone 7 applications to be able to add/delete data exposed by WCF RIA Services and its SOAP endpoints:
Note: this h264 HTML5 video will be displayed natively by IE9 & Chrome. There is a fallback done using a Silverlight player on other browsers like IE8 for instance.
Here is the source code of the Visual Studio 2010 solution hosting all the projects demonstrated up to now:
Conclusion
We’ve seen during these 3 first articles how to put in place a solution frequently needed by my customers. Silverlight 4 used in conjunction with the WCF RIA Services framework relaying on ASP.NET will offer the best productivity to the developers’ team in charge of writing the Web RIA application that will expose the business data.
Adding an OData endpoint will allow non-developers persons to read the data from the PowerPivot Excel Add-in or will be used in relatively simple web application written with WebMatrix using the OData helper.
At last, the SOAP endpoint will allow clients like WPF, Windows Phone 7, Java or others to modify the data by using the “classical” WCF layer of RIA Services. The productivity of the developers that will go through this layer will be inferior to the experience provided by SL4/RIA Services. For instance, you can’t build LINQ queries on the client side to add filtering/sorting logic that can then go over the network to the server’s data access layer. But to try workaround that, you can think of adding additional methods to the DomainService. For instance, if you’d like to return all the books only members of a specific category like the main Silverlight application is able to do via its DomainDataSource, here is the kind of method you should add to the DomainService for the WPF/WP7 clients:
public IQueryable<Book> GetBooksByCategoryId(int categoryId)
{
return this.ObjectContext.Books
.Where(b => b.CategoryID.Equals(categoryId))
.OrderBy(b => b.Title);
}
The WCF client proxy generation will then expose a new GetBooksByCategoryId() method that will offer you part of the logic available in the richer Silverlight 4/RIA Services experience.
You should now be armed to face a lot of different scenarios where WCF RIA Services will definitely be a good friend.
See you soon in the next article dedicated to the JSON endpoint accessed from an HTML5 application.
David
Comments
Anonymous
January 02, 2011
I could not find any sourcecode of the application example given in this article.Anonymous
January 02, 2011
Hi, For the sample code, you just have to click on the yellow folder which brings you on this link : cid-0ee4bd0f5ea745c6.office.live.com/.../BookShelfRIAServices%5E_WPF%5E_WP7.zip to download the sourcecode. Bye, DavidAnonymous
January 10, 2011
Hi David, On my question about reusing an existing ria service you redirected me on this post. Initialy I tried this method to since there was a sample on it on codeplex. But the one thing I was struggling with were associated properties (navigation properties) The associations that I see on the server side on the edmx file are not generated on the client side. Should I make the association in the metadata of the domainservice on the server side by using of the key, include and association attributes? Or how else can I get my related properties generated on the client side? Offcourse the related properties on the edmx file are decorated with the soapignore attribute and this can be causing it to. Any ideas? By the way, if you have a solution you should make a blogpost about it since I can not find anything about this kind of question on the web. And then your great posts will be even more complete and a very good reference for other developers with these kind of questions. Thx again StijnAnonymous
January 20, 2011
The WPF SOAP cookie trasnfer for authentication still doesn't work, still access denied.Anonymous
January 20, 2011
Hi Ben, Have you download my solution? Does it work well when you're launching my solution on your machine? Which kind of authentication are you using in your project? FBA ou Windows Integrated? Regards, DavidAnonymous
March 05, 2011
The comment has been removedAnonymous
March 30, 2011
Hi David, Can you explain how associations/compositions are intended to work through the SOAP endpoint? When I try to submit a changeset with 2 entries, one of a parent entity and one of a child, i get "child cannot be updated independently from its parent". I assume I have to do something with the Associations collection .. but documentation on that property is shady, and a KVP<string,int[]> leaves more questions than answers. TIAAnonymous
March 30, 2011
Nevermind. The KVP works like: <PropertyNameOnParent, KeysOfChildEntities>Anonymous
March 30, 2011
that isn't very clear .. see my post on the SL forums: http://j.mp/hV2rVaAnonymous
April 01, 2011
The comment has been removedAnonymous
April 17, 2011
Dear David. This is an awesome article but i got some problem when implement WCF RIA like this . I build Silverlight & WPF with same Domain Service ,everything is ok but when on Silverlight Client ,although i insert-update-delete a record but on WPF Client nothing happen immediately ,i have to re-run WPF Client to see the change. So what should i do to resolve this problem? Sorry for my poor english :). Thanks & regarding.Anonymous
April 17, 2011
@Snowdreamist: no, I rather think that this is the browser which send back the auth cookie to your webserver. Even if you add another WCF service, I guess it still hosted in the same server/domain as your WCF RIA Services logic. That's why, for the SL application and thus the browser, it's sending back the cookie. @Kenny: that's normal. There is no push mechanism in RIA Services. If you want the data to be automatically updated in the WPF client, you need to do some polling to check for new data. DavidAnonymous
April 19, 2011
The comment has been removedAnonymous
May 31, 2011
Hi, great article I was able to easily put soap onto my services and the use them in both a WPF client and a monotouch client for the iphone with authentication. My question is this, is there a way to exclude certain methods on the SOAP service that is in the RIA service? For example, i have a user registration service that I do not want to be accessible via soap. many thanks, JezaAnonymous
May 31, 2011
Hi jeza, Thanks for your feedback. No, I can't see a way to prevent some methods to be available in the SOAP endpoints while available in the based SL endpoint. The only way is maybe to create a separate DomainService containing the logic to be available only for the SL client and vice versa. Bye, DavidAnonymous
November 14, 2011
Thanks for the detailed article. I just came here to verify if I had everything right for my RIA Services SOAP endpoint. --RaghavAnonymous
November 14, 2011
Wanted to leave one more comment. I was able to access the service using http://hostname:portno/FullyQualifiedServiceName.svc (words seperated by "-") Thanks, RaghavAnonymous
April 28, 2012
Great article. Anyway, I think it's a shame on Microsoft for not providing WCF RIA for WPF. Please vote here: dotnet.uservoice.com/.../746156-support-for-wpf connect.microsoft.com/.../support-wcf-ria-service-and-mvvm-generators-for-wpf-projects-add-project-templates-for-itAnonymous
April 09, 2013
Can you please post the xml envelope for the submitchanges?Anonymous
July 28, 2014
Can u send source code link again, please? Thanks for great article!!Anonymous
May 20, 2015
Anyone have David's source code for this series. Can you give me a link? Thanks again for this series