TFS2010 Object Model - Introduzione, server, collezioni e progetti

Scritto da  Massimo Bonanni il mercoledì 9 febbraio 2011
Linguaggio: VB   •  Framework: 3.5,4.0   •  Livello: 200

Download sorgenti

Download pdf


Quest'articolo è il primo di una serie dedicata all'object model di TFS 2010 e al suo utilizzo per interagire con la piattaforma Visual Studio ALM.

In particolare, in quest' articolo affronteremo l'architettura dell'object model e i concetti principali per interagire con TFS, ovvero, connessione al server, recupero delle collections e dei progetti.

Architettura della piattaforma (a cura di Matteo Emili)
Abbiamo a disposizione due object model ben distinti:

  • Client Object Model : permette di realizzare dei nostri client che interagiscono con la piattaforma TFS. E' l'argomento di questa serie di articoli;
  • Server Object Model: permette di estendere le funzionalità della piattaforma TFS. Non ci occuperemo di questo aspetto in questa serie di articoli.

Figura1

Come possiamo vedere dalla figura, Team Foundation Server espone le sue funzionalità attraverso dei Web Services che, in linea teorica possiamo tranquillamente richiamare dal nostro codice.

Questi Web Service sono:

  • Classification Service, che permette l'accesso a tutte le informazioni dei progetti ed alla sezione di Version Control;
  • Eventing Service, che espone la possibilitò di comunicare in modo bidirezionale (es.: Team Build);
  • Linking Service, è il servizio che ha in carico la gestione delle dipendenze di ogni artefatto (che siano file nel Version Control o Work Item) del Team Foundation Server;
  • Security Service, che implementa il security model e genera automaticamente le ACL per ogni componente, sia out of the box che estensione:
  • Registration Service, l'entry point dell'estendibilità mediante servizi. Ogni servizio deve essere registrato nel Registration Service, e permette anche il discovery di nuovi servizi che vengono a loro volta pubblicati;

Richiamare direttamente i web service di TFS, però comporta una serie di problematiche: si tratta di web service non documentati, quindi ogni operazione viene fatta "alla cieca" e senza alcun tipo di supporto, ma soprattutto saremmo legati alla struttura dei web service stessi e, eventuali cambiamenti in questa, ci costringerebbero a un cambiamento sostanziale del nostro codice e ad una nuova analisi dei web services nella nuova versione.
L'Object Model Client, invece, si interpone fra i web service e la nostra applicazione, fornendo una serie di classi che fungono da wrapper per le chiamate, è aggiornato e distribuito direttamente da Microsoft, corredato da documentazione (sia nell'IDE che su MSDN) e, in definitiva, ci permette di scrivere codice in modo più semplice, funzionale e manutenibile. Entrando in dettaglio alle funzionalità esposte dall'Application Tier, troviamo i seguenti blocchi costitutivi:

Figura2

I web services di TFS espongono le funzionalità relative ai progetti contenuti nelle collections ed in particolare, quelli di cui ci occuperemo in dettaglio sono:

  • Work Item Tracking
  • Version Control
  • Build Service

I Servizi di framework (Team Foundation Framework Service) forniscono tutta una serie di funzionalità trasversali alla piattaforma come ad esempio la gestione delle ACL.
Di questi servizi non ci occuperemo esplicitamente anche se, ovviamente, li utilizzeremo.

Le DLL principali sono tre:

  • Microsoft.TeamFoundation.dll, che contiene tutti i componenti base (classi, interfacce, ecc.) che sono utilizzati dagli altri assembly
  • Microsoft.TeamFoundation.Client.dll, che contiene tutte le classi di connettività ed autenticazione
  • Microsoft.TeamFoundation.WorkItemTracking.Client.dll, che dà la possibilità di navigare e modificare il TFS Work Item Store
  • Microsoft.TeamFoundation.VersionControl.Client.dll, che dà la possibilità di gestire il versioning dei sorgenti;
  • Microsoft.TeamFoundation.Build.Client.dll, che dà la possibilità di interagire con il processo di build automatizzato delle nostre applicazioni.

