5

I've this WCF web service hosted inside a Sharepoint 2010 web application.

This service is created using the following factory:

Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"

I can successfully access this service from a Visual Web Part inside the same SP web application via jQuery.

Now I need to access it from a completely independant C# project outside Sharepoint.

Using the following code returns a 400 - Bad request

// local
string uri = @"http://sharepoint-site-domain/_vti_bin/namespace/serviceName.svc/serviceMethod";

// create the web request object
var request = WebRequest.Create(uri);

//req.Proxy = WebProxy.GetDefaultProxy(); // Enable if using proxy
request.Method = "Get";        // Post method
request.ContentType = "application/json; charset=utf-8";

// Send the data to the webserver
var response = request.GetResponse(); // throws 400 - Bad request exception

// create a stream reader
using (var reader = new StreamReader(response.GetResponseStream()))
{
    // get the response text
    responseText = reader.ReadToEnd();
}

Also take in consideration that this application will receive the Web method URI as a parameter and call the method, so adding the reference to a specific web service instance in the C# project is not an option.

How can I access it?

Thanks

UPDATE

Here are the headers copied from Fiddler when the REST service is called in the Sharepoint site via jQuery:

GET http://spsiteurl/_vti_bin/serviceNamespace/Service.svc/Method?_=1355849073206 HTTP/1.1
Host: spsiteurl
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: spsiteurl/Pages/SomePage.aspx
Cookie: OfflineClientInstalled=0; http://spsiteurl=PrevWorkbookUrl=J082NAOxhS+hD3YntFcGpENzasyP3LUFrNLcxX7/DsWKoqUxbUXkz8RlcMew6hXOhcxSGRzQiLljOduVZtpt36KhVhYZXWF1m27PgamZZp9F3owZ3O0YMw41m+IGWA78jilTnqbiz+TrJpEbmfqYinMLbpMvw0zxaZOIjD3iyLQgVkqZLIOcUAAgPjLjjGPPrBPeUxrazoLVcWxFduLeRl6pGXb2yfC6M9N49CH7nhEZqm03d8ebaiOJII3jRYaKORk3UzjPmXQ=&ClientPermissions=J082NAOxhS+hD3YntFcGpENzasyP3LUFrNLcxX7/DsWKoqUxbUXkz8RlcMew6hXOhcxSGRzQiLljOduVZtpt30+39ImUYscvwSdMGCDgdHb/85CJpVrvCJ657VMStRjuEGqIeCo9z++Au/7/1uzt3ib4rtgrkYyEMjFVXfxHYNVRlWuTCRH0wu8j+lKTh2zw&FirstColumn=0&FirstRow=0&ActiveSheetName=Budget%20Summary&EWRLT=07/31/2012 19:30:55&cwut=http%3A%2F%2F374848%2Dweb1%3A23170%2FSynxi%20Demo%20Data%2FFinance%5FBudget%5F2013%2Exlsx&wfnt=Finance%5FBudget%5F2013%2Exlsx&SessionId=36.a71dab4d-da9e-4729-850f-ee87e92664ed162.1.V21.1i/9tAJ4bohdi2eqj0yR990.5.en-US5.en-US73.+0300#0000-11-00-01T02:00:00:0000#+0000#0000-03-00-02T02:00:00:0000#-006036.b4b95b83-1e5e-436e-b5c4-20a8429ffe331.N; TimerJobsPage=CurrentMode=WebApplication; Microsoft.SharePoint.Administration.SPServer=CurrentId=65cad12d-0358-4b7b-b0fb-66ff13df87c9; Microsoft.SharePoint.Administration.SPWebApplication=CurrentId=b89d78b2063d4ac4810ebde4e04a829e; digInstalled=0; stsSyncAppName=Client; stsSyncIconPath=; databaseBtnText=0; databaseBtnDesc=0; loginAsDifferentAttemptCount=; previousLoggedInAs=; WSS_KeepSessionAuthenticated={b89d78b2-063d-4ac4-810e-bde4e04a829e}; http://374848-web1:23429=PrevWorkbookUrl=ISCYczELEmZ3gKRBAW0D247X81zt/BfamCjgrIgoKzQDAfe6ndSvRRfLtNodMlAV4SyQcg3m1wzMjOLoZusllnD6MTYotG6tbAzjz02XyRxTEuKyWLMFsAO9gMC0qO/qUfCCLpF8u2Xd5Bkk/u+qO/JRPg5BWQuNl696GfvxCZfGMKvwgsAZ1bsCCJLFRS0hXzig0BJYBYuhCjYF2+ODEN1stZLxbd5eQpSwJzZajuKndO8O/0EhmfgORWf2CWmY42gDXCPiAwg=&ClientPermissions=ISCYczELEmZ3gKRBAW0D247X81zt/BfamCjgrIgoKzQDAfe6ndSvRRfLtNodMlAV4SyQcg3m1wzMjOLoZuslls88H/HhVfg4hpoUkHwpUb/knahen7/IFvYhv53qkfF5mX0Ht4/VyuQgrnntFMWnIP4UNsuPMvXcivx5kgpKNVZgFfjK/TkL1WrCNggTvYPB&FirstColumn=0&FirstRow=0&ActiveSheetName=Budget%20Summary&EWRLT=11/30/2012 21:58:33&cwut=http%3A%2F%2F374848%2Dweb1%3A23429%2FShared%20Documents%2FFinance%5FBudget%5F2013%2Exlsx&wfnt=Finance%5FBudget%5F2013%2Exlsx&SessionId=36.a71dab4d-da9e-4729-850f-ee87e92664ed162.1.V21.1fkgT6S2xnz8Yv6QpyHV090.5.en-US5.en-US73.+0300#0000-11-00-01T02:00:00:0000#+0000#0000-03-00-02T02:00:00:0000#-006036.b684c406-404f-4aae-ade7-e18d8ce9d01e1.N; Ribbon.WebApp=1440531|-1|625|-1509927122

