Thomas Alva Edison's light bulb VBInfoZine Home
   An ordinary VB developer shares his own successes and failures
   FREE to registered subscribers.  

I hate SharePoint

How a tiny project turned into a big headache because of poor platform choice!?

The other day, my company has got a request from the CIO of one of our long-term, loyal customer. The CIO apparently fell in love with SharePoint Portal Server and he wanted to migrate one of his old ASP applications to SPPS. The ASP application was a simple presentation solution for a set of documents representing the company's bylaws. Not a rocket science here:
  • One MDB file containing one table with records describing the documents stored in the file system.
  • One spaghetti ASP page presenting the information to the user.
Pretty standard ASP stuff, I'd say. :-)

The publishing funcionality was not automated at all! When a new bylaw was issued, a dedicated employee had to copy the document onto the appropriate place in the file system and add a new record describing the document to the MDB database.

So I took over the task of converting the data and building the presentation as well as publishing feature working with the SPPS storage system.

I defined a simple interface representing the required document management functionality:

' This is our document server API.
Public Interface IDocumentServer

' Returns a DataTable with a list of folderUrl subfolders. folderUrl can be Nothing or an empty
' string. In this case the method should return a list of toplevel folders for given storage system.
' The DataTable should have the following schema (at least);
'    Url	(String) - URL of the subfolder
'    Name (String) - name of the subfolder
'    ParentUrl (String) - URL of the folder's parent 
Function GetFolders(ByVal folderUrl As String) As DataTable

' Returns a list of documents in the given folder. folderUrl must not be Nothing.
' The resultset should have the following schema: 
'  Url (String) - URL of the doc
'  FolderUrl (String) - URL of the folder containing the doc
'  Author (String) - document author
'  Description (String) - document description
'  MimeType (String) - MIME type of the document content (i.e. application/msword)
'  IssueDate (DateTime) - document issue date
'  MasterNumber (Integer) - unique document number 
'  SupplementNumber (Integer) - unique supplement number (if the doc is a supplement)
'
' The fields MasterNumber and SupplementNumber help to implement the concept of
' a supplement; master document and its supplements have the same MasterNumber. Master
' document has SupplementNumber = 0, the supplement documents have their SupplementNumber
' always > 0.
Function GetDocuments(ByVal folderUrl As String) As DataTable

' Creates a subfolder with the give name. If parentUrl is Nothing or String.Empty, the 
' method should create a toplevel (root) folder in the given storage.
' The method should return the URL of the newly created folder.
Function CreateFolder(ByVal parentUrl As String, ByVal name As String) As String

' Removes the given folder.
Sub DeleteFolder(ByVal url As String)

' Creates a document with the given attributes and returns its URL.
' If the masterNumber argument is <> 0, the document should be treated as an existing
' master document supplement and the server should assign the document a 
' unique SupplementNumber.
' The pointer in the content stream must be set to the beginning of the document data.
Function CreateDocument( _
 ByVal folderUrl As String, _
 ByVal author As String, _
 ByVal description As String, _
 ByVal issueDate As DateTime, _
 ByVal masterNumber As Integer, _
 ByVal supplementNumber As Integer, _
 ByVal mimeType As String, _
 ByVal fileTitle As String, _
 ByVal content As System.IO.Stream) As String

' Returns a stream with the content of the given document.
Function GetDocumentContent(ByVal documentUrl As String, ByRef mimeType As String) As 
System.IO.Stream

End Interface

Two classes implemented the interface:
  • The ExtantDocumentServer class implemented the interface over the existing MDB + file system storage.
  • The SppsDocumentServer class implemented the interface over the SPPS web storage system using the PKMCDO library.
Both implementations were contained within one monolithic ASP.NET application along with a webform for browsing a document store and a webform for publishing documents into the store. The actual IDocumentServer implementation was chosen at runtime based on settings in the application's web.config file.

I've also implemented a Migrate.aspx page, which instantiated both the ExtantDocumentServer and the SppsDocumentServer and copied the documents from one storage type to the other:

Migrate.aspx.vb

After a couple of days of development and testing I took the piece to deploy and test it at the customer's site. The customer has one dedicated intranet server INTRANET (that's exactly the server's name :-)). The SPPS thing was installed on another server; let's call it NT5.

