Question

I want to set up a WCF that can be called by AJAX from a javascript loaded from a custom webpart on our Sharepoint Foundation 2010 site. To simplify the processing on the Javascript side I want to present a Restful service that give Json back to the caller.

Problem is that when I call the server with a AJAX call the SPContext.Current is null.

I am using MultipleBaseAddressWebServiceHostFactory the in svc file to create the webservice

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$"%>  
<%@ServiceHost Language="C#" Debug="true"
Service="Driftportalen.LvService.SuggestService"
Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
     %>

The contract for the webservice is:

[ServiceContract(Namespace = "", ProtectionLevel= ProtectionLevel.None)]  
public interface ISuggestServiceTest
{
    [WebGet(UriTemplate = "/SuggestAddress/{streetprefix}/", ResponseFormat = WebMessageFormat.Json)]
    [OperationContract]
    Dictionary<string, GenericAddress> SuggestAddress(string streetprefix);
}

Implementation of the webservice is basically as follows.

[Guid("BA6733B3-F98D-4AD8-837D-7673F8BC527F")]
[BasicHttpBindingServiceMetadataExchangeEndpoint]
[ServiceBehavior(IncludeExceptionDetailInFaults = true, AddressFilterMode = AddressFilterMode.Any)]
[AspNetCompatibilityRequirements(RequirementsMode =  AspNetCompatibilityRequirementsMode.Required)]
public class SuggestService : ISuggestServiceTest
{
    private SPWeb currentWeb;

    public SPWeb CurrentWeb
    {
        get
        {
            if (currentWeb == null)
            {
                var siteUrl = SPContext.Current.Web.Url;
                SPSecurity.RunWithElevatedPrivileges(delegate
                {
                    using (var site = new SPSite(siteUrl))
                    using (var web = site.OpenWeb())
                    {
                        currentWeb = web;
                    }
                });
            }
            return currentWeb;
        }
    }


    public Dictionary<string, GenericAddress> SuggestAddress(string streetprefix)
    {
        LvService lvService = new LvService(CurrentWeb);

        Dictionary<string, GenericAddress> suggestions = new Dictionary<string, GenericAddress>();

        //SNIP
        //Code that uses lvService to populate suggestions

        return suggestions;
    }
}

I have verified that if I call the webservice from the webbrowser everything works as expected and that I get the right data back.

I use the following Ajax call

 $.ajax({
   url: addressUrl + "/"+request.term,
   dataType: 'json',
   success: function (data) {
      responseCallback(data);
     $(this).removeClass("fetching");
   }
});

Using Firebug I have verified that the correct URL is called from the javascript and I have verified on the server side that the right code is indeed reached, but SPContext.Current is null.

The Sharepoint server uses Windows and Claims for login. This means the actual WCF will be run using a different account than the Sharepoint solution, but since I deploy to a folder below vti_bin Sharepoint should provide its context to the WCF. It seems to me like the AJAX call won't trigger Sharepoint to provide its context, in some sense it is anonymous.

At first I assumed the webservice itself was to blame since it would fail randomly when called from the browswer, but I think I sorted that issue out by installing a upgrade to Sharepoint Foundation 2010.

How can I make a AJAX call from the javascript/ a web service that accept AJAX calls from Javascript that allow the webservice to access the context of the user that has signed in to the Sharepoint site?

Was it helpful?

Solution

I did figure out the solution of this problem so I want to share my findings in case anyone else stumbled into this kind of a problem.

The basic problem is caused by fact that the Microsofts svc factories fail to add suiting access bindings for ajax calls. This mean the authenticiation magic of vti_bin folder will not happen. You get a running svc, but no Sharepoint context when you access it with ajax calls from your javascript even though ordinary access works fine.

You can fix the issue by extending the Factory to replace the bindings with the correct ones

 <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$"%>  
 <%@ServiceHost Language="C#" Debug="true"
 Service="Driftportalen.LvService.SuggestService, $SharePoint.Project.AssemblyFullName$"
Factory="Driftportalen.LvService.AjaxCompatibleRestServiceHostFactory,Driftportalen.LvService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab8de4d18e388c1f"
         %>

Implementation of the new Factory is as follows

public class AjaxCompatibleRestServiceHostFactory : Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new AjaxCompatibleRestServiceHost(serviceType, baseAddresses);
    }
}

And finally the actuall code to replace the bindings

public class AjaxCompatibleRestServiceHost : Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHost
{

    public AjaxCompatibleRestServiceHost(Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
    }

    protected override void OnOpening()
    {
        base.OnOpening();

        foreach (ServiceEndpoint endpoint in base.Description.Endpoints)
        {
            if (((endpoint.Binding != null) &&    (endpoint.Binding.CreateBindingElements().Find<WebMessageEncodingBindingElement>() != null)) && (endpoint.Behaviors.Find<WebScriptEnablingBehavior>() == null))
            {
                // try remove any previous behaviours
                while (endpoint.Behaviors.Count > 0)
                {
                    endpoint.Behaviors.RemoveAt(0);
                }
                endpoint.Behaviors.Add(new WebHttpBehavior());
            }

        }


        ServiceDebugBehavior debug = this.Description.Behaviors.Find<ServiceDebugBehavior>();
        // if not found - add behavior with setting turned on 
        if (debug == null)
        {
            this.Description.Behaviors.Add(
                new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
        }
        else
        {
            // make sure setting is turned ON    
            if (!debug.IncludeExceptionDetailInFaults)
            {
                debug.IncludeExceptionDetailInFaults = true;
            }
        }

        ServiceMetadataBehavior metadata =this.Description.Behaviors.Find<ServiceMetadataBehavior>();
        // if not found - add behavior with setting turned on 
        if (metadata == null)
        {
            this.Description.Behaviors.Add(
                new ServiceMetadataBehavior() { HttpGetEnabled = true });
        }
        else
        { 
            // make sure setting is turned ON    
            if (!metadata.HttpGetEnabled)
            {
                metadata.HttpGetEnabled = true;
            }
        }

    }
}

Possibly you might want to use WebScriptEnablingBehavior instead of WebHttpBehavior if you want the result wrapped when you get them back.

There are some additional things to consider. There is some indication on the net that lack of SP1 might also result in the lack of Sharepoint context, so verify that you have the latest service packs if you run into problems.

Finally in case you are building a REST service it might be tempting to use UriTemplate to get the URL structure you desire. Unfortunately at the time of this writing UriTemplate is not supported by Microsoft in this scenario so investigate this issue before you base your design on the existence of UriTemplate.

OTHER TIPS

I faced the same problem using SharePoint 2013. I haven't had the chance to try out your solution, because I read about another one which basically gets the current site and web id before elevating the privileges and then recreating them after. Something like this: https://sharepoint.stackexchange.com/questions/74205/spcontext-current-is-null-for-ihttphandler

HOWEVER, out of accident I discovered the following. After getting the ID of the current site like this: var currentSiteId = SPContext.Current.Site.ID;

The SPContext.Current is not anymore null later while inside the elevated privileges delegate! It seems that just accessing this property before elevating the privileges also makes it available after! Weird!!!!

So in fact the only line of code I added was the one you see above, without even using that variable later! I have the feeling that there is a catch somewhere!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top