Implementing custom validations in work item form
Recently few asking me about how to implement custom validation rules in a work item form. Our rules in work item form XML is extensive and that covers lots of scenarios. But still it doesn’t meet everyone’s needs and it leaves some holes such as not being able to add rules for AreaPath and IterationPath fields. We are investigating fixing those holes and coming up with a long term solution for extensible rules creation in future versions. This post is about achieving limited customization in V1 and in Orcas.
Custom Validations Limitations:
For now in V1 and in Orcas, it is possible to create custom rules in work item form, but it has below significant limitations:
- The rules work only when data is entered in work item form in VS. So someone could bypass it by changing data using object model, excel integration or using web service.
- Since addins/packages are optional, users without that addin/package will bypass those rules. These rules addins need to be deployed in each user’s client machine.
If these limitations are okay, a VSIP package can be created to achieve this. Some validation could be done using custom controls, but it also suffers with similar limitations above and it doesn’t have support for hooking to save event. Because of these limitations, this functionality was not documented and advertised in V1.
User experience on custom validation:
How would the experience be for users with custom validations in VSIP package? Users will fill in the form as usual, but during save time there’ll be an error stating what the issue was and the save would be aborted. Users can fix the issue and retry save. The validation message could also be thrown when specific field values are changed to invalid value as deemed by the custom rules. The invalid field could not be highlighted though like we do with standard rules.
If validations are done in a custom control, then the control needs to throw validation exception on each field value change. There isn’t an way to hook to save event of the form. Since building a VSIP package to do it take a little while, this cheaper custom control solution might work for you. Inside our teams, we built a VSIP package with few rules and bundled this package + various custom controls as one setup package and distributed to hundreds of users. Custom control documentation is here.
There were attempts to implement security of some form using this technique. But as you see from limitations, it is easily by-passable by various means, so it is not true security. It is also not possible to disable controls/tabs in the form using this approach (only your own custom controls can be made disabled).
Steps to build validation VSIP package with addin interfaces:
- Go over the addin post here to get overview on how Document Service works.
- Create a VSIP package with below attribute set on the package. It basically asks the package to load after loading Team Explorer. That way we can get reference to document service. This is the reason why a VSIP package is used instead of a simpler Addin.
[ProvideAutoLoad("{E13EEDEF-B531-4afe-9725-28A69FA4F896}")] // UI Context used by the Team Menu
- Add WIT references to this project as explained in Ed’s blog post here.
- During initialization of the package, get pointer to DTE object by calling (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE))
- Get reference to DocumentService by calling (DocumentService) this.GetGlobalService(typeof(DocumentService));
- Hook to DocumentAdded and DocumentRemoved events. This event is raised each time a workitem form is opened in VS and hence you can enforce your validations. Example here:
DocumentService.DocumentAdded += new DocumentService.DocumentServiceEventHandler(DocumentService_DocumentAdded);
DocumentService.DocumentRemoved += new DocumentService.DocumentServiceEventHandler ( DocumentService_DocumentRemoved);
- In the hooked events, hook to WorkItemDocuments Saving/Saved events. Example:
void DocumentService_DocumentAdded(object sender, DocumentService.DocumentServiceEventArgs e)
{
IWorkItemDocument doc = e.Document as IWorkItemDocument;
if (doc == null)
return;
doc.Saving += new WorkItemTrackingDocumentEventHandler(Doc_Saving);
doc.Saved += new WorkItemTrackingDocumentEventHandler(Doc_Saved);
doc.Loaded += new WorkItemTrackingDocumentEventHandler(Doc_Loaded);
doc.Closed += new WorkItemTrackingDocumentEventHandler(Doc_Closed);
}
void DocumentService_DocumentRemoved(object sender,
DocumentService.DocumentServiceEventArgs e)
{
IWorkItemDocument doc = e.Document as IWorkItemDocument;
if (doc == null)
return;
doc.Saving -= new WorkItemTrackingDocumentEventHandler(Doc_Saving);
doc.Saved -= new WorkItemTrackingDocumentEventHandler(Doc_Saved);
doc.Loaded -= new WorkItemTrackingDocumentEventHandler(Doc_Loaded);
doc.Closed -= new WorkItemTrackingDocumentEventHandler(Doc_Closed);
}
- In Saving event implement your validations. The Item property on the document has the WorkItem object for checking various fields. In the data is invalid, raise an exception with the message to be shown to user. This prevents saving the work item. Saved event can be used for post-save operations. Loaded & Closed will be raised once per document open/close, but saving could happen many times when the document is open.