Delen via


Werkitems opvragen met zoekopdrachten op programmatische wijze

Azure DevOps Services

Het ophalen van werkitems met behulp van query's is een veelvoorkomend scenario in Azure DevOps Services. In dit artikel wordt uitgelegd hoe u dit scenario programmatisch implementeert met behulp van REST API's of .NET-clientbibliotheken.

Vereisten

Categorie Vereisten
Azure DevOps - Een organisatie
- Toegang tot een project met werkitems
Authenticatie Kies één van de volgende methoden:
- Microsoft Entra ID-verificatie (aanbevolen voor interactieve apps)
- Verificatie van service-principal (aanbevolen voor automatisering)
- Verificatie van beheerde identiteit (aanbevolen voor door Azure gehoste apps)
- Persoonlijk toegangstoken (voor testen)
Ontwikkelomgeving Een C#-ontwikkelomgeving. U kunt Visual Studio gebruiken

Belangrijk

Voor productietoepassingen raden we u aan Microsoft Entra ID-verificatie te gebruiken in plaats van PERSOONLIJKE toegangstokens (PAT's). PAT's zijn geschikt voor test- en ontwikkelingsscenario's. Zie de richtlijnen voor verificatie voor hulp bij het kiezen van de juiste verificatiemethode.

Verificatieopties

In dit artikel worden meerdere verificatiemethoden beschreven die geschikt zijn voor verschillende scenario's:

Gebruik Microsoft Entra ID-verificatie voor productietoepassingen met gebruikersinteractie:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />

Voor geautomatiseerde scenario's, CI/CD-pijplijnen en servertoepassingen:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />

Voor toepassingen die worden uitgevoerd op Azure-services (Functions, App Service, enzovoort):

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />

Verificatie van persoonlijk toegangstoken

Voor ontwikkelings- en testscenario's:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.1" />

Voorbeelden van C#-code

In de volgende voorbeelden ziet u hoe u werkitems ophaalt met behulp van verschillende verificatiemethoden.

Voorbeeld 1: Microsoft Entra ID-verificatie (interactief)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.VisualStudio.Services.InteractiveClient  
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class EntraIdQueryExecutor
{
    private readonly Uri uri;

    /// <summary>
    /// Initializes a new instance using Microsoft Entra ID authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    public EntraIdQueryExecutor(string orgName)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
    }

    /// <summary>
    /// Execute a WIQL query using Microsoft Entra ID authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Use Microsoft Entra ID authentication
        var credentials = new VssAadCredential();
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = '" + project + "' " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }

    /// <summary>
    /// Print the results of the work item query.
    /// </summary>
    public async Task PrintOpenBugsAsync(string project)
    {
        var workItems = await this.QueryOpenBugsAsync(project).ConfigureAwait(false);
        Console.WriteLine($"Query Results: {workItems.Count} items found");

        foreach (var workItem in workItems)
        {
            Console.WriteLine($"{workItem.Id}\t{workItem.Fields["System.Title"]}\t{workItem.Fields["System.State"]}");
        }
    }
}

