3

我想设置一个可以由 AJAX 从我们的 Sharepoint Foundation 2010 站点上的自定义 Web 部件加载的 javascript 调用的 WCF。为了简化 Javascript 端的处理,我想提供一个 Restful 服务,将 Json 返回给调用者。

问题是当我使用 AJAX 调用服务器时,SPContext.Current 为空。

我正在使用 svc 文件中的 MultipleBaseAddressWebServiceHostFactory 创建 web 服务

<%@ 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"
     %>

网络服务的合同是:

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

webservice的实现基本如下。

[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;
    }
}

我已经验证,如果我从 webbrowser 调用 web 服务,一切都按预期工作,并且我得到了正确的数据。

我使用以下 Ajax 调用

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

使用 Firebug,我已经验证从 javascript 调用了正确的 URL,并且我已经在服务器端验证了确实到达了正确的代码,但是 SPContext.Current 为空。

Sharepoint 服务器使用 Windows 和声明进行登录。这意味着实际的 WCF 将使用与 Sharepoint 解决方案不同的帐户运行,但由于我部署到 vti_bin 下面的文件夹,Sharepoint 应该将其上下文提供给 WCF。在我看来,AJAX 调用不会触发 Sharepoint 来提供其上下文,在某种意义上它是匿名的。

起初我认为 Web 服务本身是罪魁祸首,因为从浏览器调用它时它会随机失败,但我认为我通过安装 Sharepoint Foundation 2010 的升级解决了这个问题。

如何从 javascript/web 服务进行 AJAX 调用,该服务接受来自 Javascript 的 AJAX 调用,允许 web 服务访问已登录到 Sharepoint 站点的用户的上下文?

4

2 回答 2

5

我确实想出了这个问题的解决方案,所以我想分享我的发现,以防其他人偶然发现这种问题。

基本问题是由于 Microsoft 的 svc 工厂未能为 ajax 调用添加合适的访问绑定。这意味着 vti_bin 文件夹的身份验证魔法不会发生。你得到一个正在运行的 svc,但当你从你的 javascript 使用 ajax 调用访问它时没有 Sharepoint 上下文,即使普通访问工作正常。

您可以通过扩展工厂以将绑定替换为正确的绑定来解决此问题

 <%@ 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"
         %>

新工厂的实现如下

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

最后是替换绑定的实际代码

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;
            }
        }

    }
}

如果您希望在取回结果时包装结果,您可能希望使用 WebScriptEnablingBehavior 而不是 WebHttpBehavior。

还有一些额外的事情需要考虑。网上有一些迹象表明缺少 SP1 也可能导致缺少 Sharepoint 上下文,因此如果遇到问题,请验证您是否拥有最新的服务包。

最后,如果您正在构建 REST 服务,可能会很想使用 UriTemplate 来获取您想要的 URL 结构。不幸的是,在撰写本文时,Microsoft 在这种情况下不支持 UriTemplate,因此请在将设计基于 UriTemplate 的存在之前调查此问题。

于 2012-02-20T11:00:10.887 回答
4

我在使用 SharePoint 2013 时遇到了同样的问题。我没有机会尝试您的解决方案,因为我读到了另一个基本上在提升权限然后重新创建它们之前获取当前站点和 web id 的解决方案。像这样的东西: https ://sharepoint.stackexchange.com/questions/74205/spcontext-current-is-null-for-ihttphandler

但是,出于偶然,我发现了以下内容。像这样获取当前站点的 ID 后: var currentSiteId = SPContext.Current.Site.ID;

SPContext.Current 稍后在提升的权限委托中不再为空!似乎在提升权限之前访问此属性也使其在之后可用!奇怪的!!!!

所以实际上我添加的唯一一行代码就是您在上面看到的那一行,甚至后来都没有使用该变量!我有一种感觉,某处有一个陷阱!

于 2014-02-25T11:30:33.010 回答