I needed to insert a Work Item into TFS at a specific state, rather than following the predefined state transitions. Well, not just one work item, but thousands of them, since I’m working on migrating items from another system which is not yet supported by the TFS Integration Tools. I had to write some custom migration code to get this done. The TFS API does not allow you to bypass the rules specified by the Work Item Type Definition (rightly so), so I could approach this two different ways:
- Insert all work items with the initial pre-configured state, and then update it multiple times until I get to the desired state. This would’ve worked but it would also created unnecessary noise in history.
- Call the TFS Web Service directly, which allows you to set a “BypassRules” value and it does exactly what it says: it bypasses WITD rules (not all, as you still need things like title, created by, etc).
I went with option 2. Now, I don’t recommend that you do this unless you absolutely have to for a few very valid reasons:
- These services are not documented by Microsoft
- Microsoft reserves the right to change these web services on any future version of TFS.
- If you get an error from the web service, you’ll need to decipher it yourself. Some errors are easier to figure out, such as “The System.AreaID field must contain an integer greater than zero”. But errors such as “Forcing rollback —> Forcing rollback —> Forcing rollback” will leave you digging through code for a while.
So, on to the code…
First, you need to reference Microsoft.TeamFoundation.WorkItemTracking.Proxy and Microsoft.TeamFoundation.Client.
Then you connect to your TFS Collection and then the WorkItemServer service:
1: TfsTeamProjectCollection collection = new TfsTeamProjectCollection(new Uri("http://TFSServer:8080/tfs/DefaultCollection"));
2: WorkItemServer svc = collection.GetService(typeof(WorkItemServer)) as WorkItemServer;
The service has a few methods, but the only one that you need to be concerned with is the “Update” method. It deals with an XML package, which contains all the work item data that you are looking to insert. This same method can also be used for updates (obvious, based on the name).
The XML package should look something like this:
1: <Package>
2: <InsertWorkItem ObjectType='WorkItem' BypassRules='1'>
3: <Columns>
4: <Column Column='System.AreaID' Type='Number'><Value>8Value>Column>
5: <Column Column='System.IterationID' Type='Number'><Value>12Value>Column>
6: <Column Column='System.WorkItemType' Type='String'><Value>BugValue>Column>
7: <Column Column='System.Title' Type='String'><Value>Your bugValue>Column>
8: <Column Column='System.State' Type='String'><Value>FixedValue>Column>
9: <Column Column='System.Reason' Type='String'><Value>MigratedValue>Column>
10: <Column Column='System.CreatedDate' Type='ServerDateTime'>Column>
11: <Column Column='System.CreatedBy' Type='String'><Value>Automated ProcessValue>Column>
12: Columns>
13: <InsertText FieldName='System.History' FieldDisplayName='History'>NOTE: This item was migratedInsertText>
14: InsertWorkItem>
15: Package>
Something interesting to point out is that “InsertText” element. If you want to add a comment to the history record for the insert, that’s how you should go about doing it.
The signature for the Update service method is:
public void Update(string requestId, XmlElement package, out XmlElement result, MetadataTableHaveEntry[] metadataHave, out string dbStamp, out IMetadataRowSets metadata);
This will get you your requesID: WorkItemServer.NewRequestId().
And you can retrieve your new work item ID from result.FirstChild.Attributes[“ID”].Value;
I hope this helps someone!