Overwriting TFS Web Services
- by javarg
In this blog I will share a technique I used to intercept TFS Web Services calls. This technique is a very invasive one and requires you to overwrite default TFS Web Services behavior. I only recommend taking such an approach when other means of TFS extensibility fail to provide the same functionality (this is not a supported TFS extensibility point). For instance, intercepting and aborting a Work Item change operation could be implemented using this approach (consider TFS Subscribers functionality before taking this approach, check Martin’s post about subscribers). So let’s get started. The technique consists in versioning TFS Web Services .asmx service classes. If you look into TFS’s ASMX services you will notice that versioning is supported by creating a class hierarchy between different product versions. For instance, let’s take the Work Item management service .asmx. Check the following .asmx file located at: %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\_tfs_resources\WorkItemTracking\v3.0\ClientService.asmx The .asmx references the class Microsoft.TeamFoundation.WorkItemTracking.Server.ClientService3: <%-- Copyright (c) Microsoft Corporation. All rights reserved. --%>
<%@ webservice language="C#"
Class="Microsoft.TeamFoundation.WorkItemTracking.Server.ClientService3" %>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The inheritance hierarchy for this service class follows:
Note the naming convention used for service versioning (ClientService3, ClientService2, ClientService).
We will need to overwrite the latest service version provided by the product (in this case ClientService3 for TFS 2010).
The following example intercepts and analyzes WorkItem fields. Suppose we need to validate state changes with more advanced logic other than the provided validations/constraints of the process template.
Important: Backup the original .asmx file and create one of your own.
Create a Visual Studio Web App Project and include a new ASMX Web Service in the project
Add the following references to the project (check the folder %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\):
Microsoft.TeamFoundation.Framework.Server.dll
Microsoft.TeamFoundation.Server.dll Microsoft.TeamFoundation.Server.dll
Microsoft.TeamFoundation.WorkItemTracking.Client.QueryLanguage.dll
Microsoft.TeamFoundation.WorkItemTracking.Server.DataAccessLayer.dll
Microsoft.TeamFoundation.WorkItemTracking.Server.DataServices.dll
Replace the default service implementation with the something similar to the following code:
Code Snippet
/// <summary>
/// Inherit from ClientService3 to overwrite default Implementation
/// </summary>
[WebService(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/WorkItemTracking/ClientServices/03", Description = "Custom Team Foundation WorkItemTracking ClientService Web Service")]
public class CustomTfsClientService : ClientService3
{
[WebMethod, SoapHeader("requestHeader", Direction = SoapHeaderDirection.In)]
public override bool BulkUpdate(
XmlElement package,
out XmlElement result,
MetadataTableHaveEntry[] metadataHave,
out string dbStamp,
out Payload metadata)
{
var xe = XElement.Parse(package.OuterXml);
// We only intercept WorkItems Updates (we can easily extend this sample to capture any operation).
var wit = xe.Element("UpdateWorkItem");
if (wit != null)
{
if (wit.Attribute("WorkItemID") != null)
{
int witId = (int)wit.Attribute("WorkItemID");
// With this Id. I can query TFS for more detailed information, using TFS Client API (assuming the WIT already exists).
var stateChanged =
wit.Element("Columns").Elements("Column").FirstOrDefault(c => (string)c.Attribute("Column") == "System.State");
if (stateChanged != null)
{
var newStateName = stateChanged.Element("Value").Value;
if (newStateName == "Resolved")
{
throw new Exception("Cannot change state to Resolved!");
}
}
}
}
// Finally, we call base method implementation
return base.BulkUpdate(package, out result, metadataHave, out dbStamp, out metadata);
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
4. Build your solution and overwrite the original .asmx with the new implementation referencing our new service version (don’t forget to backup it up first).
5. Copy your project’s .dll into the following path:
%Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin
6. Try saving a WorkItem into the Resolved state.
Enjoy!