12.2. The SharePoint Records Repository

Office SharePoint Server 2007 includes a default implementation of a records repository in the form of the Records Center site template. This template implements the Official File web service and provides all the core functionality needed to build records management solutions. The diagram in Figure 12-1 shows the architecture of a Records Center site.

Figure 12.1. Figure 12-1

A document library is configured to send documents to a central site based on the Records Center site template. Site administrators set this up at the document library level using the user interface. Alternatively, a document library can be configured automatically via the object model or as part of a feature activation. Users can manually select the Send To command from the document library menu to initiate the transfer of documents to the repository. Alternatively, documents can be transferred automatically by a workflow or other process.

SharePoint accesses the Official File web service exposed by the Records Center site to obtain a list of RecordRouting objects. RecordRouting objects specify the locations within the repository that can accept records of a given type. SharePoint maps the name of the content type associated with each document to the name of the routing record to determine which RecordRouting object to use.

SharePoint then submits the file to the repository. The repository loads any custom routers to give them a chance to perform special processing for a given file. Custom routers have the opportunity to examine the document content and metadata and may choose to reject the file, canceling the submission. The submitted file is moved into the appropriate storage location. Matching document metadata is automatically promoted to the document library level within the repository to support auditing and reporting. If a policy has been attached to the document library, then the policy is applied.

12.2.1. Using the Official File Web Service

The Official File web service provides a standardized means of managing a records repository and has been designed to support records management architectures that may use mechanisms other than those provided by the SharePoint platform. By implementing the Official File web service contract, any system can participate in the Office SharePoint Server records management scheme.

Figure 12-2 shows the methods and structure of the Official File web service as defined by the Office SharePoint Server object model.

Figure 12.2. Figure 12-2

The Official File web service can be used to implement a simple Windows Forms application that lets the user select local files and then save them to a Records Center site. The first step is to create a Records Center site to use for testing.

12.2.1.1. Setting Up the Records Center Site

To create a Records Center site, use the built-in Records Center site template installed by the Publishing feature. From the Site Actions menu of the top-level site, select Create Site and then select Records Center from the Enterprise tab in the Template Selection section of the New SharePoint Site page.

The Office SharePoint Server Publishing feature must be activated in order for the Records Center site template to be shown. For details about activating this feature, see Chapter 13.

After creating the Records Center site, add a document library to receive the submitted files. For this example, create a new document library named "Legal Agreements," accepting the default settings. Next, add a record routing type to the site by clicking the Configure Record Routing link on the home page to open the Record Routing custom list. Alternatively, you could select New New Item from the toolbar of the Record Routing list Web Part.

Figure 12-3 shows the item detail for the Contract record routing type. Note that the Contract record routing type refers to the Legal Agreements document library and specifies an additional alias for documents of type Agreement.

Figure 12-4 shows the home page of the resulting Records Center site.

Figure 12.3. Figure 12-3

Figure 12.4. Figure 12-4

12.2.1.2. Creating the Client Application

The OfficialFileWebServiceClient is a Windows Forms application that allows the user to specify a record routing type, a target repository, and a list of local files. Figure 12-5 shows the application user interface.

The user enters the repository URL and then presses the Connect button, which calls the web service to retrieve the list of routing record types available in the repository. The first step is to connect to the web service and then call the GetRecordRoutingCollection method to retrieve the record series names.

Connecting to the web service requires a Web Reference for the OfficialFile web service, which is exposed by all top-level sites in a SharePoint Server deployment at the following URL:

http://<server>/<portal>/_vti_bin/OfficialFile.asmx

Add the Web Reference by navigating to the appropriate portal in the URL field, as illustrated in Figure 12-6.

Adding the Web Reference creates a proxy class for communicating with the web service. In this example, the generated namespace is OfficialFileService and the proxy class is named RecordsRepository as defined by the web service.

