11.5. Developing XML-Driven Document Management Solutions

Writing code whenever you want to change the policies associated with a content type can become a tedious operation. It would be better if you could define the more volatile aspects of the policy in an external file and then process that file during execution in order to determine if metadata has been provided consistently and completely. The problem is where should you put the file?

You could certainly put the file in a well-known location, but that would require too much knowledge of the operating environment. What you are really trying to do is separate the policy from your content type implementation so that you can change it more easily.

This section shows how to control the behavior of a class of documents using rules defined in an XML schema associated with a content type. After developing a simple project proposal management schema you create a default policy based on the schema and then attach it to the content type definition. As events are generated by SharePoint during the document revision cycle, you retrieve the policy from the content type and apply the policy.

11.5.1. Developing a Proposal Management Policy Schema

Referring back to the document lifecycle diagram, you can see how policies will affect the overall document revision cycle. Figure 11-9 shows the revision cycle with policies attached. You want to ensure that the total bid amount for fixed-bid proposals is never less than $5000. On the other hand for time-and-materials proposals, you want to reject any revision where the estimated hours are less than 300. And you want to be able to change these values without recoding the solution.

Figure 11.9. Figure 11-9

You can meet these needs by defining a simple schema to manage project proposals. The schema will distinguish between the two different types of proposals and provide elements for specifying acceptable bid amounts and estimated hours. In addition, you need a way to control what happens when the policy has been violated. This example shows how to display an error message to the user stating the condition and the expected values. Figure 11-10 depicts the proposal management policy schema shown in Listing 11-11.

Figure 11.10. Figure 11-10

Example 11.11. Proposal management policy schema
<xs:element name="ProposalManagementPolicy"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType>
    <xs:sequence>
        <xs:element name="FixedBid">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="BidAmount">
                        <xs:complexType>
                            <xs:sequence>
                                <xs:element
                                    name="ErrorMessage"
                                    type="xs:string" />
                            </xs:sequence>
                            <xs:attribute name="MinimumValue"
                                type="xs:decimal"
                                use="required" />
                        </xs:complexType>
                    </xs:element>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
        <xs:element name="TimeAndMaterials">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="EstimatedHours">

<xs:complexType>
                            <xs:sequence>
                                <xs:element
                                    name="ErrorMessage"
                                    type="xs:string" />
                            </xs:sequence>
                            <xs:attribute name="MinimumValue"
                                type="xs:decimal"
                                use="required" />
                        </xs:complexType>
                    </xs:element>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xs:sequence>
</xs:complexType>
</xs:element>

Using this schema definition, you can create a default policy for new project proposals. To make it easier for system administrators who will be editing these files, the example uses a simple value substitution scheme that looks for tokens in the error message text and replaces them with attribute values.

<ProposalManagementPolicy
        xmlns="http://schemas.johnholliday.net/proposalmanagementpolicy.xsd">
    <FixedBid>
        <BidAmount MinimumValue="5000">
            <ErrorMessage>The bid amount must be at least $$MinimumValue$$.</ErrorMessage>
        </BidAmount>
    </FixedBid>
    <TimeAndMaterials>
        <EstimatedHours MinimumValue="300">
            <ErrorMessage>The estimated hours must be at least $$MinimumValue$$.</ErrorMessage>
        </EstimatedHours>
    </TimeAndMaterials>
</ProposalManagementPolicy>

11.5.1.1. Attaching the Policy to a Content Type

In order for this to work, you need to associate the default policy with the project proposal content type. As it turns out, SharePoint has a built-in facility for doing this.

SharePoint maintains a collection of XML documents for each content type. These documents are accessible through the XmlDocuments Element in the content type definition. There are two ways to associate an XmlDocument with a content type: in the content type definition XML, used to provision the content type, and through the Windows SharePoint Services 3.0 object model. Which method to use depends on when the information is required and whether it will change.

If the information is static or is needed during the provisioning process, then the best option is to include it in the content type definition XML. On the other hand, if the information is dynamic, then the object model is probably the best choice. That way, you can drive other behaviors or control user interactions based on the contents of the associated XML document.

In this example, both conditions are true. You need the information from the default policy during the provisioning process so you can accept or reject new proposals, and you also need the policy information to be dynamic because you want to enable administrative users to modify the policy at any given time.

