Policy Plug-ins

The policy framework exposes a plug-in model for you to use. This enables you to provide your own policy logic. Plug-ins are used both within the policy definition process as well as during the policy evaluation process. Policy plug-ins install either by themselves or as part of a separate application. They are registered with the policy framework so they can be loaded, as needed.

For more information, see Architecture for Check-in Policy.

Anatomy of a Policy Plug-in

A policy plug-in is implemented as one or more managed .NET assemblies. These plug-ins provide one or more classes implementing the interfaces required by the policy framework. The policy framework reflects all registered assemblies in search of appropriately attributed classes that implement the interfaces required for policy definition or evaluation. Classes that meet the requirements are instantiated, as appropriate.

A policy plug-in must expose the following interfaces:

  • IPolicyDefinition   The IPolicyDefinition interface exposes methods used in the process of defining policy requirements for a team project. It includes methods for invoking user interfaces specific to the policy plug-in. It allows users to easily define a check-in policy.

  • IPolicyEvaluation   The IPolicyEvaluation interface exposes methods used in the process of evaluating policy compliance during the check-in process. It includes methods that accept the contents of a check-in operation. It analyzes them to establish whether the defined policy is satisfied.

Finally, multiple policy plug-ins can be packaged in the same assembly. Nothing prevents you from defining a full compliment of policy plug-ins within a single assembly. The only requirement is that you implement the plug-ins as separate classes.

Note

These interfaces are exposed in PolicyBase. As an alternative to implementing IPolicyDefinition and IPolicyEvaluation, you can derive a class from PolicyBase. This is described in PolicyBase Policy.

Plug-in Life Cycle

Load the plug-in assemblies, as necessary, at the beginning of any of the following events:

  1. Create a pending change (such as check-out, add, rename, branch, delete, etc.) within a given team project.

    At this time, the system transfers the applicable policy definitions to the client and loads the policy plug-ins.

  2. Click the Add button on the policy definition user interface.

    The system unloads the assemblies when these activities complete.

Registration

The policy framework must know about the assemblies for a check-in policy in order for the framework to instantiate the assemblies. You can install the assemblies as part of a larger application. They can depend on references to dynamic link libraries (DLLs) in the other applications such that their installation location cannot be dictated by the policy framework. Finally, there is no connection between the order of application installation and the assembly's registration. Do not assume that Team Foundation Server version control is installed before the use of a policy plug-in.

Team Foundation Server version control has a central location where you can register check-in policies. This is where the policy framework learns about the policies. For this purpose, Team Foundation Server version control uses the Windows registry. The framework looks for registered assemblies under the HKEY_LOCAL_MACHINE registry. Specifically, the framework looks for assemblies registered under:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies]

String values for policy assemblies can be registered underneath this registry key. The name of the value should be the name of the assembly (without the .dll extension) that describes the check-in policy. The value stored should be the full path to the assembly that implements the policy. If you split a policy across multiple assemblies, you should register each assembly independently. The framework simply reflects on all policy assemblies and loads all of the assemblies that implement the functionality needed.

Policy Definition

For the policy definition process, a plug-in must implement the IPolicyDefinition interface. The policy framework reflects on all registered assemblies searching for classes that implement this interface. It instantiates all classes at the beginning of the policy definition process. These objects then process the list of existing defined policies. They are used to generate new policy definitions.

The following code shows the policy definition interface:

namespace Microsoft.TeamFoundation.VersionControl.Client
{
    public interface IPolicyDefinition
    {
        bool CanEdit { get; }

        // A string describing the behavior of this instance of a policy.
        string Description { get; }

        // A message that is displayed if the policy appears to not be installed.
        string InstallationInstructions { get; }

        //A string representing the type of the policy
        string Type { get; }
        
        // A string describing what this type of policy does.
        string TypeDescription { get; }

        bool Edit(IPolicyEditArgs policyEditArgs);
    }
}

Policy Evaluation