Server, collezioni e progetti
La classe fondamentale per l'accesso al TFS è la TFSConnection contenuta all'interno del namespace Microsoft.TeamFoundation.Client (assembly Microsoft.TeamFoundation.Client.dll).
TFSConnection è una classe astratta (MustInherit) e rappresenta la connessione del client verso il server che ospita TFS.
Da questa classe derivano due classi concrete che possiamo sfruttare per connetterci al server e recuperare, ad esempio, l'insieme delle collezioni presenti.

Figura3

Le due classi TFSConfigurationServer e TFSTeamProjectCollection consentono, rispettivamente, di accedere ai servizi e le proprietà del server e di una singola project collection.
Entrambe le classi, derivando da TFSConnection, mettono a disposizione la possibilità di autenticarsi e di recuperare il riferimento a un servizio (vedremo più avanti nella serie cosa significa ciò).

Nell'esempio proposto  in questo articolo vogliamo elencare le collection presenti all'interno del server e i progetti contenuti in queste ultime.

Per fare ciò è necessario utilizzare la classe TFSConfigurationServer che mette a disposizione la possibilità di recuperare l'elenco delle collections.
Possiamo ottenere un'istanza di TFSConfigurationServer in due modi:

  • Utilizzare il costruttore della classe TFSConfigurationServer;
  • Utilizzare la classe TFSConfigurationServerFactory che, come tutte le classi factory, si occupa di costruire in maniera opportuna l'oggetto TFSConfigurationServer.

La sostanziale differenza tra le due possibilità è che se utilizziamo il costruttore otteniamo diverse istanze della classe TFSConfigurationServer mentre se utilizziamo la classe Factory, il framework, restituirà sempre la stessa istanza.

Nel nostro caso utilizzeremo la seconda strada.

Tra i tanti overload del metodo GetConfigurationServer della classe statica TFSConfigurationServerFactory utilizzeremo il:

Public Shared Function GetConfigurationServer (uri As Uri,
    fallbackCredentialsProvider As ICredentialsProvider) As TfsConfigurationServer

 

dove uri contiene l'indirizzo del server TFS (in maniera analoga a quanto facciamo nel Team Explorer in Visual Studio quando vogliamo connetterci a TFS), mentre fallbackCredentialProvider è un qualunque oggetto che implementa l'interfaccia ICredentialProvider in grado di "fornire" le credenziali di accesso nel caso in cui servano.

Nei nostri primi esempi utilizzeremo come fornitore di credenziali di accesso la classe UICredentialProvider (del namespace Microsoft.TeamFoundation.Client) che richiede le credenziali con un prompt a video.

Vedremo, in seguito come creare un nostro fornitore di credenziali per tutti quegli scenari in cui non è possibile o non è necessario richiede le credenziali all'utente.

A questo punto, cominciamo a scrivere la nostra classe di servizio per l'accesso a TFS.

Imports Microsoft.TeamFoundation.Client
Imports System.Net
Imports System.Diagnostics.Contracts
Public Class TFSService
    Implements IDisposable

    Public Sub New(ByVal serverUrl As String)
        Me.ServerURL = serverUrl
    End Sub

    Public Sub New()
    End Sub

    Protected Property TFSInstance() As TfsConfigurationServer

    Public Property ServerURL As String

    Public Overridable Function Login() As Boolean
        Dim retval = False
        If TFSInstance IsNot Nothing Then
            TFSInstance.Dispose()
            TFSInstance = Nothing
        End If
        Try
            TFSInstance = TfsConfigurationServerFactory.GetConfigurationServer(New Uri(Me.ServerURL), _
                                                                                   New UICredentialsProvider())
            TFSInstance.Authenticate()
            retval = True
        Catch ex As System.Exception
            retval = False
            TFSInstance = Nothing
        End Try
        Return retval
    End Function

#Region "IDisposable Support"
    .
    .
    .
#End Region

End Class

 