So there are a lot of Cookies embedded in the request I think. How can I replicate them when calling it from outside in C#?

The service contract:

[OperationContract]
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string Test();

Implementation:

 public string Test()
 {
     return "You are in Site Collection: " + SPContext.Current.Web.Title;
 }
11
  • 1
    If you have fiddler or firebug, can you look at the exact HTTP Request you are sending and copy it here. And also, try to set the "Accept" to "application/json".
    – natdico
    Dec 6, 2012 at 19:00
  • 2
    Do not do web service dev without Fiddler, you will waste SO MANY HOURS if you don't: fiddler2.com/fiddler2
    – James Love
    Dec 6, 2012 at 19:26
  • Ok, I have Fiddler, what exactly do you need me to copy? Also, just a normal jQuery.ajax() request and just entering the web method url in the browser returns the expected result. It's only trying to do it via C# that throws BAD REQUEST, so maybe I need to use some other C# object to do it.
    – empz
    Dec 6, 2012 at 21:38
  • @natdico I think I've found the problem. The web service is using the factory that creates a REST service, to be accessible via jQuery. But in order for .NET to access needs to be a SOAP service with the endpoints. Is there any way to have both?
    – empz
    Dec 6, 2012 at 23:05
  • 1
    I don't think it's a problem with the headers because typying the url of the web method in the browser works fine. But trying to do a GET request (which is what jQuery does) using Fiddler or .NET returns a 400 - Bad request. My guess is that it doesn't work because I'm not "logged in" to the Sharepoint Site while using Fiddler or .NET, but in the browser I am.
    – empz
    Dec 7, 2012 at 15:25

6 Answers 6

3
+50

Why don't you try to first make a request to the site url, which will authenticate you (I am assuming your .NET Client and the SharePoint site are in the same Network/Forest), and you will be able to get the correct cookies that you can use against your REST service.