On the INTRANET machine, I created an IIS vroot for the application and entered the appropriate web.config settings. On the NT5 machine I created the required document profile in the SPPS storage. After that I fired up IE eager to see my creation in action. Guess what? The browser was "opening the page" for a couple of minutes and then it displayed a Server Application Unavailable message to me. The INTRANET event log contained a EventID 1003 meaning the aspnet_wp.exe process was recycled because it was deadlocked.

To make a long story short, the reason for this was in the PKMCDO library. The KnowledgeFolder class exposes a PromptToAuthenticate property, which defaults to True. The INTRANET's local ASPNET account (under which the ASPX application was running) had no permissions on the NT5 machine at all. Therefore, when the account called the KnowledgeFolder.DataSource.Open method, PKMCDO prompted for credentials with a dialog box, which nobody could dismiss (the dialog box was "displayed" on the aspnet_wp.exe process' own window station, which nobody could see).

OK, I hear you. This was entirely my fault, because I didn't study the PKMCDO API thoroughly.

I fixed the issue by always assigning False to the KnowledgeFolder.PromptToAuthenticate property and by using explicit credentials (with sufficient permissions to open and manipulate the SPPS store) in a call to the KnowledgeFolder.DataSource.Open method.

I've tested the browsing and publishing feature and it worked great.

At this point it is important to note that I was running the browser under an account that was member of the Domain Admins global group. When I logged onto my client machine as a "normal" domain user (i.e. Domain User member only), the browsing functionality worked well, but when I've tried to publish a document, I've got a confusing "The directory name is invalid" error:

System.Runtime.InteropServices.COMException (0x8007010B): The directory name is invalid.
  at PKMCDO.IDataSource.SaveTo(String SourceURL, Object ActiveConnection,
  ConnectModeEnum Mode, RecordCreateOptionsEnum CreateOptions, 
  RecordOpenOptionsEnum Options, String UserName, String Password)
I've tried almost everything imaginable to make the publishing work for domain users, but it was of no avail. No combination of the IIS / COM+ application identity, web.config authorization / impersonation settings or SPPS workspace settings worked for domain users. It seems that even if SPPS allows you to pass explicit user credentials to manipulate its storage, the browser's user access token is still used for some internal processing, which results in the "directory name is invalid" error. (Auditing didn't help, because the SPPS web storage system apparently doesn't generate security events).

So the end result is:

When the browser user is member of the Domain Admins group, publishing works. Otherwise it doesn't. Period.

For the customer, it wasn't acceptable for the bylaw documents publisher to be member of the Domain Admins global group, so I've had to implement a workaround:
  • I've created a separate assembly containing just the IDocumentServer interface definition.
  • I've cut the SppsDocumentServer implementation from the ASP.NET application and placed it in its own assembly (the existing documents were migrated already, so I didn't bother with the ExtantDocumentServer).
These assemblies were then deployed locally on the SPPS server machine (NT5) and the ASP.NET application sitting on the INTRANET machine used .NET remoting to communicate with the SppsDocumentServer. Finally, everything worked well for both domain administrators as well as "normal" users, including publishing!

I was exhausted!

The worst thing is that I still don't know why SPPS was returning the "directory name is invalid" error.

Well, they probably know why they abandoned the infamous web storage system and why the new SPPS version 2 is based on SQL Server and .NET framework.

What do you think?

© Palo Mraz, Monday, July 28, 2003

Links

http://www.microsoft.com/sharepoint/server/ - The official SPPS site.

http://msdn.microsoft.com/library/en-us/spssdk/html/_adodb_connection_object.asp - I should have read this article on PKMCDO's PromptToAuthenticate handling. :-)

http://msdn.microsoft.com/library/en-us/dnbda/html/DBGch03.asp - Great article on debugging ASP.NET applications, including description of the 1003 "deadlock" event ID.

http://www.sharepointserver.com/forum/topic.asp?TOPIC_ID=377 - Another "nice" SPPS endeavor.

http://msdn.microsoft.com/msdnmag/issues/01/08/Interop/ - Nice article about .NET - COM interoperability (a bit outdated, but still valid).

 ©2003-2008 Palo Mraz. All Rights Reserved.   See my 'new browser window' policy