Tralasciamo la sezione contenente l'implementazione dell'interfaccia IDisposable, necessaria per rilasciare correttamente l'eventuale istanza di TFSConfigurationServer tramite la chiamata al metodo Dispose(), ma non interessante in questo contesto.
Nel metodo Login utilizziamo la factory per creare l'istanza di TFSConfigurationServer (sfruttando il fornitore di credenziali che visualizza il popup all'utente) e richiamiamo il metodo Authenticate per eseguire fisicamente l'autenticazione.
In realtà non sarebbe necessario eseguire il metodo Authenticate perché nel momento in cui si utilizza l'istanza di TFSConfigurationServer per recuperare le informazioni dal server, la stessa classe provvede ad eseguire automaticamente il tentativo di autenticazione utilizzando l'ICredentialProvider in caso di fallimento.
Nel nostro metodo di Login è stato utilizzato semplicemente per forzare l'autenticazione e verificare che i dati di accesso siano corretti.
Nello stesso metodo di Login intercettiamo genericamente l'eventuale eccezione generata dal tentativo di connessione e restituiamo False al chiamante.
In uno scenario reale, la risposta al chiamante dovrebbe essere più dettagliata per permettere di capire cosa realmente è successo (il server non esiste, l'utente non ha i privilegi, etc., etc.).
Per implementare questo, l'object model di TFS mette a disposizione una serie di eccezioni che troviamo nel namespace Microsoft.TeamFoundation.

Supponendo, quindi, di essere in grado di verificare che il login funzioni, implementiamo il metodo che consente di recuperare l'elenco delle project collections presenti nel server.
La classe TFSConnection espone una proprietà chiamata CatalogNode (dichiarata MustOverride e di sola lettura) che restituisce un oggetto di tipo CatalogNode che sostanzialmente rappresenta il catalogo delle risorse presenti nel server puntato dalla TFSConnection.
La classe CatalogNode espone il metodo QueryChild la cui funzione è quella di eseguire una query sul catalogo alla ricerca di particolari risorse. Il metodo ha due overloads e utilizzeremo quello con la firma:

Public Function QueryChildren ( _
    resourceTypeFilters As IEnumerable(Of Guid), _
    recurse As Boolean, _
    queryOptions As CatalogQueryOptions) As ReadOnlyCollection(Of CatalogNode)

 

Gli argomenti del metodo hanno la seguente funzione:

  • resourceTypeFilters : è una enumerazione di GUID. Vengono utilizzati i GUID perché tutte le risorse in TFS sono recuperabili tramite questo tipo di identificatore. Anche i tipi di risorse sono recuperabili tramite GUID. La classe CatalogResourceTypes prevede dei campi statici che restituiscono i GUID delle risorse di TFS (per maggiori info http://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.framework.common.catalogresourcetypes.aspx);
  • recurse: permette di eseguire la query in maniera ricorsiva. Nel caso di ricerca di project collection all'interno di un server TFS non ha senso in quanto una project collection non contiene altre project collection;
  • queryOptions: permette di definire le opzioni della query.

Il risultato del metodo è una collezione in sola lettura di CatalogNode che possiamo enumerare per recuperare le informazioni che ci servono.

Nel nostro caso potremmo scrivere il seguente metodo:

Public Function GetCollections() As IEnumerable(Of ProjectCollection)
    Dim retList As IEnumerable(Of ProjectCollection) = Nothing
    If Me.TFSInstance IsNot Nothing Then
        Try
            Dim collections = Me.TFSInstance.CatalogNode().QueryChildren({CatalogResourceTypes.ProjectCollection},
                                                                         False,
                                                                         CatalogQueryOptions.None)
            retList = From o In collections
                     Select New ProjectCollection() With {.Identifier = o.Resource.Identifier,
                                                          .Name = o.Resource.DisplayName,
                                                          .Description = o.Resource.Description}
        Catch ex As Exception
            Throw
        End Try
    End If
    Return retList
End Function

 

La classe ProjectCollection contiene i dati della collection che ci servono (in pratica l'identificativo, il nome e la descrizione).
Lo stesso meccanismo utilizzato per recuperare l'elenco delle collection è applicabile per il recupero dell'elenco dei progetti all'interno di una collection.
Possiamo, infatti, eseguire una query sul server alla ricerca di oggetti di tipo "TeamProject", applicando una procedura ricorsiva e recuperando anche i nodi padre in modo da poter, in seguito, filtrare il risultato in base all'identificativo della collection ricercata.

Possiamo, in definitiva, scrivere:

Public Function GetProjects(ByVal collectionGuid As Guid) As IEnumerable(Of Project)
    Dim retList As IEnumerable(Of Project) = Nothing
    If Me.TFSInstance IsNot Nothing Then
        Try
            Dim projects = Me.TFSInstance.CatalogNode().QueryChildren({CatalogResourceTypes.TeamProject},
                                                                    True,
                                                                    CatalogQueryOptions.IncludeParents)
            retList = From o In projects
                      Where o.ParentNode.Resource.Identifier.Equals(collectionGuid)
                     Select New Project() With {.Identifier = o.Resource.Identifier,
                                                .Name = o.Resource.DisplayName,
                                                .Description = o.Resource.Description}
 
        Catch ex As Exception
            Throw
        End Try
    End If
    Return retList
End Function

 

In realtà, il codice visto in precedenza non è l'unico modo possibile per recuperare I progetti di una collection.
Possiamo, infatti, recuperare preventivamente la collezione utilizzando il metodo GetCollection() della classe TfsConfigurationServer, quindi, eseguire una query sulla proprietà CatalogeNode della collection stessa per recuperare le informazioni dei progetti:

Public Function GetProjects2(ByVal collInstanceId As Guid) As IEnumerable(Of Project)
    Dim retList As IEnumerable(Of Project) = Nothing
    If Me.TFSInstance IsNot Nothing Then
        Try
            Dim collection = Me.TFSInstance.GetTeamProjectCollection(collInstanceId)
            If collection IsNot Nothing Then
                Dim projects = collection.CatalogNode().QueryChildren({CatalogResourceTypes.TeamProject},
                                                                    False,
                                                                    CatalogQueryOptions.None)
 
                retList = From o In projects
                          Select New Project() With {.Identifier = o.Resource.Identifier,
                                                     .Name = o.Resource.DisplayName,
                                                     .Description = o.Resource.Description}
            End If
        Catch ex As Exception
            Throw
        End Try
    End If
    Return retList
End Function

 

Alla fine dell'articolo siamo in grado di realizzare un'applicazione che elenca, dato un server TFS, le sue collezioni e, fissata una collezione, i progetti in essa contenuti (il codice completo dell'applicazione è contanuto nei sorgenti in allegato).

Figura4

Per concludere vediamo come implementare un CredentialsProvider da poter utilizzare dove non è possibile visualizzare un prompt per richiedere le credenziali all'utente (ad esempio un web service o un servizio di windows).

Un credential provider altro non è che una classe che implementa l'interfaccia ICredentialsProvider ed in particolari i due metodi:

  • GetCredentials : viene richiamato quando la connessione a TFS ha necessità di avere un oggetto che implementa ICredentials (ad esempio un oggetto di tipo NetworkCredentials) per autenticare l'utente;
  • NotifyCredentialAuthenticated : viene richiamato dal framework per notificare al provider che le credenziali sono correttamente verificate.

Una semplice classe che utilizza NetworkCredentials per autenticare l'utente è la seguente:

Imports Microsoft.TeamFoundation.Client
Imports System.Net

Friend Class CredentialProvider
    Implements ICredentialsProvider

    Public Sub New(ByVal credentials As ICredentials)
        If credentials Is Nothing Then
            Throw New ArgumentNullException("credentials")
        End If
        Me._Credentials = credentials
    End Sub

    Private _Credentials As ICredentials

    Public Function GetCredentials(ByVal uri As System.Uri, _
                                   ByVal failedCredentials As System.Net.ICredentials) As System.Net.ICredentials _
                               Implements ICredentialsProvider.GetCredentials
        Return _Credentials
    End Function

    Public Sub NotifyCredentialsAuthenticated(ByVal uri As System.Uri) _
        Implements ICredentialsProvider.NotifyCredentialsAuthenticated

    End Sub
End Class

 

Riferimenti

[1] Team Foundation Server Architecture: http://msdn.microsoft.com/en-us/library/ms252473.aspx
[2] Extending Team Foundation: http://msdn.microsoft.com/en-us/library/bb130146.aspx
[3] Team Foundation Server 2010 SDK: http://code.msdn.microsoft.com/TfsSdk
[4] Team Foundation Server Team Blog: http://blogs.msdn.com/b/team_foundation/


Tags: TFS,Team Foundation Server

 
x