To use the site url cookies against your REST service:


            const string siteUri = @"https://siteUrl";
            var uri = string.Format(@"{0}/{1}", siteUri,
                          "_vti_bin/namespace/serviceName.svc/serviceMethod");

            CookieCollection cookies;

            var request = (HttpWebRequest)WebRequest.Create(siteUri);
            request.CookieContainer = new CookieContainer();
            request.UseDefaultCredentials = true;
            //request.Credentials = new NetworkCredential("username", "password");

            using (var response = (HttpWebResponse) request.GetResponse())
            {
                cookies = response.Cookies;
            }

            var rest = (HttpWebRequest) WebRequest.Create(uri);

            rest.CookieContainer = new CookieContainer();
            foreach (Cookie cookie in cookies)
            {

                rest.CookieContainer.Add(cookie);
            }

            rest.Method = "Get";
            rest.ContentType = "application/json; charset=utf-8";

            var res = request.GetResponse();           

8
  • Well, this seems a step forward. The GetResponse() doesn't throw an exception like before, but when trying to read the res.GetResponseStream() throws an "Cannot access a disposed object. Object name: 'System.Net.HttpWebResponse'."
    – empz
    Dec 18, 2012 at 18:48
  • just don't call request.GetResponse(), just call GetResponsStream() [it's because you can't call GetResponse twice]
    – natdico
    Dec 18, 2012 at 19:02
  • I think this should work but I don't think you need the cookies part. I'm assuming you're using Ntlm/Windows auth.
    – Kit Menke
    Dec 18, 2012 at 19:10
  • Well, thank you! It worked. Even without the cookies foreach. But for some reason it doesn't work without the first request. Any thoughts about why is that needed? Also, this will be installed in different Sharepoint 2010 servers, with different configurations. Do you think this could not work in any specific scenario? Thank you again, you saved my week.
    – empz
    Dec 18, 2012 at 19:40
  • @emzero, is the Client and the SharePoint Site within the same network? what's between the SharePoint Site and the Client?
    – natdico
    Dec 18, 2012 at 19:56
0

This is an example of how to call a WCF service hosted on SharePoint using C#.

In this example the CreateFolderStructure method is called to create folders in a SharePoint Document Library.

The IWebConnectorService is the Service Contract and the FolderStructure is a Data Contract.

EndpointAddress endpoint = new EndpointAddress(new Uri("http://localhost:1111/_vti_bin/WebConnectorService/WebConnectorService.svc"));
WebHttpBinding webHttpBinding = new WebHttpBinding();
webHttpBinding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
webHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;

using (ChannelFactory<IWebConnectorService> cf = new ChannelFactory<IWebConnectorService>(webHttpBinding, endpoint))
{
  cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
  IWebConnectorService channel = cf.CreateChannel();
  FolderStructure folderStructure = new FolderStructure
  {
     IntegrationName = "Integration Name",
     CreationPath = "WebConnectorItemsLibrary",
     //CreationPath = "Pre Post Cover",                    
     RootFolderName = "Root Folder",
     IntegrationId = "32874932847328",
     SubFoldersNames = new List<string> { "Application", "Assessment Report", "Policy and invoice" }
  };
  FolderStructure result = channel.CreateFolderStructure(folderStructure);
}
5
  • 1
    That is for a SOAP service, this is a REST service.
    – empz
    Dec 18, 2012 at 16:51
  • No I am calling a RESTful service that returns JSON.
    – user3470
    Dec 18, 2012 at 17:35
  • Are you sure? Is that a custom WCF Restful service that you've created and hosted in the Sharepoint site? I'll try it. Thanks
    – empz
    Dec 18, 2012 at 18:24
  • Question, where does the IWebConnectorService come from? I mean, I don't have the serviec contract in this .NET project. Should I copy the service contract to this new project and use that?
    – empz
    Dec 18, 2012 at 18:28
  • Well, did what you suggested and now I'm getting a System.TimeoutException with an inner exception saying "The remote server returned an error: (504) Gateway Timeout.".
    – empz
    Dec 18, 2012 at 18:40
0

This is the code I use. First, you need to add a service reference in the project containing the code that needs to call the service. Reference your deployed WCF Service running under SharePoint by entering the service URL, then adding /mex at the end (which gets the WSDL). To do this, enable anonymous access on your sharepoint site in IIS (you can undo that after the service reference is added).

