Using a WCF Client to talk to SharePoint OOTB Web Services

Posted on 7/8/2008 @ 12:08 AM in #SharePoint by | Feedback | 7611 views

First of all - OOTB SP Web services suck! There I said it. I know this will make many raise their eyebrows, but sure bring on your arguments. I think WCF is so darned superior to ASMX's that you'd be crazy to not use WCF instead of asmx's.

However, certain web services are darned hard to write ;-), example - ExcelService.asmx. So sometimes you may have a situation where you would want to use an OOTB Web Service with a WCF client. This takes some interesting adjustments.

The issue is, after you "right click add service reference", WCF gives you a clean proxy - divorced from authentication mechanisms. So, I did "Add Service Reference" to ExcelService.asmx, and wrote the following C# code,

   1:  ExcelService.ExcelServiceSoapClient client = new ExcelSheetFun.ExcelService.ExcelServiceSoapClient();
   2:  
   3:   
   4:  ExcelService.Status[] outStatus;
   5:  string sessionID = client.OpenWorkbook(docLibAddress + excelFileName, "en-US", "en-US", out outStatus);
   6:  ExcelService.RangeCoordinates rc = new ExcelSheetFun.ExcelService.RangeCoordinates();
   7:  rc.Column = 2;
   8:  rc.Row = 3;
   9:  rc.Height = 3;
  10:  rc.Width = 1;
  11:  outStatus = client.Calculate(sessionID, "SimpleAddition", rc);
  12:  object o = client.GetCell(sessionID, "SimpleAddition", 4, 1, false, out outStatus);

Only to be rudely shown the following error -

 

The exception is a MessageSecurityException, and it says ---

"The Http request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'NTLM'.

Okay so, ExcelService expects NTLM (or Kerberos). To make it work, you need to make 2 adjustments -

a) Change your .config to the following under the security for your basicHttpBinding -

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <system.serviceModel>
   4:      <bindings>
   5:        <basicHttpBinding>
   6:          <binding name="ExcelServiceSoap" SettingsRemovedForBrevity>
   7:            <readerQuotas SettingsRemovedForBrevity/>
   8:            <security mode="TransportCredentialOnly">
   9:              <transport clientCredentialType="Ntlm" proxyCredentialType="Ntlm" realm=""/>
  10:              <message clientCredentialType="UserName" algorithmSuite="Default" />
  11:            </security>
  12:          </binding>
  13:        </basicHttpBinding>
  14:      </bindings>
  15:      <client>
  16:        <endpoint address="http://smw2k8/_vti_bin/ExcelService.asmx"
  17:            binding="basicHttpBinding" bindingConfiguration="ExcelServiceSoap"
  18:            contract="ExcelService.ExcelServiceSoap" name="ExcelServiceSoap" />
  19:      </client>
  20:    </system.serviceModel>
  21:  </configuration>

And, add the following line to your C# code (shown in bold red)

   1:  ExcelService.ExcelServiceSoapClient client = new ExcelSheetFun.ExcelService.ExcelServiceSoapClient();
   2:  client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
   3:   
   4:  ExcelService.Status[] outStatus;
   5:  string sessionID = client.OpenWorkbook(docLibAddress + excelFileName, "en-US", "en-US", out outStatus);
   6:  ExcelService.RangeCoordinates rc = new ExcelSheetFun.ExcelService.RangeCoordinates();
   7:  rc.Column = 2;
   8:  rc.Row = 3;
   9:  rc.Height = 3;
  10:  rc.Width = 1;
  11:  outStatus = client.Calculate(sessionID, "SimpleAddition", rc);
  12:  object o = client.GetCell(sessionID, "SimpleAddition", 4, 1, false, out outStatus);
  13:   

If you wish to specify alternate credentials, you may simply do so using the following code:

 

   1:  client.ClientCredentials.Windows.ClientCredential = 
   2:      new System.Net.NetworkCredential("userName", "password", "domain");

 

What is cool about WCF now is that, if (which it doesn't), your server side ASMX (which you didn't write), supported alternate credentials (like any good WCF endpoint would have), you could have used ANY authentication mechanism. Heck, it could have used any communication mechanism as well. But since you are stuck with asmx's that you didn't write - you're outta luck. But atleast you can use them as asmx's over basicHttpBinding.

Anyone still wanna argue that ASMX's suck? Go on .. tell me why you love asmx's over WCF. I'll give ya a starter, asmx's don't need all these twiddly configurations, but you get so much more flexibility with WCF, so you gotta live with it's twiddliness.

Sound off but keep it civil:

Older comments..


On 7/16/2008 4:14:00 PM Steve Clements said ..
As i think i heard from you we will all just be xml editors and tweakers soon!!

I agree with that WCF is more superior...who wouldn't, but this post certainly addressed a need.

Perhaps MOSS v.Next will be WCF services with AJAX dripping from the UI!!


