Writing a Project Data Service Extender
Writing a Project Data Service Extender
This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.
Summary
This article describes how Project Data Service (PDS) Extenders allow you to easily develop solutions with Microsoft® Project Server.
- Download The Project Data Service Extender template is available from the Microsoft Download Center.
Introduction
This article describes how Project Data Service (PDS) Extenders allow you to easily develop solutions with Microsoft Project Server. The programming interface and procedure for registering PDS Extenders are described in detail. You can also download the file PDSxtndr.exe in the MSDN Downloads; the file contains sample code that can act as a template for PDS Extender implementations. A more detailed example of a PDS Extender can be found in the Project Managers PDS Extender article in the Microsoft Project Software Development Kit (SDK).
Project Data Service Extenders
A PDS Extender allows you to customize Microsoft Project Server by using the standard PDS interface. Because you can customize within the standard PDS interface, making calls to your PDS Extender is no different than making method calls to the PDS itself, and method calls take the form of XML requests. By using the same interface you also get the additional benefit of Microsoft Project Server security, which ensures that project data stored in the database remains consistent. For more information on Microsoft Project Server security, see The Project Data Service and Microsoft Project Server Security Architecture article in the Microsoft Project SDK.
When the PDS receives a request that isn't part of its standard API, it checks to see if any PDS Extenders are registered. If a PDS Extender is found, the request is forwarded to the PDS Extender. If the PDS Extender can handle the request, it returns a response to the PDS, which, in turn, returns that response to the client. From the client's perspective, it appears that the PDS itself services the method call. If the PDS Extender does not recognize the request, the request is passed to another PDS Extender, if one exists. There can be a maximum of 100 registered PDS Extenders. If no PDS Extender can handle the request, the PDS returns an error to the client indicating that the method is not recognized. Figure 1 summarizes how the PDS handles requests to a PDS Extender.
Figure 1. The PDS forwards unrecognized requests to PDS Extenders
PDS Extender Interface
A PDS Extender is implemented as COM DLL. This DLL must expose one public method, named XMLRequest, which has the following signature in Microsoft Visual Basic®:
Public Function XMLRequest( _
ByVal sXML As String, _
ByVal sUser As String, _
ByVal sConnect As String, _
ByVal lDBType As Long, _
ByRef nHandled As Integer) _
As String
The sXML, sUser, sConnect, and lDBType parameters are provided by the PDS. The PDS Extender sets the nHandled parameter on return to the PDS. The return value of XMLRequest is the XML response of the method.
The sXML parameter contains the XML request. For consistency, it is required that the request be formatted in the same way as other PDS API methods and enclosed in <REQUEST> tags, such as the following:
<Request>
<NameOfExtenderMethod>
<ExtenderParameter1>Value1</ExtenderParameter1>
<ExtenderParameter2>Value2</ExtenderParameter2>
...
</NameOfExtenderMethod>
</Request>
The primary task for an XMLRequest implementation is to parse the XML request and return an XML response. Additional information passed by the PDS may be useful. The sUser parameter is the authenticated Microsoft Project Server logon ID making the request. The sConnect parameter contains the connection string for connecting to the Microsoft Project Server database on behalf of the logged-on user. The lDBType parameter indicates the installation type of the Microsoft Project Server database and is always equal to 1, which means Microsoft SQL Server™.
The return value should also be of the same format as a PDS API method, which includes an HRESULT and Status tag. A non-zero HRESULT indicates a communication error. A non-zero Status indicates a logical error. The PDS reserves Status code values up to 10000. Therefore, it is recommended that extenders return values that are greater than 10000. The return value for XMLRequest takes the following form:
<Reply>
<HRESULT></HRESULT>
<Status></Status>
<NameOfExtenderMethod>
method-specific output
</NameOfExtenderMethod>
</Reply>
In addition to the XML response, the extender sets the nHandled parameter to 1 if it successfully handles the request. If the extender cannot handle the request, it sets nHandled to 0. This notifies the PDS that another extender may be able to handle the request. In this case, the PDS ignores the return value of XMLRequest.
Registering a PDS Extender
In addition to registering the COM DLL that implements the extender, a PDS Extender must be registered with the PDS. The location of this key is:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\10.0\MS Project _
\WebClient Server\<Virtual Directory>]
Each PDS Extender contains a string value indicating the ProgID of the COM DLL:
"PDSExtensionN"="PDSExtenderDllName.PublicInterfaceClass"
Where N represents an integer from 1 to 100.
PDS Extender Template
PDSExtension.vbp is a Visual Basic project containing code that acts as a template for creating PDS Extenders. It implements a simple method named Ping, which a client application calls using the following XML:
<Request>
<Ping/>
</Request>
The PDS Extender processes the request and returns the following XML response:
<Reply>
<HRESULT>0</HRESULT>
<STATUS>0</STATUS>
<Ping/>
</Reply>
For more information on making PDS API calls, see the PDS Reference for Microsoft Project Server in the Microsoft Project SDK.
Build the PDSExtension.vbp project and register the DLL on the Microsoft Project Server computer (for example, by using Regsvr32.exe). Within the following registry key:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\10.0\MS Project\WebClient Server\ProjectServer]
Create the following string-value key:
"PDSExtension1"="PDSExtension.CMain"
You should first examine the registry for an existing extender named PDSExtension1 so that you don't accidentally unregister it. If the extender PDSExtension1 exists, rename the key by changing the 1 to a 2 or to any other integer value up to 100.
Note that if you modify and replace an existing PDS Extender on the Microsoft Project Server computer, you will need to restart Internet Services to overwrite the DLL.
Examining the PDS Extender code reveals one function, which is XMLRequest:
Public Function XMLRequest( _
ByVal sXML As String, _
ByVal sUser As String, _
ByVal sConnect As String, _
ByVal lDBType As Long, _
ByRef nHandled As Integer) _
As String
Most of the work of XMLRequest is to parse the request. Parsing XML is easily done with the Microsoft XML Parser (MSXML). The function first checks to see if the XML request is valid:
'Checking if sXML variable is empty.
'If yes then error message is created...
If Len(sXML) = 0 Then
sXMLReply = "<Error>Request string is empty</Error>"
m_lStatus = STATUS_INVALID_REQUEST
GoTo PrepareReply
End If
'Checking if sXML variable is valid XML.
'If not, then error message is created...
Dim oXMLDocument As MSXML2.DOMDocument
Set oXMLDocument = New MSXML2.DOMDocument
oXMLDocument.loadXML sXML
If oXMLDocument.parseError.errorCode <> 0 Then
sXMLReply = "<Error>XML Parse error</Error>"
m_lStatus = STATUS_INVALID_REQUEST
GoTo PrepareReply
End If
Note that the constants for status codes are arbitrary values defined by the function implementer. If the XML request is valid, XMLRequest looks for the <Request> node:
'Loading up the root node which should be <Request> node
Dim oXMLRoot As MSXML2.IXMLDOMElement
Set oXMLRoot = oXMLDocument.firstChild
If oXMLRoot Is Nothing Then
sXMLReply = "<Error>Root node not found</Error>"
m_lStatus = STATUS_INVALID_REQUEST
GoTo PrepareReply
End If
'Looking for the <Request> node to proceed with further
'operations on the XML method request
Dim sNodeName As String
sNodeName = oXMLRoot.nodeName
Select Case sNodeName
'Found the <Request> node
Case "Request"
... look for recognized methods ...
Case Else
m_lStatus = STATUS_UNKNOWN_REQUEST
End Select
After XMLRequest has determined that this is a valid request, it parses for recognized methods. In this implementation, there is one valid method named Ping. Although the Ping method doesn't contain any parameters, this is also where you parse method parameters.
Dim oNode As MSXML2.IXMLDOMNode
Dim oChildNode As MSXML2.IXMLDOMNode
For Each oNode In oXMLRoot.childNodes
sNodeName = oNode.nodeName
result = oNode.hasChildNodes()
'There is a child node, now it has to see if it recognizes
'it. This loop is the essence of the PDS extender -
'add code to recognize the method(s) here...
Select Case sNodeName
'Recognizing child node <Ping>
Case "Ping"
sXMLReply = "<Ping/>"
nHandled = REQUEST_HANDLED
'If STATUS_UNKNOWN_REQUEST then assumption is that
'another registered extender can handle the request
Case Else
m_lStatus = STATUS_UNKNOWN_REQUEST
End Select
'If successful in recognizing first xml command then it tries
'to recognize all commands
Next oNode
If we got something other than a Ping request, the extender passes it on to the next extender:
If m_lStatus = STATUS_UNKNOWN_REQUEST Then
nHandled = REQUEST_NOT_HANDLED
sXMLReply = "<Error>Unknown request " & sNodeName & "</Error>"
GoTo PrepareReply
End If
Otherwise, XMLRequest returns a successful reply:
'Returns a reply with an HRESULT, a STATUS code
'and any other data that needs to be returned
PrepareReply:
XMLRequest = "<Reply><HRESULT>0</HRESULT><STATUS>" & _
Format$(m_lStatus, "0") & "</STATUS>" & sXMLReply & "</Reply>"
End Function