Take the 2-minute tour ×
SharePoint Stack Exchange is a question and answer site for SharePoint enthusiasts. It's 100% free, no registration required.

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;
 }
share|improve this question
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 '12 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 '12 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. –  emzero Dec 6 '12 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? –  emzero Dec 6 '12 at 23:05
    
You have to have all the necessary headers, so if it working with JQuery, monitor the request headers and compare it with one from C# using fiddler. And, one more thing, did you try setting the "Accept" header to the webrequest (C#) to be "application/json"? –  natdico Dec 7 '12 at 4:49
show 6 more comments

6 Answers

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".

share|improve this answer
    
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. –  emzero Dec 18 '12 at 17:26
add comment
up vote 0 down vote accepted

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
}
share|improve this answer
add comment

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();
}
share|improve this answer
    
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 '12 at 19:15
add comment

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();           

share|improve this answer
    
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'." –  emzero Dec 18 '12 at 18:48
    
just don't call request.GetResponse(), just call GetResponsStream() [it's because you can't call GetResponse twice] –  natdico Dec 18 '12 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 '12 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. –  emzero Dec 18 '12 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 '12 at 19:56
show 3 more comments

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)
{
}
share|improve this answer
    
This is a REST based WCF service, there's no WDSL. –  emzero Dec 18 '12 at 18:41
add comment

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);
}
share|improve this answer
1  
That is for a SOAP service, this is a REST service. –  emzero Dec 18 '12 at 16:51
    
No I am calling a RESTful service that returns JSON. –  user3470 Dec 18 '12 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 –  emzero Dec 18 '12 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? –  emzero Dec 18 '12 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.". –  emzero Dec 18 '12 at 18:40
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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