On 7/16/2008 4:47:50 PM Steve Clements said ..
Although I should say it doesnt seem to work for all...


using (OCUserProfileService.UserProfileServiceSoapClient service =


new TestAndDestroy.OCUserProfileService.UserProfileServiceSoapClient(GetWcfBinding())) {

service.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;

PropertyData[] properties = service.GetUserProfileByName("domain\\user.name");

foreach (PropertyData p in properties) {


Console.WriteLine(p.Name);


Console.WriteLine(p.Values);


}

produces....


The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://microsoft.com/webservices/SharePointPortalServer/UserProfileService:GetUserProfileByNameResponse. The InnerException message was 'Error in line 1 position 392. 'Element' 'IsPrivacyChanged' from namespace 'http://microsoft.com/webservices/SharePointPortalServer/UserProfileService' is not expected. Expecting element 'Name | Privacy'.'. Please see InnerException for more details.

any ideas???


On 7/25/2008 5:03:57 PM Brock said ..
Awesome -- works great !!!

But -- boy are you right -- those OOTB webservices totally blow.

I am going to be deploy my own WCF services (using the VPP mangler) using the SP object model to get some decent services (I hope).


On 7/25/2008 11:58:32 PM Sahil Malik said ..
Brock - are you the Brock I know? (Brock A?)


On 10/17/2008 3:53:09 PM Tony Hinkley said ..
There are a couple of issues with the WCF client when binding to the GetUserProfileByName (and others). In this case, the error is due to the fact that the WSDL for the UserProfileService doesn't return the elements in the correct order in its response, compare the expected response in the WSDL with the actual response and you'll see that the fields are in the wrong order, which breaks the deserialize step of the call, there are others with the same issue. You need to alter the WSDL to reflect the correct ordering.

Then you may be struck with the issue I currently have, which is that my WCF client proxy won't deserialize guids, as it's can't reconcile them to a schema definition. Works fine in good old System.Web.Services though, which would be fine, if I wasn't calling this from Silverlight!


On 10/17/2008 11:26:52 PM YK said ..
I got the same exception during call the webservice of "GetUserProfileByName" in silverlight. does anyone the reason?

There was an error while trying to deserialize parameter http://microsoft.com/webservices/SharePointPortalServer/UserProfileService:GetUserProfileByNameResponse. The InnerException message was 'Error in line 1 position 375. 'Element' 'IsPrivacyChanged' from namespace 'http://microsoft.com/webservices/SharePointPortalServer/UserProfileService' is not expected. Expecting element 'Name | Privacy'.'. Please see InnerException for more details.


On 11/12/2008 2:12:08 AM Gavin Mckay said ..
You just saved me! I have been looking for a solution to this problem for about 2 days now grrrr - kept getting FaultExceptions with *no* information to help out :( Thankyou!


On 12/29/2008 10:25:22 PM Greg said ..
I have to say I also found your post very helpful. Thank you for blogging this solution!


On 5/7/2009 4:10:16 PM Louie said ..
excellent solution!! Fixed it right up!


On 6/4/2009 2:36:32 AM Roman said ..
Hi! Sorry very mach, but I can't udrestand - What is solution?


I have similar error.


On 2/11/2010 11:29:07 AM Edwin Morris said ..
Thank you!!


On 2/16/2010 7:46:56 AM boomshanka said ..
Ah...the feeling of spending the last two hours searching the web for the C# solution to the "Unauthorized Http Request" and to have it abruptly end by your great post is simply priceless.


On 3/10/2010 9:12:39 AM Steve said ..
You can avoid the C# line adding an endpoint behavior :

<behaviors>


<endpointBehaviors>


<behavior name="SPEndpointBehavior">


<clientCredentials>


<windows allowedImpersonationLevel="Impersonation" />


</clientCredentials>


</behavior>


</endpointBehaviors>


</behaviors>

then applying this behavior to the endpoint :


<endpoint address="http:/server/_vti_bin/Webs.asmx"


behaviorConfiguration="SPEndpointBehavior" binding="basicHttpBinding"


bindingConfiguration="SPSoap" contract="SharePointWS.Webs.WebsSoap"


name="WebsSoap" />


On 3/10/2010 11:15:08 AM Sahil Malik said ..
Steve - Thank you for the tip!


On 3/18/2010 10:25:51 AM Trent Foley said ..
Thanks for pointing out the AllowedImpersonationLevel setting. This did the trick for me. I ended up configuring this programmatically as follows:


<pre lang="csharp">


// Binding


var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);


binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

// EndPoint Address


var endpointAddress = new EndpointAddress("http://mysharepointsite/_vti_bin/Lists.asmx");

var listServiceProxy = new ListsSoapClient(binding, endpointAddress);


listServiceProxy.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;


</pre>


On 5/10/2012 11:59:22 AM Pankaj said ..
Hi


This resolved my error... To others please also watch for the config parameters... don't forget 'Ntlm' and 'TransportCredentialOnly'