Although the top-level site exposes the web service definition, the actual URL will be set dynamically at runtime. The top-level site is used only to generate the proxy class. This allows the user to work with different repositories. To enable this feature, the URL Behavior property of the generated proxy class must be set to Dynamic.

When the user presses the Connect button, a connection to the web service is established using the URL provided in the Repository URL text box and the credentials of the current user.

Next, you need to get the web service proxy and point it at the designated URL, as shown here:

OfficialFileService.RecordsRepository m_repository;
m_repository = new OfficialFileService.RecordsRepository();
m_repository.Credentials = System.Net.CredentialCache.DefaultCredentials;
m_repository.Url = repositoryUrl.Text;

Figure 12.5. Figure 12-5

Figure 12.6. Figure 12-6

To simplify the implementation of the client application, object data sources are used to bind data retrieved from the web service with the controls displayed in the user interface. The drop-down list of routing types is bound to a RoutingTypeList containing RoutingType objects, and the data grid is bound to an OfficialFileCollection containing OfficialFile objects. The RoutingType class is a simple wrapper for the routing type name. The OfficialFile class encapsulates the file path and the ability to submit the file to a records repository. The following code shows the RoutingTypeList and RoutingType utility classes.

class RoutingTypeList : List<RoutingType>
{
   public RoutingTypeList() { }
}

class RoutingType
{
   private string m_name;

   public string Name
   {
      get { return m_name; }
      set { m_name = value; }
   }

   public RoutingType(string name)
   {
      m_name = name;
   }
}

The GetRecordRoutingCollection method returns an XML string containing the available record routing types. For a SharePoint Records Center site, this is the list of items in the Record Routing list. Figure 12-7 shows the return value for the sample Records Center site.

Figure 12.7. Figure 12-7

If the connection is valid, the other controls are enabled and the drop-down list is populated with the available record routing types. This can be done easily using a simple XPath expression and an XPathNodeIterator, adding a new RoutingType wrapper object to the data source for each node as shown below.

string rTypes = m_repository.GetRecordRoutingCollection();
string expr = "/RecordRoutingCollection/RecordRouting/Name";

XPathDocument doc = new XPathDocument(new StringReader(rTypes));
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator iter = nav.Select(expr);

this.routingTypeListBindingSource.Clear();
while (iter.MoveNext())
{
this.routingTypeListBindingSource.Add(
   new RoutingType(iter.Current.Value));
}

The data grid is bound to an OfficialFileCollection containing OfficialFile objects. The OfficialFile class encapsulates the file path as well as the ability to submit an official file to a repository. When the user presses the Submit button, the selected files are processed, and the return value from the web service call is displayed in the grid automatically using the Result property of the OfficialFile class. Listing 12-1 shows the code in the mainform.cs file, which delegates the file submit logic to the OfficialFile class. Listing 12-2 shows the relevant code from officialfile.cs.

Example 12.1. listing 12-1
/// <summary>
/// Retrieves the list of files and submits them to the records repository.
/// </summary>
private void DoSubmitFiles()
{
   // Ensure there are files to work on.
   if (this.officialFileCollectionBindingSource.List.Count == 0)
   {
      MessageBox.Show("There are no files in the list.");
      return;
   }
   // Get the selected routing type.
   RoutingType routingType = routingTypeListBindingSource.Current
      as RoutingType;
   if (routingType == null)
   {
      MessageBox.Show("No routing type selected.");
      return;
   }

   // Process each file in the list.
   foreach (OfficialFile officialFile in
      officialFileCollectionBindingSource.List)
   {
      officialFile.Submit(m_repository, routingType);
   }

   // Refresh the UI.
   this.Refresh();
}

Example 12.2. listing 12-2
class OfficialFile
{
    private string m_result = "Not submitted";

    /// <summary>
    /// A text string representing the result of submitting the file.
    /// </summary>
    public string Result
    {
        get { return m_result; }
        set { m_result = value; }
    }

    private string m_fileName;

/// <summary>
    /// The base file name.
    /// </summary>
    public string FileName
    {
        get { return m_fileName; }
        set { m_fileName = value; }
    }