Adding the default policy to the content type definition XML requires that you insert an XmlDocuments element that contains a child XmlDocument element. You then place the contents of the custom XML document inside the XmlDocument element.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- _filecategory="ContentType" _filetype="Schema" _filename="contenttype.xml"
_uniqueid="cff96a1e-6a52-4462-a3d0-d01471b8bfef" -->
<ContentType ID="0x0101004a257cd7888d4e8baea35afcdfdea58c"
    Name="Project Proposal"
    Group="ProSharePoint2007"
    Description="A Content Type for Managing Project Proposals"
    Version="0">
    <FieldRefs>
        <FieldRef ID="{246D0907-637C-46b7-9AA0-0BB914DAA832}" Name="Author"/>
        <FieldRef ID="{76A81629-44D4-4ce1-8D4D-6D7EBCD885FC}" Name="Subject" />
        <FieldRef ID="{9DC4BA7E-6C50-4e24-9797-355131089A2E}" Name="ProposalType"
                                     Required="TRUE"/>
        <FieldRef ID="{24A18FDA-927A-4232-88AE-F713FFD3FBB4}" Name="EstimatedCost"/>
        <FieldRef ID="{41495470-9EA3-46e0-9A34-0E0C3DE1A445}" Name="BidAmount"/>
        <FieldRef ID="{D46C0900-5617-414c-97E5-E5626DBC1495}" Name="EstimatedHours"/>
        <FieldRef ID="{79368859-EAF8-4361-8A9D-C3CBD9C88697}" Name="HourlyRate"/>
        <FieldRef ID="{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}" Name="StartDate" />
        <FieldRef ID="{8A121252-85A9-443d-8217-A1B57020FADF}" Name="EndDate" />
        <FieldRef ID="{1DAB9B48-2D1A-47b3-878C-8E84F0D211BA}" Name="Status" />
        <FieldRef ID="{52578FC3-1F01-4f4d-B016-94CCBCF428CF}" Name="Comments" />
        <FieldRef ID="{B66E9B50-A28E-469b-B1A0-AF0E45486874}" Name="Keywords" />
    </FieldRefs>
    <XmlDocuments>
        <XmlDocument NamespaceURI="$Resources:ProposalManager,
ProposalManagementPolicyNamespace">
            <ProposalManagementPolicy>
                <FixedBid>
                    <BidAmount MinimumValue="5000">
                        <ErrorMessage>The bid amount must be at least $$MinimumValue$$.</ErrorMessage>
                    </BidAmount>
                </FixedBid>
                <TimeAndMaterials>
                    <EstimatedHours MinimumValue="50">
                        <ErrorMessage>The estimated hours must be at least $$MinimumValue$$.</ErrorMessage>
                    </EstimatedHours>
                </TimeAndMaterials>
            </ProposalManagementPolicy>
        </XmlDocument>
    </XmlDocuments>
</ContentType>
</Elements>

SharePoint uses the NamespaceURI attribute to index the collection of XML documents associated with a content type. This means that you must specify a namespace attribute for the XmlDocument element or you will not be able to locate the document when calling the SharePoint object model from your code.

The SharePoint SDK states that you can add any number of XML documents to the XmlDocuments collection, and that any XML document can be used as long as it is valid XML. In practice, I've found that not only must the XML be valid, but it must not cause an exception to be thrown while SharePoint is deserializing the XML fragment. For instance, if namespaces are used, they must resolve properly. Also, it must be an XML fragment and not an XML document. In other words, it must not include an xml directive. If an exception occurs, then SharePoint will silently consume the exception but will not completely load the content type. One side effect of this is that the content type will be created, but without the expected metadata fields. Similarly, if the NamespaceURI attribute is missing, SharePoint will abort the load.

11.5.1.2. Processing the Policy in Response to Events

The logical place to process policy files is in the synchronous event receiver methods ItemAdding and ItemUpdating, because you can cancel the operation and display an error message to the user. In order for this to work, you need to add a static method to the ProjectProposalType wrapper class.

The ApplyPolicy method extracts the XML document containing our default policy and then uses the XmlSerializer to deserialize it into a C# class. The ProposalManagementPolicy class is shown in Listing 11-12. By passing the SPItemEventProperties parameter along, you can then invoke the appropriate methods on the deserialized class to analyze the properties of the new or modified item in the context of the policy values. You use an extra parameter to specify the context in which the policy is to be applied (add, update, delete, etc.).

public static void ApplyPolicy(SPItemEventProperties properties,
                    ProposalManagementPolicy.PolicyContext context)
{
    ProposalManagementPolicy policy = null;
    ProjectProposalType proposalType = new ProjectProposalType();
    using (SPWeb web = properties.OpenWeb())
        {
            if (proposalType.Create(web) != null) {
                string xml = proposalType.GetXmlString(
                    Strings.ProposalManagementPolicyNamespace);
                policy = ProposalManagementPolicy.FromXml(xml);
            } else {
                policy = ProposalManagementPolicy.ReadFrom(
                    Strings.DefaultPolicy);
            }
        }
        if (policy != null) {
            policy.ApplyPolicy(properties.AfterProperties, context);
        }
}

