Udostępnij za pośrednictwem


Team Foundation Version Control check-in policies, DisplayHelp/Activate, and making policies

You may have read Jim's excellent posts about policy creation (part 1, part 2) or seen check-in policies mentioned in Chris Menegay's MSDN article on Team Foudation Version Control.  Either way, check-in policies are a useful mechanism for running code at check-in time to perform whatever sanity checks you want (Are there tabs in your source?  Did it successfully build?  Did the unit tests run successfully?  Are there copyright statements in the source?  Did it pass FxCop tests? Is the source formatted as per company guidelines?  Was there sufficient use of Cornflower Blue?  Was the source rot13 encoded at least twice?).

The very typical situation is that people are going to implement policies just like Jim showed in his example - your policy's Evaluate will return an array of normal PolicyFailure objects.  Those PolicyFailure's you return back to the policy framework get used in a few different manners.  First, obviously, we display them in the list of policy failures.  However, there's also a couple of methods on them that we can call depending on what the user does.  If the user decides to double-click on an individual failure in the list of policy failures, we will call that PolicyFailure's Activate() method.  If they select the failure and then hit F1, we will call that PolicyFailure's DisplayHelp() method.

Rather than force policy authors to do even more work, the PolicyFailure objects that you normally use will take these 2 method calls - failure.DisplayHelp() and failure.Activate() and simply defer them to the actual policy (the class you wrote that implements IPolicyEvaluation), calling policy.DisplayHelp(failure) and policy.Activate(failure) respectively.  Then your policy can do whatever it wants to perform those actions for the user.

However, we realize that maybe there are situations where you don't always want to handle things the same way, so we allow you to subclass PolicyFailure and override the DisplayHelp and/or Activate methods (they're both virtual) to do whatever custom logic that particular failure may want to do.  You may have them call the base.DisplayHelp / base.Activate or not, which is totally up to you since the base calls are going right back to your policy anyway.

    class MyPolicyFailure : PolicyFailure

    {

        public MyPolicyFailure(string message, IPolicyEvaluation policy)

            : base(message, policy) { }

        public override void Activate()

        {

            MessageBox.Show("Wonder-twin powers ... Activate!");

        }

        public override void DisplayHelp()

        {

            MessageBox.Show("Help! Help! I'm being repressed!");

        }

    }

Once you have your own custom policy failures, you could mix and match the ones you send back from your policy's Evaluate

    class MyPolicy : IPolicyEvaluation, IPolicyDefinition

{

  …

        public PolicyFailure[] Evaluate()

        {

          return new PolicyFailure[] {

                new PolicyFailure("this one is so normal", this),

                new PolicyFailure("this one is normal too", this),

                new MyPolicyFailure("Blast!", this),

                new MyPolicyFailure("What the deuce?", this),

                new PolicyFailure("back to normal", this),

            };

        }

      …

    }

 

Now, I have to admit I can't think of any reason off-hand you would need to create your own PolicyFailure subclass, but the flexibility is there if you really want it.

 

Why does any of this matter?  One very specific reason.

 

Remember how the PolicyFailure just turns around and calls the policy passing itself as the parameter?  That's very important to know.  Imagine you're writing a policy and it comes time to implement DisplayHelp(PolicyFailure) or Activate(PolicyFailure).  You'll notice that the PolicyFailure you've been passed has a matching method of the same name.  You may be very tempted to just say something like:

 

     void IPolicyEvaluation.Activate(PolicyFailure policyFailure)

    {

        policyFailure.Activate();

    }

    void IPolicyEvaluation.DisplayHelp(PolicyFailure policyFailure)

    {

        policyFailure.DisplayHelp();

    }

 

Can you guess at this point why this is bad?  That's right!  You've just created a nice little stack overflow as your policy calls the PolicyFailure's method, which in turn calls your policy's method.  Lather, rinse, repeat-until-crash.  PolicyFailure by default is deferring these methods to the policy, and when these methods hit your policy, you are the authoritative source for what to do when these methods are called - do NOT try to pass them back to the PolicyFailures.  The one possible exception case would be if your policy never uses the default PolicyFailure class and your subclasses never call through to the base, but that's not a situation I would recommend.  Your policy's DisplayHelp and Activate should do whatever you want to have happen when the user asks for help or chooses to activate (double-click, for instance) your failure.  As an example, one of the check-in policies that ships out-of-the-box is the "Work Items" policy that requires there's a work item associated with this checkin.  If the user double-clicks on that failure, it does a very nice thing - automatically switches you over to the Work Items channel so that you can select a work item with this changeset.

 

And, finally, you may decide that implementing those 2 interfaces (IPolicyDefinition and IPolicyEvaluation) is just too much work and you want to do something simple.  To make it even simpler, we give you an abstract PolicyBase class that you can subclass in the creation of your policy.

 

    class SimplePolicy : PolicyBase

    {

        public override PolicyFailure[] Evaluate()

        {

            return new PolicyFailure[] {

                new PolicyFailure("this one is so normal", this),

                new PolicyFailure("this one is normal too", this),

                new MyPolicyFailure("Blast!", this),

                new MyPolicyFailure("What the deuce?", this),

                new PolicyFailure("back to normal", this),

            };

        }

public override bool CanEdit { get { return false; } }

public override bool Edit(IPolicyEditArgs policyEditArgs)

{

// no UI for this policy, so the settings can't change

return false;

}

        public override string Description { get { return "foo"; } }

        public override string Type { get { return "bar"; } }

        public override string TypeDescription { get { return "baz"; } }

    }