views:

647

answers:

2

I'm trying to build a web service that is sensitive to the subweb context (i.e., it exposes a WebMethod that needs to be able to touch lists in particular subwebs).

I have deployed the web app with the following files:

ISAPI/MyService.asmx
ISAPI/MyServiceWsdl.aspx
ISAPI/MyServiceDisco.aspx

The codebehind:

[WebService]
public class MyService : System.Web.Services.WebService
{
    [WebMethod]
    public ListSettings GetListSettings(string listName)
    {
        SPWeb site = SPControl.GetContextWeb(this.Context);
        SPList list = site.Lists[listName];

        return new ListSettings(list);
    }

    [WebMethod]
    public void UpdateListSettings(string listName, ListSettings settings)
    {
        SPWeb site = SPControl.GetContextWeb(this.Context);
        SPList list = site.Lists[listName];

        list.EnableFolderCreation = settings.EnableFolders;
        list.Update();
    }
}

public class ListSettings
{
    public ListSettings() { }

    internal ListSettings(SPList list)
    {
        EnableFolders = list.EnableFolderCreation;
    }

    public bool EnableFolders { get; set; }
}

This seems to work just fine at the root web of my site collection. However, when I make requests to http://moss/subweb/_vti_bin/MyService.asmx, and stop the web method on a breakpoint to inspect the HttpContext, I find that the request has been made to http://moss/_vti_bin/MyService.asmx.

I read a number of resources that describes the adjustments needed in the WSDL/disco files and in the spdisco.aspx file in ISAPI and made the adjustments I though I was supposed to make. Like so:

In MyServiceDisco.aspx:

<%@ Page Inherits="System.Web.UI.Page" Language="C#" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<% Response.ContentType = "text/xml"; %>

<discovery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/disco/"&gt;
  <contractRef ref=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request) + "?wsdl", '"'); %> 
            docRef=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request), '"'); %> 
            xmlns="http://schemas.xmlsoap.org/disco/scl/" />
  <soap address=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request), '"'); %> 
        xmlns:q1="http://tempuri.org/" binding="q1:MyServiceSoap" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
  <soap address=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request), '"'); %> 
        xmlns:q2="http://tempuri.org/" binding="q2:MyServiceSoap12" xmlns="http://schemas.xmlsoap.org/disco/soap/" />
</discovery>

At the top of MyServiceWsdl.aspx:

<%@ Page Inherits="System.Web.UI.Page" Language="C#" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<% Response.ContentType = "text/xml"; %>

And at the bottom:

<wsdl:service name="MyService">
    <wsdl:port name="MyServiceSoap" binding="tns:MyServiceSoap">
      <soap:address location=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request), '"'); %> />
    </wsdl:port>
    <wsdl:port name="MyServiceSoap12" binding="tns:MyServiceSoap12">
      <soap12:address location=<% SPEncode.WriteHtmlEncodeWithQuote(Response, SPWeb.OriginalBaseUrl(Request), '"'); %> />
    </wsdl:port>
  </wsdl:service>

And finally, the additions to spdisco.aspx:

<contractRef ref=<% SPEncode.WriteHtmlEncodeWithQuote(Response, spWeb.Url + "/_vti_bin/MyService.asmx?wsdl", '"'); %>
            docRef=<% SPEncode.WriteHtmlEncodeWithQuote(Response, spWeb.Url + "/_vti_bin/MyService.asmx", '"'); %> 
            xmlns="http://schemas.xmlsoap.org/disco/scl/" />
  <discoveryRef ref=<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(spWeb.Url + "/_vti_bin/MyService?disco"),Response.Output); %> 
                xmlns="http://schemas.xmlsoap.org/disco/" />

At long last, I thought I had done everything necessary to get the service working properly in every web in the site collection, but alas, it doesn't seem to be right. What did I forget to do?

Update: another tidbit of information is that trying to get the discovery XML leads to a "Not Found" sharepoint error. In other words, accessing http://moss/mysubsite/_vti_bin/MyService.asmx?disco leads to a Sharepoint "File Not Found" error.

Update: turns out there was a minor typo in MyServiceDisco.aspx that caused the "File Not Found" error. However, I fixed that and now the service is completely broken. Using the WCF testing client, I get an error message of "Server was unable to process request. ---> Object reference not set to an instance of an object" followed by a stack trace. From an application I'm working on, when I invoke the generated proxy class "MyServiceSoapClient", the response is "The remote server returned an error: NotFound".

Arrrgggh!

Update: Alright, the "object reference not set..." message was the result of a boneheaded mistake on my part. Things seem to be working now, except that the SPContext.Current.HttpRequestContext.Request.Path property always shows the url to the service under the root web instead of the current web. I've used Wireshark to make sure the client is POSTing to the correct url (and it is: http://moss/subweb/_vti_bin/MyService.asmx), I've used the ASP.NET tracing facility and haven't noticed anything awry (the request shows up in the trace with the correct url), and I've used the IIS tracing utility (logman.exe) which shows nothing unexpected. The only thing that doesn't seem right is the HttpRequest context once I hit the breakpoint in my service.

+1  A: 

What you have looks good to me. I have used this several times before without problem.

One difference that I noticed was my code for getting the SPWeb is slightly different (and simpler):

        // Get the current site based on our context
        // Note that this approach does _not_ require a Dispose of the SPWeb
        SPWeb site = SPContext.Current.Web;
Kirk Liemohn
And you're able to get the correct context in subwebs?
Ben Collins
A: 

Duuude... I've had enough issues like this with various pieces of SharePoint to be thoroughly convinced that it's really still a beta product.

At long last, I've discovered that using SPContext instead of SPControl to get the current web gives me what I need. All the MSDN documentation says to get the Web like this:

SPControl.GetContextWeb(this.Context);

And, if you disassemble the built-in web services, you'll find that this is also how the built-in web services get the current web. For my service, it didn't work, and this does:

SPContext.Current.Site.OpenWeb();

Why???? Why, Microsoft, why???!!!!

Ben Collins
You must have come across some out-of-date documentation. MS haven't been great with that. Blogs are the best source of SharePoint best practices.
Alex Angas