Фирма

«Инрэко ЛАН»

Целью данной статьи является описание того, каким образом в TeamAlerts осуществляется подключение к серверу TFS и работа с его основными службами, необходимыми для решения задачи управления подписками на изменения в рабочих элементах Microsoft Team Foundation Server.

Для работы с TFS необходимо подключить следующие библиотеки, находящиеся в папке %Microsoft Visual Studio 9.0 install directory%Common7IDEPrivateAssemblies:

Microsoft.TeamFoundation.dll;
Microsoft.TeamFoundation.Common.dll;
Microsoft.TeamFoundation.Client.dll;
Microsoft.TeamFoundation.WorkItemTracking.Client.dll;

Эти библиотеки поставляются вместе с Visual Studio Team System Team Explorer, который обязательно должен быть установлен на машине разработчика. Распространять вышеперечисленные библиотеки в составе продуктов собственного производства не разрешается.

В TeamAlerts всю работу с TFS с использованием стандартных библиотек можно разделить на две части. Первая - это непосредственно подключение к TFS-серверу под некоторой учётной записью, вторая - работа с подписками и связанными с ними элементами через стандартные сервисы, работающие в рамках TFS сервера.

Регистрация и подключение к TFS серверу

За работу с TFS в TeamAlerts отвечает класс TeamAlertsManager. Далее приведён код той части класса, который отвечает непосредственно за установление соединения и взаимодействие с сервисами TFS.
[Serializable] 
public partial class TeamAlertsManager
{
private TeamFoundationServer server = null;
private IEventService eventService = null;
private IGroupSecurityService groupSecurity = null;
private WorkItemStore workItemStorage = null;

private string userDomain;
private bool isAdminSession = false;
private Identity tfsServiceIdentity = null;

public Identity TfsServiceIdentity
{
  get { return tfsServiceIdentity; }
  set { tfsServiceIdentity = value; }
}
private bool connected = false;
public bool Connected
{
  get { return connected; }
}

public bool IsAdminSession
{
  get { return isAdminSession; }
  set { isAdminSession = value; }
}

public TeamFoundationServer Server
{
  get { return server; }
  set { server = value; }
}

public IEventService EventService
{
  get { return eventService; }
  set { eventService = value; }
}

public IGroupSecurityService GroupSecurity
{
  get { return groupSecurity; }
  set { groupSecurity = value; }
}

public WorkItemStore WorkItemStorage
{
  get { return workItemStorage; }
  set { workItemStorage = value; }
}

public string UserDomain
{
  get { return userDomain; }
  set { userDomain = value; }
}

public void ConnectServer()
{
  if (server == null)
  {
   return;
  }

  try
  {
   if (!connected)
   {
    this.eventService = (IEventService)this.server.GetService(typeof(IEventService));
    this.workItemStorage = (WorkItemStore)this.server.GetService(typeof(WorkItemStore));
    this.groupSecurity = (IGroupSecurityService)this.server.GetService(typeof(IGroupSecurityService));
    if (tfsServiceIdentity == null)
tfsServiceIdentity = GroupSecurity.ReadIdentityFromSource(SearchFactor.AccountName, "TFSService");
    connected = true;
   }
  }
  catch (Exception e)
  {
   return;
  }
}
}

Из приведённого кода не сложно понять, что данный класс хранит сам объект TFS-сервера и ссылки на сервисы, необходимые для дальнейшей работы. Процедура соединения с сервером (метод ConnectServer()) представляет собой инициализацию объектов соответствующих сервисов. Следует заметить, что в данном методе присутствует код, выполняющий считывание из TFS стандартной учётной записи TFSService. Это действие происходит довольно продолжительное время - порядка нескольких десятков секунд и выполняется единожды при подключении к TFS серверу. Скорость взаимодействия с остальными сервисами TFS на порядок выше.

Теперь рассмотрим, каким же образом происходит инициализация объекта server ссылкой на нужный сервер TFS и передача ему необходимых параметров учётной записи. В TeamAlerts эти действия выполняются на странице Login.

Механизм взаимодействия с сервером TFS таков, что при первом же удачном соединении с ним клиента в реестре клиентской машины оставляется запись, содержащая информацию обо все TFS серверах, с которыми когда-либо соединялась данная машина под данной учётной записью. Список зарегистрированных на клиентской машине серверов TFS можно получить, вызвав статический метод GetServers() класса RegisteredServers из пространства имён Microsoft.TeamFoundation.Client:

TeamFoundationServer[] registeredServers = RegisteredServers.GetServers();

Если результирующий массив содержит хотя один объект, то он предлагается пользователю для подключения. Если количество объектов в массиве больше одного, пользователь может выбрать интересующий сервер из списка.

Если результирующий массив не содержит ни одного объекта, то необходимо создать новый объект класса TeamFoundationServer. Параметры учётной записи, под которой будет осуществляться взаимодействие с сервером, необходимо указывать на этапе создания инстанции TFS-сервера. Для этого у класса TeamFoundationServer есть 4 перегруженных конструктора, принимающих различные параметры. Один из них принимает строковое имя сервера и любой объект, реализующий интерфейс ICredentials. Таким образом, имеем следующий код для создания нового объекта TFS- сервера под конкретной учётной записью:

NetworkCredential credentials = new NetworkCredential(userName, password, domain); 
TeamFoundationServer tfServer = new TeamFoundationServer(serverName, credentials);
tfServer.Authenticate();

Таким образом, получена инстанция для сервера serverName под учётной записью, представленной credentials. Она сохраняется в поле server объекта класса TeamAlertsManager. После этого остаётся лишь вызвать метод ConnectServer(), о котором упоминалось ранее. На этом процесс подключения к TFS-серверу завершён.

Работа с подписками на изменения в рабочих элементах TFS

В API TFS подписки представлены классом Subscription пространства имён Microsoft.TeamFoundation.Server. Класс имеет следующую сигнатуру:

namespace Microsoft.TeamFoundation.Server 
{
public class Subscription
{
public static readonly int Unset;

public Subscription();

public string ConditionString { get; set; }
public DeliveryPreference DeliveryPreference { get; set; }
public string Device { get; set; }
public string EventType { get; set; }
public int ID { get; set; }
public string Subscriber { get; set; }
public string Tag { get; set; }

public override string ToString();
}
}

Физически подписки хранятся в базе данных TfsIntegration, создаваемой TFS при установке, в таблице tbl_subscription. Первичным ключом является целочисленное автоинкрементное поле id.

Возможность работы с подписками TFS обеспечивает сервис EventService, реализующий интерфейс IEventService. Этот интерфейс имеет следующую сигнатуру:

namespace Microsoft.TeamFoundation.Server 
{
public interface IEventService
{
Subscription[] EventSubscriptions(string userId);
Subscription[] EventSubscriptions(string userId, string tag);
void FireAsyncEvent(object theEvent);
void FireAsyncEvent(string theEvent);
void FireBulkAsyncEvents(object[] theEvents);
void FireBulkAsyncEvents(string[] theEvents);
int SubscribeEvent(string userID, string eventType, string filterExpression, DeliveryPreference preferences);
int SubscribeEvent(string userId, string eventType, string filterExpression, DeliveryPreference preferences, string tag);
void UnsubscribeEvent(int subscriptionId);
}
}

В TeamAlerts объект типа IEventService инкапсулирован внутри класса TeamAlertsManager. Для получения массива подписок у объекта, приведённого к типу IEventService, существует 2 перегруженных метода EventSubscriptions, один из которых принимает строковое значение userId, которое имеет формат <доменимя_пользователя_в_домене>. Например, для получения всех подписок стандартной учётной записи TFSService необходимо написать следующий код:

Subscription[] projectSubscriptions =  
  TFSManager.EventService.EventSubscriptions(TFSManager.Server.AuthenticatedUserIdentity.Domain + @" fsservice");
Для создания новых подписок существуют перегруженные методы SubscribeEvent. При вызове данного метода с необходимыми параметрами TFS проверяет, существует ли подписка с такими же параметрами. Если такая подписка уже существует, не производится никаких действий и возвращается id этой подписки. Если нет ни одной подписки, абсолютно совпадающей по всем параметрам с параметрами на входе, то создаётся новая подписка и возвращается её id. Возможности для редактирования подписки API TFS не предоставляет. Для обхода этого ограничения просто создаётся новая подписка, которая обязательно должна отличаться от исходной редактируемой подписки, а исходная подписка удаляется вызовом метода UnsubscribeEvent. То есть физически это уже новая подписка, однако у пользователя TeamAlerts создастся впечатление, что он отредактировал имеющуюся подписку, так как он не может узнать id этой подписки.

Сергей Крючков, инженер-программист 2-й категории 

Метки: C# | TFS | проекты

Добавить комментарий