    private string m_filePath;

    /// <summary>
    /// The fully qualified file path.
    /// </summary>
    public string FilePath
    {
        get { return m_filePath; }
        set { m_filePath = value; }
    }

    /// <summary>
    /// Public constructor.
    /// </summary>
    public OfficialFile(string filePath)
    {
        this.FilePath = filePath;
        this.FileName = Path.GetFileName(filePath);
    }

    /// <summary>
    /// Submits the file using the specified routing type.
    /// </summary>
    public string Submit(OfficialFileService.RecordsRepository repository,
                         RoutingType routingType)
    {
        try
        {
            // Retrieve the file data.
            byte[] data = File.ReadAllBytes(this.FilePath);

            // Create a property array.
            OfficialFileService.RecordsRepositoryProperty[] props
               = new OfficialFileService.RecordsRepositoryProperty[1];

            // Specify some property data.
            props[0] = new OfficialFileWebServiceClient.OfficialFileService
                              .RecordsRepositoryProperty();
            props[0].Name = "OriginalPath";
            props[0].Type = "String";
            props[0].Value = this.FilePath;

            // Parse the xml result.
            string result = repository.SubmitFile( data, props,
               routingType.Name, this.FilePath,
               WindowsIdentity.GetCurrent().Name);

XPathNavigator nav =
               new XPathDocument(
                  new StringReader(
                     string.Format("<OFS>{0}</OFS>",result)
                     )).CreateNavigator();

            XPathNodeIterator iter = nav.Select("/OFS/ResultCode");
            this.Result = iter.MoveNext() ?
               iter.Current.Value : string.Empty;
        }
        catch (Exception x)
        {
            this.Result = x.Message;
        }
        return this.Result;
   }
}

12.2.2. Using the SharePoint Object Model

To illustrate how closely linked the object model is to the web services implementation, the SharePoint object model can be used to submit documents to a Records Center site. Figure 12-8 shows the objects used.

Figure 12.8. Figure 12-8

The OfficialFileCore class implements a static SubmitFile method, which in turn calls the web service associated with a given SharePoint web site. If the code will be executed on the SharePoint server, you can call this method directly to submit the specified file to the repository instead of calling the web service.

The example in Listing 12-3 (EnumRoutingTypes.cs) implements a custom STSADM command that enables SharePoint server administrators to enumerate the available routing types for a given records repository site.

Example 12.3. listing 12-3
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

using Microsoft.SharePoint.StsAdmin;
using Microsoft.Office.RecordsManagement.RecordsRepository;

namespace OfficialFileSTSADMCommand
{
    /// <summary>
    /// Implements a custom STSADM command to enumerate the
    /// record routing types in a records repository.
    /// </summary>
    class EnumRoutingTypes : ISPStsadmCommand
    {
        #region ISPStsadmCommand Members

        string ISPStsadmCommand.GetHelpMessage(string command)
        {
            string msg = "Enumerates the available routing types ";
            msg += "in a records repository site.";
            msg += "
-url	<url>		the url of the Records Center site";
            return msg;
        }

        int ISPStsadmCommand.Run(string command,
            System.Collections.Specialized.StringDictionary keyValues,
            out string output)
        {
            int result = 1;
            string sRepositoryUrl = string.Empty;
            try
            {
                // validate the arguments
                if (null == (sRepositoryUrl = keyValues["url"])
                          || sRepositoryUrl.Length == 0)
                    throw new ApplicationException("No url specified.");

                // open the website
                using (SPSite site = new SPSite(keyValues["url"]))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        // Enumerate the routing types using
                        // the RecordSeriesCollection wrapper.
                        RecordSeriesCollection routingTypes =
                           new RecordSeriesCollection(web);

                        if (null == routingTypes || routingTypes.Count == 0)
                        {
                            output = "No routing types were found.";
                        }
                        else
                        {
                            output = string.Format(
                            "Found {0} routing types:", routingTypes.Count);
                            for (int i=0; i < routingTypes.Count; i++)
                            {
                                RecordSeries routingType = routingTypes[i];
                                output += string.Format("
{0}
	{1}
",

routingType.Name,
                                   routingType.Description);
                            }
                        }
                    }
                }
            }
            catch (Exception x)
            {
                output = x.ToString();
            }
            return result;
        }

        #endregion
    }
}