Voorbeeld 2: Verificatie van service-principals (geautomatiseerde scenario's)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class ServicePrincipalQueryExecutor
{
    private readonly Uri uri;
    private readonly string clientId;
    private readonly string clientSecret;
    private readonly string tenantId;

    /// <summary>
    /// Initializes a new instance using Service Principal authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    /// <param name="clientId">Service principal client ID</param>
    /// <param name="clientSecret">Service principal client secret</param>
    /// <param name="tenantId">Azure AD tenant ID</param>
    public ServicePrincipalQueryExecutor(string orgName, string clientId, string clientSecret, string tenantId)
    {
        this.uri = new Uri($"https://dev.azure.com/{orgName}");
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.tenantId = tenantId;
    }

    /// <summary>
    /// Execute a WIQL query using Service Principal authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Acquire token using Service Principal
        var app = ConfidentialClientApplicationBuilder
            .Create(this.clientId)
            .WithClientSecret(this.clientSecret)
            .WithAuthority($"https://login.microsoftonline.com/{this.tenantId}")
            .Build();

        var scopes = new[] { "https://app.vssps.visualstudio.com/.default" };
        var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

        var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = '" + project + "' " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Voorbeeld 3: Verificatie van beheerde identiteit (door Azure gehoste apps)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class ManagedIdentityQueryExecutor
{
    private readonly Uri uri;

    /// <summary>
    /// Initializes a new instance using Managed Identity authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    public ManagedIdentityQueryExecutor(string orgName)
    {
        this.uri = new Uri($"https://dev.azure.com/{orgName}");
    }

    /// <summary>
    /// Execute a WIQL query using Managed Identity authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Use Managed Identity to acquire token
        var credential = new DefaultAzureCredential();
        var tokenRequestContext = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
        var tokenResult = await credential.GetTokenAsync(tokenRequestContext);

        var credentials = new VssOAuthAccessTokenCredential(tokenResult.Token);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = '" + project + "' " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Voorbeeld 4: Verificatie van persoonlijk toegangstoken

// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class PatQueryExecutor
{
    private readonly Uri uri;
    private readonly string personalAccessToken;

    /// <summary>
    /// Initializes a new instance using Personal Access Token authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    /// <param name="personalAccessToken">Your Personal Access Token</param>
    public PatQueryExecutor(string orgName, string personalAccessToken)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
        this.personalAccessToken = personalAccessToken;
    }

    /// <summary>
    /// Execute a WIQL query using Personal Access Token authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = '" + project + "' " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Voorbeelden van gebruik

Verificatie van Microsoft Entra-id gebruiken (interactief)

class Program
{
    static async Task Main(string[] args)
    {
        var executor = new EntraIdQueryExecutor("your-organization-name");
        await executor.PrintOpenBugsAsync("your-project-name");
    }
}

Verificatie van service-principal gebruiken (CI/CD-scenario's)

class Program
{
    static async Task Main(string[] args)
    {
        // These values should come from environment variables or Azure Key Vault
        var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
        var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
        var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
        
        var executor = new ServicePrincipalQueryExecutor("your-organization-name", clientId, clientSecret, tenantId);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs via automation");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

Verificatie van beheerde identiteiten gebruiken (Azure Functions/App Service)

public class WorkItemQueryFunction
{
    [FunctionName("QueryOpenBugs")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        ILogger log)
    {
        var executor = new ManagedIdentityQueryExecutor("your-organization-name");
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        return new OkObjectResult(new { 
            Count = workItems.Count,
            Items = workItems.Select(wi => new { 
                Id = wi.Id, 
                Title = wi.Fields["System.Title"],
                State = wi.Fields["System.State"]
            })
        });
    }
}

Gebruik maken van persoonlijk toegangstoken-verificatie (ontwikkeling/testen)

class Program
{
    static async Task Main(string[] args)
    {
        var pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT"); // Never hardcode PATs
        var executor = new PatQueryExecutor("your-organization-name", pat);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

Beste praktijken

Authenticatie

  • Microsoft Entra ID gebruiken voor interactieve toepassingen met gebruikersaanmelding
  • Service-principal gebruiken voor geautomatiseerde scenario's, CI/CD-pijplijnen en servertoepassingen
  • Beheerde identiteit gebruiken voor toepassingen die worden uitgevoerd op Azure-services (Functions, App Service, VM's)
  • Vermijd persoonlijke toegangstokens in productie; alleen gebruiken voor ontwikkeling en testen
  • Codeer nooit referenties in broncode; omgevingsvariabelen of Azure Key Vault gebruiken
  • Referentierotatie implementeren voor langlopende toepassingen
  • Zorg voor de juiste toepassingsbereiken: Voor werkitemquery's zijn gepaste leestoegangsrechten in Azure DevOps vereist.

Foutafhandeling

  • Logica voor opnieuw proberen implementeren met exponentieel uitstel voor tijdelijke fouten
  • Logboekfouten op de juiste wijze registreren voor foutopsporing en bewaking
  • Specifieke uitzonderingen verwerken , zoals verificatiefouten en netwerktime-outs
  • Annuleringstokens gebruiken voor langdurige bewerkingen

Prestatie

  • Batch-werkitem ophalen bij het uitvoeren van query's op meerdere items
  • Queryresultaten beperken met de TOP-component voor grote gegevenssets
  • Veelgebruikte gegevens in de cache opslaan om API-aanroepen te verminderen
  • De juiste velden gebruiken om de gegevensoverdracht te minimaliseren

Queryoptimalisatie

  • Specifieke veldnamen gebruiken in plaats van SELECT * voor betere prestaties
  • Voeg de juiste WHERE-componenten toe om resultaten op de server te filteren
  • Sorteer de resultaten op de juiste wijze voor uw gebruikssituatie
  • Overweeg querylimieten en paginering voor grote resultatensets

Probleemoplossingsproces

Verificatieproblemen

  • Verificatiefouten met Microsoft Entra ID: zorg ervoor dat de gebruiker over de juiste machtigingen beschikt en is aangemeld bij Azure DevOps
  • Verificatiefouten met service-principal: controleer of de client-id, het geheim en de tenant-id juist zijn; service-principalmachtigingen controleren in Azure DevOps
  • Mislukte verificatie van beheerde identiteit: zorg ervoor dat de Azure-resource een beheerde identiteit heeft ingeschakeld en de juiste machtigingen heeft
  • PAT-verificatiefouten: controleer of het token geldig is en de juiste scopes heeft (vso.work voor de toegang tot werkitems)
  • Verloop van token: controleer of uw PAT is verlopen en genereer indien nodig een nieuwe

Problemen met query's

  • Ongeldige WIQL-syntaxis: zorg ervoor dat de syntaxis van uw querytaal voor werkitems juist is
  • Fouten met projectnamen: controleer of de projectnaam bestaat en juist is gespeld
  • Veldnaamfouten: gebruik de juiste systeemveldnamen (bijvoorbeeld System.Id, System.Title)

Algemene uitzonderingen

  • VssUnauthorizedException: Verificatiereferenties en -machtigingen controleren
  • ArgumentException: controleer of alle vereiste parameters zijn opgegeven en geldig
  • HttpRequestException: Netwerkconnectiviteit en servicebeschikbaarheid controleren

Prestatieproblemen

  • Trage query's: Voeg de juiste WHERE-clausules toe en beperk de resultaatsets.
  • Geheugengebruik: Grote resultatensets verwerken in batches
  • Snelheidsbeperking: Logica voor opnieuw proberen implementeren met exponentieel uitstel

Volgende stappen