11.4. Managing the Document Revision Cycle

Referring back to the generic model of the document lifecycle, note that all documents follow the general pattern of creation/revision/publication. Unlike the creation phase, which happens only once, the revision phase occurs repeatedly until the document is ready for publication. During the revision phase, many different types of events can occur, depending on the state of the document metadata and the status of its content.

The SharePoint object model defines eight pairs of events that can occur during the document revision cycle. These are captured by the following elements of the SPItemEventReceiverType enumeration. Using these events, you can control all aspects of document revision.

  • ItemUpdated/Updating

  • ItemCheckedIn/CheckingIn

  • ItemCheckedOut/CheckingOut

  • ItemUncheckedOut/UncheckingOut

  • ItemAttachmentAdded/Adding

  • ItemAttachmentDeleted/Deleting

  • ItemFileMoved/Moving

  • ItemDeleted/Deleting

    We shall ignore the ItemFileConverted event and the ItemFileMoved/Moving event pair because they happen outside the document revision cycle.

During each stage of the revision cycle, you can use document metadata to analyze the current state of the document in terms of the problem domain and then update the metadata in the appropriate way. This revised metadata can be used to further constrain the behavior of the document or to control the actions of the people involved in editing it.

The following sections explore the construction of both generic and domain-specific tools for analyzing metadata to assist in answering the question "what happens next?" in the context of these events.

11.4.1. Building Custom Tools for Metadata Analysis

Sometimes it is useful to distinguish between metadata maintained by the system and custom metadata you defined within the problem domain. For the former, it's easy to create a library of reusable tools that can be used to quickly analyze system-defined properties. For instance, SharePoint can automatically track major and minor versions of each document in a library and can enforce moderation (approval) and checkout policies. Using the object model components associated with these properties, you can create components that perform useful functions, such as:

  • Determine if the major or minor version number exceeds a certain limit

  • Determine the current approval status of the document

  • Compute the average length of time a document remains checked out

  • Count the number of times a given user has checked in or reviewed a document

You can take the same approach for domain-specific metadata such as, in this example, where you might want to define a set of high-level methods for working with project proposals. These general and specific methods can make it much easier to build custom business rules. For example, you might need the following two rules:

  • Compare the estimated cost of a fixed-bid proposal to a predefined limit.

  • Compare the estimated man-hours of a time-and-materials proposal to a predefined minimum.

To support these rules, you can create a wrapper class for retrieving the bid amount and estimated hours from a set of project proposal properties. The properties are passed to the SPItemEventReceiver as described earlier. Listing 11-10 shows the ProjectProposal wrapper class that is derived from a generic wrapper for SharePoint list items.

Example 11.10. A project proposal wrapper class
/// <summary>
/// A wrapper class for a project proposal instance.
/// </summary>
class ProjectProposal : SharePointListItem
{
    /// <summary>
    /// Constructs a wrapper for the underlying list item.
    /// </summary>
    public ProjectProposal(SPListItem item):base(item)
    {
    }

    #region Property Value Accessors

    /// <summary>
    /// Retrieves the proposal type as reflected by the item properties.
    /// </summary>
    public static ProposalType GetProposalType(SPItemEventDataCollection properties)
    {
        ProposalType type = ProposalType.TimeAndMaterials;
        try {
            object value = properties[Strings._Field_ProposalType];
            string choice = value.ToString();
            if (choice.Equals(Strings._Choice_FixedBid))
                type = ProposalType.FixedBid;
        } catch {
        }
        return type;
    }

/// <summary>
    /// Retrieves the proposal bid amount.
    /// </summary
    public static decimal GetBidAmount(SPItemEventDataCollection properties)
    {
        decimal amount = 0M;
        try {
            object value = properties[Strings._Field_BidAmount];
            amount = Decimal.Parse(value.ToString());
        } catch {
        }
        return amount;
    }

    /// <summary>
    /// Retrieves the estimated person hours for a proposal.
    /// </summary>
    public static decimal GetEstimatedHours(SPItemEventDataCollection properties)
    {
        decimal amount = 0M;
        try {
            object value = properties[Strings._Field_EstimatedHours];
            amount = Decimal.Parse(value.ToString());
        } catch {
        }
        return amount;
    }
    #endregion
}

11.4.1.1. Ensuring Metadata Consistency between Revisions

Using your library of metadata analysis components, you can easily test for a range of conditions whenever a proposal document is updated. First, you perform the same metadata consistency check as when adding an item, rejecting the update from the ItemUpdating event receiver if a problem exists. Then, you use the project proposal wrapper to test for the two conditions you defined in the business rules.

[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemUpdating(SPItemEventProperties properties)
{
const decimal MinimumBid =5000M;
const decimal MinimumHours = 300M;
try {
    ValidateItemProperties(properties);
    switch (ProjectProposal.GetProposalType(
            properties.AfterProperties)
    {
    case ProposalType.FixedBid: {
        if (ProjectProposal.GetBidAmount(
            properties.AfterProperties) < MinimumBid) {
            throw new ApplicationException(

"Bids must be higher than " +
                    MinimumBid.ToString());
        }
        break;
    case ProposalType.TimeAndMaterials: {
        if (ProjectProposal.GetEstimatedHours(
            properties.AfterProperties) < MinimumHours) {
            throw new ApplicationException(
                "Hours must be greater than " +
                    MinimumHours.ToString());
        }
        break;
    }
} catch (Exception x) {
        properties.ErrorMessage = x.Message;
        properties.Cancel = true;
    }
}

At this point, you can perform other tests suitable for proposal documents. As more conditions are added, you can easily extend the framework to accommodate them by implementing the appropriate static methods on the ProjectProposal class and making the corresponding call from the event receiver.

11.4.1.2. Managing Checkin and Checkout

SharePoint supports enforced checkout for document libraries and lists. Setting the ForceCheckout property to true causes SharePoint to require that users check out a document before editing it. However, you may need to place additional constraints on document items. For example, you may need to control which users are allowed to check out a document, or keep track of which users checked out which documents, or calculate the average length of time a given user keeps documents checked out, and so on.

A good example of when this might be necessary is in a document library that is set up to enforce approval via the built-in _ModerationStatus field. After the document has been approved, you might wish to prevent users other than the approver from making further changes. You can do this by implementing a CheckingOut event receiver, checking the moderation status and then comparing the moderator to the current user. If they do not match and the document has been approved, then you abort the checkout with an appropriate message to the user.

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

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