12.2.3. Building a Custom Records Repository

What's interesting about this architecture is that it is not necessary to use a SharePoint site to implement a record repository that SharePoint document libraries can use. Simply by implementing the OfficialFile web service, any repository can be substituted. The steps to accomplish this are straightforward as illustrated in the following example, which implements an image repository for storing image files of various types.

Each image file is stored into a subfolder of the repository root folder, which is created dynamically on the local file system. The subfolder name is the image type as specified by the file extension. Within that folder, the file is stored into a secondary subfolder named according to the current month, day and year. So, for example, the file MyFile.GIF would be stored in C:ImageRepositoryGIF20070215 if sent to the repository on February 15, 2007.

12.2.3.1. Creating the Web Service Project

Create a new web service project in Visual Studio 2005 by selecting File New Web Site. From the New Web Site dialog box, choose the ASP.NET Web Service template and select File System from the Location drop-down. Although it is unnecessary for the service to work, you can rename the Service.cs and Service.asmx files to something more descriptive, like OfficialFileService.cs and OfficialFileService.asmx, respectively.

If you rename these files, be sure to update the CodeBehind and Class attributes in the .asmx file with the correct names.

Edit the code behind file (in this case, OfficialFileService.cs) and modify the WebService attribute so that the namespace matches that of the SharePoint OfficialFile web service. This step is important, because it enables SharePoint to talk to the web service. Without this namespace directive, SharePoint will not recognize the web service as a valid implementation of the OfficialFile web service. The modified declaration should look like the following:

