Monday, October 31, 2011

Send a SharePoint 2010 document to an external web service using Records Center (II)

In the previous entry I explained the solution adopted to send documents from a SharePoint library to an external web service using Records Center, and the first of the 2 steps to do that:

  • Add a a new connection in the configuration in Central Admin –> General Application Settings –> Configure send to connections.
  • Create a new webservice in a different Web Application (normal ASP.NET service).

In this new post I will explain how to override the standard service OfficialFile.asmx to copy documents to the Records Center, with new functionality to send to our own external service. Following one of the steps that Wictor Wilén described in this post, I managed to implement this same MOSS web service.

First of all, create a new VS2010 project of type ASP.NET Web Service application.

image

Then, using the tool wsdl.exe from the .NET Framework SDK we can get an interface based on the WSDL definition of the OfficialFile.asmx service. So to ensure that we have the latest web service interface, let’s execute the following command to get the interface we will add to our code.

wsdl /out:IRecordsRepositorySoap.cs /n:TestRecordsCenter_WebRole /serverinterface 
http://<serverUrl>/<recordsCenterUrl>/_vti_bin/OfficialFile.asmx?WSDL

That command will create the file IRecordsRepositorySoap.cs containing the definition of the web service. Someting with the following structure:


/// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
    [System.Web.Services.WebServiceBindingAttribute(Name="RecordsRepositorySoap", Namespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/")]
    public interface IRecordsRepositorySoap {
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/SubmitFile", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string SubmitFile([System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] fileToSubmit, [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] RecordsRepositoryProperty[] properties, string recordRouting, string sourceUrl, string userName);
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetFinalRoutingDes" +
            "tinationFolderUrl", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        DocumentRoutingResult GetFinalRoutingDestinationFolderUrl([System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] RecordsRepositoryProperty[] properties, string contentTypeName, string originalSaveLocation);
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetServerInfo", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetServerInfo();
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetRecordRoutingCo" +
            "llection", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetRecordRoutingCollection();
       
        /// <remarks/>
        [System.Web.Services.WebMethodAttribute()]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://schemas.microsoft.com/sharepoint/soap/recordsrepository/GetRecordRouting", RequestNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", ResponseNamespace="http://schemas.microsoft.com/sharepoint/soap/recordsrepository/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        string GetRecordRouting(string recordRouting);
    }

Now we can use this interface as a Service Reference, so add this file directly to the solution to implement at least these five methods. Then create a new service .asmx (it is not necessary to call it OfficialFile.asmx) and the final solution will look to something like this:


image


Keep in mind that it is necessary to implement and override the five methods of the Service reference. One important point is that these methods can return anything (“hello World”, i.e., but obviously the connection will not work properly.´), except the method GetServerInfo(), which MUST return a string containing an XML string with the following format:


<ServerInfo><ServerType>Test Server</ServerType><ServerVersion>1.0</ServerVersion></ServerInfo>

Where ServerType and ServerInfo can be anything. No official documentation was found with this info, and I could only find something about that in Wictor’s blog.


The OfficialFile.asmx file will finally look like this (there are a lot of discussions in different forums about the correct way to declare this service, but this should work 100%). Keep in mind that you may need to deploy the built DLL to the GAC.


<%@ WebService Language="C#" Class="OfficialFile, SharePointToHYDMediaService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f16d3448d010e7e6" %>

And the code-behind (except using sentences):


[WebService(Namespace = "http://schemas.microsoft.com/sharepoint/soap/recordsrepository/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class OfficialFile : System.Web.Services.WebService, CustomRecordsCenter_WebRole.IRecordsRepositorySoap
{
   
    public string SubmitFile(byte[] fileToSubmit, RecordsRepositoryProperty[] properties, string recordRouting, string sourceUrl, string userName)
    {
        return "<ResultCode>Hello World</ResultCode>";
    }
    public string GetServerInfo()
    {
        return "<ServerInfo><ServerType>Test Server</ServerType><ServerVersion>1.0</ServerVersion></ServerInfo>";
    }
    public string GetRecordRoutingCollection()
    {
        return "Hello World";
    }
    public string GetRecordRouting(string recordRouting)
    {
        return "Hello World";
    }
    public DocumentRoutingResult GetFinalRoutingDestinationFolderUrl(RecordsRepositoryProperty[] properties, string contentTypeName, string originalSaveLocation)
    {
        return null;
    }
}

The next step consists on deploy the webservice to a different web application that you can create directly in the IIS Manager. This application needs to run under the SharePoint application pool to work properly, so configure this when you create the WebSite clicking the button Select application pool.


image 


Publish your service to the web application, and cross fingers.


image


If everything went fine, you should be able to browse your custom web service.


image


Finally, add the new service (that remember is based on the original OfficialFile.asmx) as a Service Connection as described in the part I of this post. Clicking in the link to test the service, should show a confirmation popup.


image


The custom Service Connection to send documents to an external web service is up and running, and although making a real test will throw an exception (basically because all the “return ‘Hello World’”) a debugger can be attached to the w3wp.exe process to check that it is working with the custom web service.

Monday, October 17, 2011

Send a SharePoint 2010 document to an external web service using Records Center (I)

One of the main demands that a SharePoint customer -that has just jumped into the platform- usually makes is “how can I connect SharePoint to send/receive documents from SAP/Documentum/MyOwnRepository/whatever?”.

To receive documents or just any kind of data with associated metadata and, i.e., upload these items to a List, I would say that one of the best options is to use standard Web Services. If the standard services are not enough, then we can create our own service and then use the Object Model to build that extra-functionality.

To send documents (again, with or without associated metadata), the chosen solution in out last customer was use some Records Management functionality as an intermediate step to send the document to the customer’s system. The Records Center is an Enterprise site template available since SharePoint 2007 which functionality has been slightly modified for 2010. The cool point of this solution is that the document can be sent to the Records Center either using the contextual menu (Send To –> “Name of the external connection”), or with a custom Activity in a workflow (to send batches of documents)

These are the steps to implement this solution:

  • Add a a new connection in the configuration in Central Admin –> General Application Settings –> Configure send to connections.
  • Create a new webservice in a different Web Application (normal ASP.NET service). This will hold the bridge connection between SharePoint and the external service.

So far, it seems a simple and fast to implement solution, but there are a couple of “special issues features” that may give you headaches for days.

In this first post I am going to cover the connection configuration step and just take a look around to the Records Center, to understand how it works, and what are we going to do.

Let’s begin going to Central Admin –> General Application Settings –> Configure send to connections and add a new connection. You will need to use the service OfficialFile.asmx, as this service implements the methods to copy files into the Records Center. You can find thousands of resources about this service, but the official specification is here: http://download.microsoft.com/download/8/5/8/858F2155-D48D-4C68-9205-29460FD7698F/[MS-OFFICIALFILE].pdf

To set up a new connection using this service, the URL should have the format http://<server>/<recordsCenterUrl>/_vti_bin/officialfile.asmx. Notice that you should have created a Records Center site in order to use this functionality.

image

Now you should be able to see a new link in the contextual menu of the documents libraries. This link wil execute the action specified in the connection settings (copy to Records Center, i.e).

image

Click the “Official File” link in the contextual menu will copy the document to the Records Center using the service OfficialFile.asmx, and that is exactly what we want to do with our own service. Now that we –barely- know how it is going to work, we can create our own service based in the OfficialFile.asmx, and override it to provide a connection to the external service. I personally found this solution great because it is going to use a connection to an external system inside SharePoint context. My point: we are gonna have everything under control.

In the next post I will implement the second part of the solution. That is, develope a “Hello World” web service and give a couple of useful tips to make it work.