A client of mine came to me with the following problem: She has several server plugins that manipulate work items, one which I wrote, and another that was downloaded from codeplex. When applied to one work item, e.g. via the Team Explorer, everything works fine. However, when applying to a bulk of work items (via Excel publishing, for example), the process freezes for several minutes, until it completes and is only then freed.
I came up with the following solution: A server plugin that queues a job for the TFS Job Agent, and a Job Extension to handle the work, at its leisure.
The Server Plugin
A server plugin runs before and/or after an event, depending on the Notification Type you choose to use. It does so synchronously, so it doesn’t free the resource or the process until it completes. Therefore, like any other event you write, you will want to free it as soon as possible, and hand off any long-running work to a separate thread or process.
The template for writing a server plugin is rather simple and straight forward. This is the template I use.
First you have to add some references, and “using” statements. Most blog posts I read don’t mention them, which is a bit of a pain, in my opinion, so I’ll add them here:
Next, we need to take care of a few properties. This is simple enough, and rather standard:
Finally, we’ve got the Process Event callback method – this is where we handle the event. We start by making sure that we’re handling the right event. That’s right: Every ISubscriber class gets called for every event. You want to make sure as soon as possible that you’re dealing with the right type of event:
Note that since we’re queuing a job to be handled asynchronously our notification type will always be a Notification, rather than a DecisionPoint. It doesn’t make much sense to queue something that must happen before the original event occurs. You do see that, right?
I prefer to make multiple return points in my code to reduce cyclamatic complexity, rather than nesting if statements. It is purely a matter of style, but I find code easier to read if you can separate control from logic.
Next comes the interesting part. This is where you would normally place your logic. However, in order to be able to return the control as soon as possible, you will instead queue a job to handle your logic, as follows:
In this code block we do two things: First, in lines 4-6, we prepare the data we want to pass to the job. The QueueOneTimeJob method accepts an XmlNode of data that can be passed on to the job. For my example, as I am listening to the WorkItemChangedEvent, I will pass the Work Item ID, in an XmlElement. In lines 9-11, we queue a job.
Two things need to be mentioned. First is that since we are going to queue each job with different data, we use the QueueOneTimeJob. It is used when we don’t have a known job that we wish to activate (that would require knowing its GUID), therefore each call creates a new job, and two successive calls won’t merge accidentally, causing one job to get lost. Once again, this is an implementation issue that needs to be circumvented – and luckily can be done so easily.
The other thing, is that you need to pass it the domain name and class of the job extension (see line 10). This tells the TFS Job Agent which extension to activate for each job. Unfortunately, it is passed as text, and therefore runs the risk of causing a runtime error.Double check your plugin name. Don’t say I didn’t warn you!
Another important thing to note, is that you must not throw an exception. If you do, your plugin will get disabled, and will not get restarted until you restart TFS. make sure you catch and swallow every exception your code raises. Log it, and check it, but don’t allow it to escape.
Finally, return a result. Given you won’t be handling the event here, you will always return the same status result:
The Job Extension
Next comes the job extension. Similarly, there’s an interface to implement. Remember to make sure that the namespace and class match the one used in the server plugin when queuing the job:
As above, here are the references and “using” statements you’ll need:
Note that the Client namespaces are used because of my specific example. Yours might vary. Of course, the Linq to XML namespaces are also a matter of personal choice. If you decide to parse your XML differently, you’ll use another library. You really didn’t need me to tell you that, right?
So now you should define your class (did I mention that you should make sure you got the FQDN right?):
Wait a minute! What’s that lock about in line #5? I'll get to it later.
Once again, we have an out parameter so we set it up. We will change it if there’s some information we wish to log (such as an exception).
Next we write the logic to handle the job.
Like with the server plugin, if we let an exception slip through the cracks, so we have to surround our handler with a try..catch clause here as well. As for the lock, in order to avoid collisions such as from trying to write to the work item store 50 times at once from 50 different job extensions, you’ll need to protect your resources. Job extensions will run at the same time, each in its own thread. They will all however run in the same process, the TFS Job Agent, of which you have only one on each Application Tier. Therefore a static lock object will do the job nicely.
the rest of the code in the block is of little interest; I use Linq to parse my XML node and deserialize the work item ID, which I need for my example. Your mileage may vary.
Not much more to it – You have your exception handling:
What you want to do is to return a status of failure (or stopped if the request got cancelled). Additionally, you can return the exception details in the result message.
Finally, if everything went well, you’ll return a success status:
That’s all there is to it!
You can download the complete solution here: RobustServerPlugin.zip.