[
WebService( Name="ImageRepository",
Namespace = "http://schemas.microsoft.com/sharepoint/soap/recordsrepository/",
Description="An implementation of the OfficialFile Web Service to store image

files.")
]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class OfficialFileService : System.Web.Services.WebService {...}

12.2.3.2. Implementing the OfficialFile Web Service Methods

The OfficialFile web service consists of four methods: GetServerInfo, GetRecordRouting, GetRecordRoutingCollection, and SubmitFile. The return values are either a string describing an error condition or an XML string representing a data value or a result code. The data values are typically short strings, so for most implementations you can use simple string formatting to construct them.

To simplify the handling of known result codes, an enumeration can be used along with an associated wrapper class that implicitly converts the enumeration data type to a string. Listing 12-4 shows a portion of the code in officialfileservice.cs that handles the conversion of result codes.

Example 12.4. listing 12-4
/// <summary>
/// The expected result codes for the web service.
/// </summary>
enum ResultCodeType
{
    Success,
    MoreInformation,
    InvalidConfiguration,
    InvalidArgument,
    InvalidUser,
    NotFound,
    FileRejected,
    UnknownError
}

/// <summary>
/// Helper class for returning result codes.
/// </summary>
private class ResultCode
{

    string m_value = null;
    ResultCodeType m_code=ResultCodeType.UnknownError;
    const string fmtResultCode = @"<ResultCode>{0}</ResultCode>";

    public ResultCode(ResultCodeType code)
    {
        m_code = code;
    }

    public ResultCode(string value)
    {
        m_value = value;
    }

    public override string  ToString()
    {
        return string.Format(fmtResultCode,

m_value == null ? m_code.ToString() : m_value);
    }

    public static implicit operator string(ResultCode code)
    {
        return code.ToString();
    }
}

12.2.3.2.1. GetServerInfo

The GetServerInfo method returns information on an implementation of the OfficialFile web service. The return value is an XML string matching the following schema:

<ServerInfo>
  <ServerType>serverType</ServerType>
  <ServerVersion>serverVersion</ServerVersion>
</ServerInfo>

For the image repository, the following code will suffice:

/// <summary>
/// Retrieves the server information.
/// </summary>
[WebMethod]
public string GetServerInfo()
{
    try
    {
        StringBuilder sb = new StringBuilder("<ServerInfo>");
        sb.AppendFormat("<ServerType>{0}</ServerType>", "ImageRepository");
        sb.AppendFormat("<ServerVersion>{0}</ServerVersion>",
            Assembly.GetExecutingAssembly().GetName().Version.ToString());
        sb.Append("</ServerInfo>");
        return sb.ToString();
    }
    catch
    {
    }
    // failed for some reason
    return new ResultCode(ResultCodeType.UnknownError);
}

12.2.3.2.2. GetRecordRouting

The GetRecordRouting method gets the properties of a specified record routing type. For this sample image repository implementation, a list of recognized image types is hard-coded using an enumeration. The recognized image types are declared as follows:

/// <summary>
/// The recognized image types.
/// </summary>
enum ImageType
{

[Description("GIF Image File")]
    GIF,
    [Description("JPEG Image File")]
    JPG,
    [Description("TIFF Image File")]
    TIF,
    [Description("Portable Network Graphics File")]
    PNG
}
A useful technique for dealing with enumerations leverages the DescriptionAttribute (declared in System.ComponentModel) along with a wrapper class for retrieving any description that is attached to an enumeration element. For example:

/// <summary>
/// Utility class for extracting descriptions from enumerators.
/// </summary>
/// <remarks>Uses reflection to extract the DescriptionAttribute
/// from an enumerator. Returns the enumerator type name
/// if no DescriptionAttribute is found.
/// </remarks>
public class EnumDescription
{
    private string m_str = "";
    ///<summary>Constructor from an enum element.</summary>
    public EnumDescription(Enum value)
    {
        DescriptionAttribute attrib =
            Attribute.GetCustomAttribute(
               value.GetType().GetField(value.ToString()),
               typeof(DescriptionAttribute))
                   as DescriptionAttribute;
        m_str = (attrib == null) ?
           value.ToString() : attrib.Description;
    }
    // support for implicit string conversion
    public static implicit operator string(EnumDescription s)
    {
        return s.m_str;
    }
    // support for explicit string conversion
    public override string ToString()
    {
        return m_str;
    }
}

The GetRecordRouting method can be implemented as follows:

/// <summary>
/// Returns the routing information for a given record type.
/// </summary>
[WebMethod]
public string GetRecordRouting(string recordRouting) {
const string fmtRoutingType = @"<RecordRouting><Name>{0}</Name>" +

@"<Description>{1}</Description><Mappings/></RecordRouting>";
    try
    {
        ImageType t = (ImageType)Enum.Parse(
            typeof(ImageType), recordRouting);
        return String.Format(fmtRoutingType, t.ToString(),
            new EnumDescription(t));
    }
    catch
    {
    }
    return new ResultCode(ResultCodeType.InvalidConfiguration);
}

12.2.3.2.3. GetRecordRoutingCollection

The GetRecordRoutingCollection method retrieves the properties of all routing types of the repository. It is essentially a concatenation of the results from the GetRecordRouting method for each recognized routing type. Therefore, it can be implemented by iterating over the enumeration of routing types as follows:

/// <summary>
/// Returns the collection of recognized routing types.
/// </summary>
[WebMethod]
public string GetRecordRoutingCollection()
{
    StringBuilder sb = new StringBuilder("<RecordRoutingCollection>");
    try
    {
        foreach (ImageType t in Enum.GetValues(typeof(ImageType)))
        {
            sb.Append(GetRecordRouting(t.ToString()));
        }
        sb.Append("</RecordRoutingCollection>");
        return sb.ToString();
    }
    catch
    {
    }
    return new ResultCode(ResultCodeType.InvalidConfiguration);
}

12.2.3.2.4. SubmitFile

The SubmitFile method is the workhorse of the OfficialFile web service. It is responsible for validating the file, applying any policies and finally storing the file into the appropriate location within the repository.

The ImageRepository implementation presented here uses a simple hard-coded policy with the following characteristics:

  • Image files are stored according to file type.

  • The routing record type supplied by the caller is ignored.

  • The source URL is used to determine the file extension.

  • Only recognized file extensions are accepted.

To ensure that the web service works properly when called from a SharePoint site, the result codes should map to the expected result codes as described in the SharePoint SDK. The following table lists the SubmitFile result codes expected by the Office SharePoint Server implementation:

Result CodeDescription
SuccessThe file was submitted successfully.
MoreInformationRequired column metadata was not supplied. Additional information can be appended using a <ResultUrl>...</ResultUrl> tag to specify the URL of a form the user can use to enter the missing metadata fields.
InvalidConfigurationThe web service is not configured properly. This covers the case where the repository is offline or cannot be reached.
InvalidArgumentOne or more arguments were unexpected or were improperly formatted.
InvalidUserThe specified user credentials could not be authenticated or the user does not have the necessary permissions to save the file.
NotFoundThe specified user credentials could not be found, or the record routing type was not recognized.
FileRejectedThe file was rejected because of a policy violation or for other reasons, such as the file is too large or could not be saved to the specified location.
UnknownErrorSome other error occurred during processing.

Unlike the other methods, the SubmitFile method accepts an array of RecordsRepositoryProperty objects as a parameter. This object is used by callers to specify additional metadata for use by the service when storing the file. Although this property array is ignored by the Image Repository web service, the RecordsRepositoryProperty class must still be declared so that SharePoint sites can invoke the method properly.

[Serializable]
public struct RecordsRepositoryProperty
{
    public string Name;
    public string Other;
    public string Type;
    public string Value;
}

Listing 12-5 (officialfileservice.cs) shows the complete SubmitFile method implementation.

Example 12.5. listing 12-5
/// <summary>
/// Implements the submit method by storing the file into a subfolder
/// of the root (as specified in the web.config file) having the
/// same name as the routing type.
/// </summary>
/// <remarks>
/// This implementation ignores the specified record routing and
/// instead uses the file extension as the routing type. Only
/// recognized file extensions are accepted.
/// </remarks>
[WebMethod]
public string SubmitFile(byte[] fileToSubmit,
   RecordsRepositoryProperty[] properties, string recordRouting,
   string sourceUrl, string userName)
{
    try
    {
        try
        {
            // validate the source URL against the known image types
            // to ensure that the submitted file extension matches
            // a recognized file type
            string ext = Path.GetExtension(sourceUrl).ToUpper()
                            .Substring(1);  // skip the leading dot
            ImageType imageType = (ImageType)
               Enum.Parse(typeof(ImageType), ext);

            // modify the record routing type to match
            // the supplied file extension, ignoring the supplied
            // routing type
            recordRouting = ext;
        }
        catch
        {
            // reject unrecognized file extensions
            return new ResultCode(ResultCodeType.InvalidArgument);
        }

        // get the storage root path from the configuration file
        string path = ConfigurationManager.AppSettings["RepositoryRoot"];
        if (null == path || path.Length == 0) path = @"C:ImageRepository";

        // open the root folder
        DirectoryInfo storageRoot = Directory.CreateDirectory(path);
        // create a subfolder having the routing type name
        DirectoryInfo routingTypeFolder
           = storageRoot.CreateSubdirectory(recordRouting);

// create another subfolder having today's date
        string subpath = DateTime.Today.ToString("yyyyMMdd");
        DirectoryInfo submitFolder
          = routingTypeFolder.CreateSubdirectory(subpath);

        // create a new file having the specified name
        string filePath = Path.Combine(
           submitFolder.FullName, Path.GetFileName(sourceUrl));

        using (FileStream fs = File.Create(filePath))
        {
            // write the bits into the file
            fs.Write(fileToSubmit, 0, fileToSubmit.Length);
        }

        // return the success code
        return new ResultCode(ResultCodeType.Success);
    } catch
    {
    }
    // failed for some reason
    return new ResultCode(ResultCodeType.UnknownError);
}

12.2.3.3. Preparing for Testing from SharePoint

Testing the custom records repository from SharePoint requires a few additional steps to enable SharePoint to locate the repository and communicate with the web service. You need to configure a fixed port number and reconfigure SharePoint to use the custom repository.

12.2.3.3.1. Configuring a Fixed Port Number

The default settings for new web site projects in Visual Studio 2005 cause the ASP.NET Development server to generate a port dynamically each time the solution is run. These settings must be changed in order to configure SharePoint to use the service.

Right-click the project name in the Visual Studio Solution Explorer, and open the properties panel. Change the value of the Use dynamic ports property to false. Set the value of the port number property to any value you wish. You may need to rebuild the project before the property becomes editable.

12.2.3.3.2. Reconfiguring SharePoint to Use the Custom Repository

Open the SharePoint Central Administration web site and click the Records Center link from the External Service Connections section of the Application Management page. On the Configure Connection to Records Center page, check the Connect to a Records Center radio button and enter the URL of the custom web service, as shown in Figure 12-9.

During development testing, enter the fixed port number used by the ASP.NET Development server. You can obtain this URL by right-clicking on the ASP.NET Development Server task bar icon and then selecting the Show Details command, as shown in Figure 12-10.

Figure 12.9. Figure 12-9

Figure 12.10. Figure 12-10

After the SharePoint server has been configured, you can send files to the custom repository from any SharePoint document library or picture library, as illustrated in Figures 12-11 and 12-12. Figure 12-11 shows the image repository as an option on the Send To menu. Figure 12-12 shows the resulting image stored in the custom repository.

Figure 12.11. Figure 12-11

Figure 12.12. Figure 12-12

12.2.4. Creating a Custom Router

Office SharePoint Server 2007 employs an extensible routing framework to process files that are submitted to a Records Center site. This routing framework is very flexible and supports any number of custom routers, which implement the IRouter interface defined in the SharePoint API. The only limitation of this framework is that only one router can be defined for any given routing type.

The diagram in Figure 12-13 depicts the relationship between a router and the record series.

You create a custom router by implementing the IRouter interface, which declares a single method named OnSubmitFile. If the router has been associated with a record series type, this method is called whenever a file is submitted to a Records Center site, enabling the router to process the file contents and its metadata.

The OnSubmitFile method is called before the standard processing of the file occurs. Depending on the return value, the router can control or override the standard processing sequence. There are two success codes and one error code. Returning a RouterResult value of RejectFile indicates that the record repository should return an error value to the calling application. The values SuccessContinueProcessing and SuccessCancelFurtherProcessing are used to return a success value and then continue or discontinue processing of the document, respectively. Canceling further processing in this context means that the document is not stored into the repository, but the calling application still receives a success code. This means that the router can effectively filter the records going into the repository without notifying the client. De-duplication is one scenario in which this feature can be useful.

Figure 12.13. Figure 12-13

12.2.4.1. Implementing the IRouter Interface

The code shown in Listing 12-6 (EventLogRouter.cs) implements a custom router that appends an entry to the application event log for each document received in the repository.

Example 12.6. listing 12-6
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.RecordsManagement.RecordsRepository;

namespace ProSharePoint2007
{
    /// <summary>
    /// A custom router that writes an entry to the application
    /// event log for each file submitted to the record repository.
    /// </summary>

public class EventLogRouter : IRouter
    {
        RouterResult IRouter.OnSubmitFile(
            string recordSeries,
            string sourceUrl,
            string userName,
            ref byte[] fileToSubmit,
            ref RecordsRepositoryProperty[] properties,
            ref Microsoft.SharePoint.SPList destination,
            ref string resultDetails)
        {
            RouterResult result = RouterResult.SuccessContinueProcessing;

            // Setup the event source and log using the record series name.
            string source = "EventLogRouter";
            if (!EventLog.SourceExists(source))
                EventLog.CreateEventSource(source,"Records Center");

            // Set up the log entry.
            string fileName = Path.GetFileName(sourceUrl);
            string listName = (null==destination) ?
               "unknown" : destination.Title;

            EventLog.WriteEntry(source, "File '"
               + filename + "' was saved by user '"
               + username + "' to list '"
               + listName + "'",
               EventLogEntryType.Information);

            return result;
        }
    }
}

12.2.4.2. Adding the Custom Router to a Records Center Site

Custom routers are deployed using the SharePoint object model. After building the custom router assembly and copying it to the Global Assembly Cache, some additional installation code is required to make the router available to routing types declared within a Records Center site. This code is typically executed during feature activation but can also be executed from a console application or other code that runs directly on the SharePoint server.

Each Records Center site has an associated RecordSeriesCollection. This object maintains the list of routing types that the records repository understands. The RecordSeriesCollection object also maintains the list of custom routers that are available for use by individual routing types. The first step in making a custom router available is to add the router name, assembly, and class to the RecordSeriesCollection associated with a given Records Center site.

Listing 12-7 (eventlogrouter.cs) shows the code for a console application that adds or removes the EventLogRouter to or from the list of custom routers, which are available for use by routing types associated with a given Records Center site. The user supplies the site URL and a string indicating whether the router should be registered or unregistered.

Example 12.7. listing 12-7
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.RecordsManagement.RecordsRepository;
using Microsoft.SharePoint;

namespace EventLogRouterSetup
{
    /// <summary>
    /// Installs the custom event log router to a Records Center site.
    /// </summary>
    class Program
    {
        const string ROUTER_NAME = "Event Log Router";
        const string ROUTER_CLASS = "ProSharePoint2007.EventLogRouter";
        const string ROUTER_ASSEMBLY =
            "ProSharePoint2007.Chapter12.EventLogRouter"
            + ", Version=1.0.0.0, Culture=neutral"
            + ", PublicKeyToken=0b97b340d4a71524";

        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine(
                    "usage: EventLogRouterSetup "
                    + "[ register | unregister ] <url>"
                );
                return;
            }

            string command = args[0].ToUpper();
            string url = args[1];

            using (SPSite site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    try
                    {
                        RecordSeriesCollection coll
                           = new RecordSeriesCollection(web);
                        switch (command)
                        {
                            case "REGISTER":
                                coll.AddRouter(ROUTER_NAME,
                                     ROUTER_ASSEMBLY, ROUTER_CLASS);
                                break;
                            case "UNREGISTER":
                                coll.RemoveRouter(ROUTER_NAME);
                                break;
                            default:
                                Console.WriteLine("Unrecognized command");

break;
                        }
                    }
                    catch (Exception x)
                    {
                        Console.WriteLine(x.Message);
                    }
                }
            }
        }
    }
}

12.2.4.3. Activating the Custom Router for a Routing Type

After the router has been added to a Records Center site, it is available for use by routing types declared within that site. Adding the router to the site does not automatically activate it. Activating the router requires one additional step by the site administrator, which is performed through the SharePoint user interface.

When one or more custom routers have been added to a Records Center site, the Record Routing edit form is extended to include a drop-down list of router names, as shown in Figure 12-14.

Figure 12.14. Figure 12-14

Selecting an item from this list activates the custom router for that routing type. Only one router can be chosen for a given routing type. Having done so, any record of that type that is received will now be processed by the custom router. Figure 12-15 shows an entry made to the event log by the custom router when a contract is processed. Figure 12-16 shows the message detail.

Figure 12.15. Figure 12-15

Figure 12.16. Figure 12-16

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.93.168