Dela via


Hämta arbetsobjekt med frågor programmatiskt

Azure DevOps-tjänster

Att hämta arbetsobjekt med hjälp av frågor är ett vanligt scenario i Azure DevOps Services. Den här artikeln beskriver hur du implementerar det här scenariot programmatiskt med hjälp av REST-API:er eller .NET-klientbibliotek.

Förutsättningar

Kategori Krav
Azure DevOps - En organisation
– Åtkomst till ett projekt med arbetsobjekt
autentisering Använd någon av följande metoder:
- Microsoft Entra ID-autentisering (rekommenderas för interaktiva appar)
- Autentisering med tjänstens huvudnamn (rekommenderas för automatisering)
- Hanterad identitetsautentisering (rekommenderas för Azure-värdbaserade appar)
- Personlig åtkomsttoken (för testning)
Utvecklingsmiljö En C#-utvecklingsmiljö. Du kan använda Visual Studio

Viktigt!

För produktionsprogram rekommenderar vi att du använder Microsoft Entra ID-autentisering i stället för personliga åtkomsttoken (PAT). PAT:er är lämpliga för testnings- och utvecklingsscenarier. Vägledning om hur du väljer rätt autentiseringsmetod finns i Autentiseringsvägledning.

Autentiseringsalternativ

Den här artikeln visar flera autentiseringsmetoder som passar olika scenarier:

För produktionsprogram med användarinteraktion använder du Microsoft Entra ID-autentisering:

<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" />

För automatiserade scenarier, CI/CD-pipelines och serverprogram:

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

För program som körs på Azure-tjänster (Functions, App Service osv.):

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

Autentisering med personlig åtkomsttoken

För utvecklings- och testscenarier:

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

C#-kodexempel

I följande exempel visas hur du hämtar arbetsobjekt med olika autentiseringsmetoder.

Exempel 1: Microsoft Entra ID-autentisering (interaktiv)

// 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"]}");
        }
    }
}

Exempel 2: Autentisering med tjänstens huvudnamn (automatiserade scenarier)

// 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>();
            }
        }
    }
}

Exempel 3: Hanterad identitetsautentisering (Azure-värdbaserade appar)

// 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>();
            }
        }
    }
}

Exempel 4: Autentisering med personlig åtkomsttoken

// 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>();
            }
        }
    }
}

Användningsexempel

Använda Microsoft Entra ID-autentisering (interaktiv)

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

Använda autentisering med tjänstens huvudnamn (CI/CD-scenarier)

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"]}");
        }
    }
}

Använda hanterad identitetsautentisering (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"]
            })
        });
    }
}

Använda autentisering med personlig åtkomsttoken (utveckling/testning)

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"]}");
        }
    }
}

Metodtips

Autentisering

  • Använda Microsoft Entra-ID för interaktiva program med användarinloggning
  • Använda tjänstens huvudnamn för automatiserade scenarier, CI/CD-pipelines och serverprogram
  • Använda hanterad identitet för program som körs på Azure-tjänster (Functions, App Service, virtuella datorer)
  • Undvik personliga åtkomsttoken i produktion. endast för utveckling och testning
  • Hårdkoda aldrig autentiseringsuppgifter i källkoden. använda miljövariabler eller Azure Key Vault
  • Implementera rotation av autentiseringsuppgifter för långvariga program
  • Se till att det finns rätt omfång: Frågor om arbetsobjekt kräver lämpliga läsbehörigheter i Azure DevOps

Felhantering

  • Implementera logik för återförsök med exponentiell backoff för tillfälliga fel
  • Logga fel på ett lämpligt sätt för felsökning och övervakning
  • Hantera specifika undantag som autentiseringsfel och tidsgränser för nätverk
  • Använda annulleringstoken för långvariga åtgärder

Prestanda

  • Hämtning av Batch-arbetsobjekt vid frågor mot flera objekt
  • Begränsa frågeresultat med hjälp av TOP-sats för stora datamängder
  • Cachelagrade data som används ofta för att minska API-anrop
  • Använd lämpliga fält för att minimera dataöverföringen

Frågeoptimering

  • Använd specifika fältnamn i stället för SELECT * för bättre prestanda
  • Lägga till rätt WHERE-satser för att filtrera resultat på servern
  • Ordna resultaten på rätt sätt för ditt användningsfall
  • Överväg frågebegränsningar och paginering för stora resultatuppsättningar

Felsökning

Autentiseringsproblem

  • Microsoft Entra ID-autentiseringsfel: Kontrollera att användaren har rätt behörigheter och är inloggad på Azure DevOps
  • Autentiseringsfel för tjänstens huvudnamn: Kontrollera att klient-ID, hemlighets- och klientorganisations-ID är korrekta. kontrollera behörigheter för tjänstens huvudnamn i Azure DevOps
  • Autentiseringsfel för hanterad identitet: Kontrollera att Azure-resursen har en aktiverad hanterad identitet och rätt behörigheter
  • PAT-autentiseringsfel: Kontrollera att token är giltig och har lämpliga omfång (vso.work för åtkomst till arbetsobjekt)
  • Förfallodatum för token: Kontrollera om din PAT har upphört att gälla och generera en ny om det behövs

Frågor om problem

  • Ogiltig WIQL-syntax: Kontrollera att syntaxen för arbetsobjektets frågespråk är korrekt
  • Projektnamnsfel: Kontrollera att projektnamnet finns och är rättstavat
  • Fältnamnsfel: Använd rätt systemfältnamn (till exempel System.Id, System.Title)

Vanliga undantag

  • VssUnauthorizedException: Kontrollera autentiseringsuppgifter och behörigheter
  • ArgumentException: Kontrollera att alla obligatoriska parametrar har angetts och är giltiga
  • HttpRequestException: Kontrollera nätverksanslutningen och tjänstens tillgänglighet

Prestandaproblem

  • Långsamma frågor: Lägg till lämpliga WHERE-satser och begränsa resultatuppsättningar
  • Minnesanvändning: Bearbeta stora resultatuppsättningar i batchar
  • Hastighetsbegränsning: Implementera logik för återförsök med exponentiell backoff

Nästa steg