To evaluate the compliance of a set of pending changes with a defined policy, a plug-in must implement the IPolicyEvaluation interface. This interface exposes methods for processing a policy definition and evaluating the compliance of a list of changes with the defined policy. When you double-click the policy failure, this interface also exposes a method for acting upon a policy failure.

namespace Microsoft.TeamFoundation.VersionControl.Client
{
    public interface IPolicyEvaluation : IDisposable
    {
        // Event to notify the host of an asynchronous change in the policy state
        // event PolicyStateChangedHandler PolicyStateChanged;

        // Display UI to allow the user to get more info on failure
        void Activate(PolicyFailure failure);
        void DisplayHelp(PolicyFailure failure);

        // Evaluate the policy and return a list of failures
        PolicyFailure[] Evaluate();

        // Initialize policy instant so it can perform evaluations
        void Initialize(IPendingCheckin pendingCheckin);
    }
}

Policy evaluation is based on the current changeset information specified in either the Pending Check-ins window or the Check In dialog box. This information includes:

  • The check-in comment.

  • The currently selected list of files.

  • The currently selected work items.

  • The release note information.

All of these details are passed to the IPolicyEvaluation interface for evaluating policy compliance.

The IPolicyEvaluation interface provides a mechanism for the policy plug-in to force re-evaluation of the policy state. This is necessary for most policy plug-ins to react to asynchronous events that happen elsewhere within the IDE. For example, if dirtying a source file causes the policy to no longer pass, the plug-in should subscribe to the events in the IDE, which in turn will notify it of files becoming dirtied. The plug-in then can raise the PolicyStateChanged event. The PolicyStateChanged event notifies the policy framework that the policy information may have changed.

The IPolicyEvaluation interface provides a mechanism for the policy plug-in to learn about the Visual Studio 2005 SDK environment. This enables the plug-in to use the services exposed therein and potentially communicate with a particular Visual Studio 2005 SDK package. This interface exposes a single method for the framework to expose the root of the Visual Studio 2005 SDK object model. This interface is not used for a check-in outside of the Visual Studio user interface.

Format of a Policy Definition

A policy definition is persisted as a serialized .NET object. You should mark the class implementing IPolicyDefinition with a "Serializable" attribute. Mark as "NonSerializable" any members that should not be persisted with the policy definition.

Plug-in Security

Team Foundation Server version control does not provide a secure plug-in model with the check-in policy framework. Because the policy plug-ins are always deliberately installed by the user and never downloaded to the computer, Team Foundation Server version control defers the trust of the policy plug-in to the end user.

Check-in Policy Sample

The following example illustrates a simple check-in policy. This policy prevents users from checking in files that do not match a given wildcard. While it is not a difficult solution to implement, this sample gives you a strong starting point for building your own check-in policies.

To build this sample, create a new C# Class Library project called "ExtensibilityPolicy". Place this source code in Class1.cs. Add references to the following assemblies:

System.Windows.Forms;

Microsoft.TeamFoundation.Client;

Microsoft.TeamFoundation.VersionControl.Client;

Microsoft.TeamFoundation.VersionControl.Controls;

Microsoft.TeamFoundation.VersionControl.Common;

You still need to implement the DialogSampleWildcardPolicy Windows form in order to get the class to compile. For more information, open the project file (ExtensibilityPolicy) enclosed in the sample projects of Team Foundation Server Version Control SDK.

To register the policy plug-in, add the following registry entry:

HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies\ ExtensibilityPolicy = "<Path to compiled assembly>\ExtensibilityPolicy.dll>"

Once registered, you can configure this policy from the team project’s source control settings page.

using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Controls;
using Microsoft.TeamFoundation.VersionControl.Common;
using Microsoft.TeamFoundation.Client;

namespace SamplePolicy
{
    [Serializable]
    public class SamplePolicy : IPolicyDefinition, IPolicyEvaluation
    {
        public SamplePolicy()
        {
        }
        