Example 11.12. Proposal management policy class
using System;
using System.IO;
using System.Data;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.SharePoint;

namespace ProSharePoint2007
{
    [Serializable]
    [XmlType(AnonymousType=true)]
    [XmlRoot(Namespace="", IsNullable=false)]
    public class ProposalManagementPolicy {

        /// <summary>
        /// Specifies the context in which a given policy is being applied.
        /// </summary>
        public enum PolicyContext
        {
            Add,
            Update,
            CheckIn,
            CheckOut,
            Delete
        }

        /// <summary>
        /// Loads a ProposalManagementPolicy object from a file.
        /// </summary>
        public static ProposalManagementPolicy ReadFrom(string fileName)
        {
            XmlSerializer ser = new XmlSerializer(
            typeof(ProposalManagementPolicy), "");
            return (ProposalManagementPolicy)ser.Deserialize(
            new StreamReader(fileName));
        }

        /// <summary>
        /// Loads a ProposalManagementPolicy object from an xml string.
        /// </summary>
        public static ProposalManagementPolicy FromXml(string xml)
        {
            xml = xml.Replace(Strings.XmlDirective_Utf16,
                    Strings.XmlDirective_Utf8).Trim();

if (!xml.StartsWith(Strings.XmlDirective_Utf8))
                xml = Strings.XmlDirective_Utf8 + xml;

            XmlSerializer ser = new XmlSerializer(
                        typeof(ProposalManagementPolicy), "");
            return (ProposalManagementPolicy)ser.Deserialize(
                        new StringReader(xml));
        }

        /// <summary>
        /// Loads a ProposalManagementPolicy object from a content type.
        /// </summary>
        /// <param name="contentType">the containing content type instance</param>
        public static ProposalManagementPolicy FromContentType(
                            ContentType contentType)
        {
            string xml = contentType.GetXmlString(
                Strings.ProposalManagementPolicyNamespace);
            XmlSerializer ser = new XmlSerializer(
                typeof(ProposalManagementPolicy), "");
            return (ProposalManagementPolicy)ser.Deserialize(new StringReader(xml));
        }

        /// <summary>
        /// Applies the policy to a given set of proposal properties.
        /// </summary>
        public void ApplyPolicy(SPItemEventDataCollection properties,
                    PolicyContext context)
        {
            // Check the properties to determine if the policy was satisfied.
            switch (ProjectProposal.GetProposalType(properties)) {
                case ProposalType.FixedBid: {
                        ApplyFixedBidPolicy(properties, context);
                        break;
                    }
                case ProposalType.TimeAndMaterials: {
                        ApplyTimeAndMaterialsPolicy(properties, context);
                        break;
                    }
            }
        }

        /// <summary>
        /// Converts this instance to a text string.
        /// </summary>
        /// <remarks>
        /// Uses the XmlSerializer to produce an XML string representing
        /// the policy.
        /// </remarks>
        public override string ToString()
        {
            // Generate the XML string.
            StringBuilder policy = new StringBuilder();

XmlSerializer ser = new XmlSerializer(GetType(), "");
            ser.Serialize(new StringWriter(policy), this);

            // Check for and remove the xml directive.
            string xml = policy.ToString();
            xml = xml.Replace(Strings.XmlDirective_Utf8, "");
            xml = xml.Replace(Strings.XmlDirective_Utf16, "");

            return xml.Trim();
        }

        /// <summary>
        /// Updates the policy associated with a given content type.
        /// </summary>
        /// <param name="contentType"></param>
        public void UpdatePolicy(ContentType contentType)
        {
            // Attach the policy to the content type.
            contentType.AddXmlString(Strings.ProposalManagementPolicyNamespace,
                        this.ToString());
        }

        /// <summary>
        /// Applies policies that pertain to fixed bid proposals.
        /// </summary>
        /// <param name="properties"></param>
        void ApplyFixedBidPolicy(SPItemEventDataCollection properties,
                    PolicyContext context)
        {
            if (ProjectProposal.GetBidAmount(properties)
            < FixedBid.BidAmount.MinimumValue) {
                switch (context) {
                    case PolicyContext.Update:
                        throw new ProposalManagementPolicyException(
                FixedBid.BidAmount.FormattedErrorMessage);
                }
            }
        }