To call the service use:

// Create a ChannelFactory based on the Interface your service implements
ChannelFactory<SharePointServiceReference.IMyServiceChannel> _channelFactory;
SharePointServiceReference.IMyServiceChannel _client;
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly)
{
    CloseTimeout = new TimeSpan(0, 0, 5, 0),
    OpenTimeout = new TimeSpan(0, 0, 5, 0),
    ReceiveTimeout = new TimeSpan(0, 0, 5, 0),
    SendTimeout = new TimeSpan(0, 0, 5, 0)
};

// CHANGE TO HttpClientCredentialType.Windows for Kerberos
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;

// build the service URL, 
// typically it will (should) be deployed under a unique folder in the _vti_bin,
// which is mapped to the ISAPI folder in the 14 hive
var endpointAddress = new EndpointAddress(string.Format("{0}/_vti_bin/MyService.svc", "http://spbaseurl"));
_channelFactory = new ChannelFactory<SharePointServiceReference.IMyServiceChannel>(binding, endpointAddress);
// pass credentials of the currently logged in user (or specify specific credentials)
_channelFactory.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
// THIS IS IMPORTANT
_channelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
_client = _channelFactory.CreateChannel();
try
{
    _client.MyMethod();
}
catch (FaultException ex)
{
}
1
  • This is a REST based WCF service, there's no WDSL.
    – empz
    Dec 18, 2012 at 18:41
0

This worked for me:

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
// include this otherwise you'll get 401 UNAUTHORIZED
request.UseDefaultCredentials = true;

request.Method = "GET";

var response = request.GetResponse();

// create a stream reader
using (var reader = new StreamReader(response.GetResponseStream()))
{
    // get the response text
    string responseText = reader.ReadToEnd();
}
1
  • I was expecting he would get a 401 (UNAUTHORIZED), but instead he is getting 400 - Bad request, which tells me it's not just because of [UseDefaultCredentials] (which we need anyways) but also because of some cookies (perhaps because the SharePoint site is behind some kind of UAG or TMG).
    – natdico
    Dec 18, 2012 at 19:15
0

After several days of trial and error this is what ended working for me:

var siteUri = @"http://sharepoint-site-uri";
var methodUri = @"http://sharepoint-site-uri/namespace/service/method";
string responseXml = null;

var request = (HttpWebRequest)WebRequest.Create(siteUri);
var cc = new CredentialCache { { new Uri(siteUri), "NTLM", new NetworkCredential("user", "pwd", "domain") } };
request.Credentials = cc;

try
{
    // first site request
    var response = (HttpWebResponse)request.GetResponse();
    if (response.StatusCode == HttpStatusCode.OK)
    {
        request = (HttpWebRequest)WebRequest.Create(uri);
        request.CookieContainer = new CookieContainer();

        // copy the authentication cookies from the response
        foreach (Cookie c in response.Cookies)
        {
            request.CookieContainer.Add(c);
        }

        request.ContentType = "application/xml; charset=utf-8";

        // second request, now against the service url
        using (var reader = new StreamReader(request.GetResponse().GetResponseStream()))
        {
            // get the response text
            responseXml = reader.ReadToEnd();
        }
    }
}
catch (Exception e)
{       
   // handle error
}
0

You can try to enable Basic authentication on your site to see if it is an authentication issue.

IIS Manager / Sites / SharePoint / Authentication / Basic Authenticaton=Enabled.

To use it you should add you login/password pair to the authentication header like:

"Authorization: Basic TXlMb2dpbjpNeVBhc3N3b3Jk"

where "TXlMb2dpbjpNeVBhc3N3b3Jk" is a base64 from the pair login/password in the following form "login:password". So this authentication string was generated for "MyLogin:MyPassword".

1
  • Adding this makes the error change to "(401) Unauthorized" even when using a full farm admin. Anyway, this couldn't be a proper solution because this will be installed on clients servers and we can't force it to use basic authentication if they don't want to.
    – empz
    Dec 18, 2012 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.