        public String Type
        {
            get
            {
                return "Filename Filter";
            }
        }

        public String TypeDescription
        {
            get
            {
                return "Limit users to checking in only files that match a specified wildcard.  Disallow the check in if non-matching files are selected.";
            }
        }

        public String Description
        {
            get
            {
                return "Wildcard: " + m_wildcard;
            }
        }
        
        public virtual String InstallationInstructions
        {
            get
            {
                return m_installationInstructions;
            }
        }
        
        public virtual bool CanEdit
        {
            get { return true; }
        }

        public virtual void Activate(PolicyFailure failure)
        {
        }

        public virtual event PolicyStateChangedHandler PolicyStateChanged;

        protected virtual void OnPolicyStateChanged(PolicyFailure[] failures)
        {
            PolicyStateChangedHandler handler = PolicyStateChanged;
            if (handler != null)
            {
                handler(this, new PolicyStateChangedEventArgs(failures, this));
            }
        }

        public bool Edit(IPolicyEditArgs policyEditArgs)
        {
            if (m_pendingCheckin != null)
            {
                throw new ApplicationException("The policy can't be edited after it has been initialized for evaluation.");
            }

            using (DialogSampleWildcardPolicy dlg = new DialogSampleWildcardPolicy(m_wildcard))
            {
                DialogResult res = dlg.ShowDialog(policyEditArgs.Parent);
                if (res == DialogResult.OK)
                {
                    m_wildcard = dlg.Wildcard;
                    return true;
                }
                return false;
            }
        }

        public void Initialize(IPendingCheckin pendingCheckin)
        {

            if (m_pendingCheckin != null)
            {
                throw new InvalidOperationException("Policy already initialized.");
            }
            if (m_disposed)
            {
                throw new ObjectDisposedException(null);
            }
            m_pendingCheckin = pendingCheckin;

            pendingCheckin.PendingChanges.CheckedPendingChangesChanged += new EventHandler(pendingCheckin_CheckedPendingChangesChanged);
        }

        public void Dispose()
        {
            if (!m_disposed)
            {
                m_pendingCheckin.PendingChanges.CheckedPendingChangesChanged -= new EventHandler(pendingCheckin_CheckedPendingChangesChanged);

                // Mark us as disposed.
                m_disposed = true;

                // Stop firing events.
                PolicyStateChanged = null;
            }
        }

        public PolicyFailure[] Evaluate()
        {
            if (m_disposed)
            {
                throw new ObjectDisposedException(null);
            }

            ArrayList failures = new ArrayList();

            // Enumerate the checked pending changes.
            PendingChange[] changes = m_pendingCheckin.PendingChanges.CheckedPendingChanges;
            foreach (PendingChange change in changes)
            {
                // Check that they all match the allowed wildcard.
                if (!VersionControlPath.MatchFileName(change.ServerItem, m_wildcard))
                {
                    String message = String.Format("Checking in {0} is not allowed", VersionControlPath.GetFileName(change.ServerItem));
                    failures.Add(new PolicyFailure(message, this));
                }
            }

            // Return the (possibly empty) list of failures.
            return (PolicyFailure[])failures.ToArray(typeof(PolicyFailure));
        }

        private void pendingCheckin_CheckedPendingChangesChanged(Object sender, EventArgs e)
        {
            if (!m_disposed)
            {
                OnPolicyStateChanged(Evaluate());
            }
        }

        public void DisplayHelp(PolicyFailure failure)
        {
            // Launch IE to show help.
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("iexplore.exe", "https://www.microsoft.com"));
        }

        public String m_wildcard = "*.*";
        protected String m_installationInstructions = "";
        
        [NonSerialized]
        protected IPendingCheckin m_pendingCheckin;
        [NonSerialized]
        protected bool m_disposed = false;
        public static readonly PolicyFailure[] NoFailures = new PolicyFailure[0];
    }
}

See Also

Concepts

Architecture for Check-in Policy

PolicyBase Policy

Reference

PolicyBase