        /// <summary>
        /// Applies policies that pertain to time and materials proposals.
        /// </summary>
        /// <param name="properties"></param>
        void ApplyTimeAndMaterialsPolicy(SPItemEventDataCollection properties,
                        PolicyContext context)
        {
            if (ProjectProposal.GetEstimatedHours(properties)
            < TimeAndMaterials.EstimatedHours.MinimumValue) {
                switch (context) {
                    case PolicyContext.Update:
                        throw new ProposalManagementPolicyException(
                TimeAndMaterials.EstimatedHours.FormattedErrorMessage);
                }
            }
        }

private ProposalManagementPolicyFixedBid fixedBidField;
        private ProposalManagementPolicyTimeAndMaterials timeAndMaterialsField;

        /// <remarks/>
        public ProposalManagementPolicyFixedBid FixedBid {
            get {
                return this.fixedBidField;
            }
            set {
                this.fixedBidField = value;
            }
        }

        /// <remarks/>
        public ProposalManagementPolicyTimeAndMaterials TimeAndMaterials {
            get {
                return this.timeAndMaterialsField;
            }
            set {
                this.timeAndMaterialsField = value;
            }
        }
    }

    #region XML Serialization Support

    /// <remarks/>
    [Serializable]
    [XmlType(AnonymousType=true)]
    public class ProposalManagementPolicyFixedBid {

        private ProposalManagementPolicyFixedBidBidAmount bidAmountField;

        /// <remarks/>
        public ProposalManagementPolicyFixedBidBidAmount BidAmount {
            get {
                return this.bidAmountField;
            }
            set {
                this.bidAmountField = value;
            }
        }
    }

    /// <remarks/>
    [Serializable]
    [XmlType(AnonymousType=true)]
    public class ProposalManagementPolicyFixedBidBidAmount {

        private string errorMessageField;
        private decimal minimumValueField;

        /// <remarks/>

public string ErrorMessage {
            get {
                return this.errorMessageField;
            }
            set {
                this.errorMessageField = value;
            }
        }

        /// <summary>
        /// Builds the actual error message by performing field substitutions.
        /// </summary>
        public string FormattedErrorMessage
        {
            get
            {
                return ErrorMessage.Replace(
                "$$MinimumValue$$", MinimumValue.ToString());
            }
        }

        /// <remarks/>
        [XmlAttribute]
        public decimal MinimumValue
        {
            get {
                return this.minimumValueField;
            }
            set {
                this.minimumValueField = value;
            }
        }
    }

    /// <remarks/>
    [Serializable]
    [XmlType(AnonymousType=true)]
    public class ProposalManagementPolicyTimeAndMaterials {

        private ProposalManagementPolicyTimeAndMaterialsEstimatedHours             estimatedHoursField;

        /// <remarks/>
        public ProposalManagementPolicyTimeAndMaterialsEstimatedHours             EstimatedHours {
            get {
                return this.estimatedHoursField;
            }
            set {
                this.estimatedHoursField = value;
            }
        }
    }

/// <remarks/>
    [Serializable]
    [XmlType(AnonymousType=true)]
    public class ProposalManagementPolicyTimeAndMaterialsEstimatedHours {

        private string errorMessageField;
        private decimal minimumValueField;

        /// <remarks/>
        public string ErrorMessage {
            get {
                return this.errorMessageField;
            }
            set {
                this.errorMessageField = value;
            }
        }

        /// <remarks/>
        [XmlAttribute]
        public decimal MinimumValue
        {
            get {
                return this.minimumValueField;
            }
            set {
                this.minimumValueField = value;
            }
        }

        /// <summary>
        /// Builds the actual error message by performing field substitutions.
        /// </summary>
        public string FormattedErrorMessage
        {
            get
            {
                return ErrorMessage.Replace(
                "$$MinimumValue$$", MinimumValue.ToString());
            }
        }
    }

    public class StringCollection : System.Collections.Generic.List<string> {
    }
    #endregion
}

Now you can modify the ItemUpdating event receiver to process the attached policy:

[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemUpdating(SPItemEventProperties properties)
{
    try {
        // Apply the current proposal management policy.
        ProjectProposalType.ApplyPolicy(properties,
            ProposalManagementPolicy.PolicyContext.Update);
    } catch (Exception x) {
        properties.ErrorMessage = x.Message;
        properties.Cancel = true;
    }
}

If an exception is thrown during the application of the policy, you set the ErrorMessagefield and cancel the operation. SharePoint will then display an Error page whenever an attempt is made to update a proposal that does not conform to the established policy, as shown in Figure 11-11.

Figure 11.11. Figure 11-11

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

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