September 2007 - Posts
Working with the Web Services in WSSv2 and v3 is an exercise in sheer frustration. I've written before about serious and glaring omissions in the Web Services API that Sharepoint exposes, but it's not only the things missing, it's the lack of consistency in the APIs that ARE implemented that's annoying.
For most of the APIs, the return value for most methods is an XmlNode, usually documented pretty well in the SDK. It means we have to fiddle with XML to extract data, but it's not so bad. For instance the Lists WS's GetListCollection() returns a known, documented, schema-linked XML containing many properties for a given list.
For some reason, though, the SiteData web service chooses to return data as typed objects instead - oddly named objects at that (_sList, _sWebWithMetadata[], and so forth), but only for SOME data. The GetSite() method, for instance, will return a typed metadata object for the site metadata and the list of Lists, but a completely untyped STRING containing XML data for the list of groups and group members.
Futhermore, these XML snippets are sometimes undocumented, and you have to try it out to see what you get. What you get, sometimes, is the same XML you would get from a call to a different web service, but stripped of the namespace - so even though it's the same structure, you can't use the same code to parse both.
Similarly the GetListCollection() method in SiteData. It returns a typed array of _sList objects which map to the XML that's returned from the ListsWS method, only for some reason, some oft the XML attributes are NOT mapped, and are inaccessible. So if I want to see whether a list is Hidden or not, I am unable to use the typed objects and have to resort to the loose XML manipulation.
Seriously, these rants are adding up. Does anyone know who's in charge of the web services for Sharepoint and why they're so badly designed? No consistency, missing functionality, bad documentation - what's going on here? This isn't v1, either, but very little seems to have changed since the original documentation for WSS v2 in 2003.
A few days ago I wrote of a GC hole I accidently created in my C++/CLI code (with thanks to commenter Nish for the name of the phenomenon). I had the following code snippet set inside a private function:
pin_ptr<Byte> pinnedBuffer = &data[0];
char* buffer = (char*)pinnedBuffer;
Because the pin_ptr went out of scope when the function ended, I had a pointer to a memory location that was quickly moved by the GC, and got me a whole buffer full of garbage. To avoid that, I moved those two lines of code to my main method and pinned it throughout that method.
I thought that was the end of that, but today I noticed that I was still getting garbage occasionally in my buffer. This stumped me for a bit - it seemed like the pin_ptr wasn't doing its job - until I noticed how my C++ code looked:
public void DoStuff (array<Byte>^ data)
char* buffer;
if (data.Length > 0)
{
pin_ptr<Byte> pinnedBuffer = &data[0];
buffer = (char*)pinnedBuffer;
}
// Now do stuff
For C++ programmers this is probably obvious. For me and other C# devs, not so much. C++'s scoping rules say that when the if block terminates, the pin_ptr<Byte> goes out of scope immediately. This causes the pinned memory to be released, and allows the GC to move my array. Meanwhile my unmanaged code goes over the buffer and reads data. If I'm lucky, the GC won't start until I'm finished and all is well. If the GC IS active, I can either get garbage data (whatever is now pointed to by buffer) or, even worse, an access violation if I now point to protected memory.
In my current project, we have a windows service that we are developing and debugging, which involves a lot of installing/uninstalling the service. One common problem when uninstalling a service is that while the uninstallation is successful, you still see the service listed in the Services console(services.msc). If you try to start it, stop it or uninstall it again (using installutil.exe or sc delete) you get an uninformative "This service has been marked for deletion".
The KB article about this problem suggests you restart the computer, which is pretty much overkill. Sure, it'll work, but you'll never find out what caused it in the first place. Turns out it's a pretty simple affair: just make sure you close the Services console, which apparently holds a handle of some sort to it. You don't have to do it before you uninstall. The minute you close the console, all services marked for deletion will be deleted, and all will be well.
When doing it on a server, it's important to make sure you've closed all Services consoles on all active sessions. I've seen this error happen when no console was open, and it was fixed by running Task Manager and killing all instances of mmc.exe. I could have logged on to the other sessions and closed it gracefully, but I was lazy. Did the trick.
In my current code, I find myself accessing many Sharepoint web services. In one run of code I can access 5-6 different web services on dozens of different sites, not necessarily even on the same server. Given that I find myself writing a lot of boilerplate code looking like this:
using (SiteData siteDataWS = new SiteData())
{
siteDataWS.Credentials = CredentialsCache.DefaultNetworkCredentials;
siteDataWS.Url = currentSite.ToString() + "/_vti_bin/SiteData.asmx";
// Do something with the WS.
}
Seeing as this is quite wearisome, I've decided to go an genericize it. Given also that the name of the ASMX file for all Sharepoint services is the same as the name of the class to be instantiated, it gets even shorter:
1: public static WSType CreateWebService<WSType> (Uri siteUrl) where WSType : SoapHttpClientProtocol, new()
2: {
3: WSType webService = new WSType();
4: webService.Credentials = CredentialCache.DefaultNetworkCredentials;
5: string webServiceName = typeof(WSType).Name;
6: webService.Url = string.Format("{0}/_vti_bin/{1}.asmx", siteUrl, webServiceName);
7: return webService;
8: }
Line 1: Define a generic method that receives the type of Web Service and returns an instance of it. Note the generic constraints - the type must be a Web Service Proxy (SoapHttpClientProtocol) and must be instantiable. The last one is necessary for line 3 to compile.
Line 5: Get the name of the type - for our example above it would be "SiteData" - and build the URL based on the site URL and the web service name.
Now I can transform the above snippet to this:
using (SiteData siteDataWS = WebServiceHelper.CreateWebService<SiteData>(currentSite))
{
// Do something with the WS.
}
Drastic change? No, not really. But it keeps things clean.
Yes, I know I am far from the first to bemoan the atrocious behavior of Visual Studio 2005 when you press the F1 button, even accidently. The first time you do it - or other times, if it deems the contents sufficiently changed for some reason - you are treated to an obnoxious and uncancellable dialog saying Help is updating itself and showing an uninformative neverending progress bar.
Worse yet, this window, despite being non-modal (due to running in a different process, dexplorer.exe) still blocks the calling thread on devenv.exe, preventing me from working for up to 5 minutes(!) at a time. No Abort button, no way to tell it to skip this pointless operation - launching help from VS is simply a giant waste of time.
What I now discovered is that I can't even use the task manager to kill the dexplore.exe process, because Visual Studio actively monitors it and RELAUNCHES it, apparently from scratch, if I try to do so.
So tell me, what program manager decided that this particular feature, launching the Help window in a completely different process, was so amazingly important that it was allowed to take over my Visual Studio completely for whole minute while I just sat there and twiddled my thumbs?
Infuriating.
UPDATE:
Tools -> Customize -> Keyboard -> Show Commands Containing -> Help.F1Help -> Remove.
There. F1 is disabled. Peace is resumed.
More Posts