Jezda.Common.Integrations.AzureDevOps 3.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Jezda.Common.Integrations.AzureDevOps --version 3.0.0
                    
NuGet\Install-Package Jezda.Common.Integrations.AzureDevOps -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Jezda.Common.Integrations.AzureDevOps" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Jezda.Common.Integrations.AzureDevOps" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Jezda.Common.Integrations.AzureDevOps" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Jezda.Common.Integrations.AzureDevOps --version 3.0.0
                    
#r "nuget: Jezda.Common.Integrations.AzureDevOps, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Jezda.Common.Integrations.AzureDevOps@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Jezda.Common.Integrations.AzureDevOps&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Jezda.Common.Integrations.AzureDevOps&version=3.0.0
                    
Install as a Cake Tool

Jezda.Common.Integrations.AzureDevOps

A strongly-typed .NET client for Azure DevOps REST API, built on top of Jezda.Common.Integrations. This library simplifies interaction with Azure DevOps by providing easy-to-use methods for Work Items and WIQL queries.

✨ Features

  • Work Item Management: Create and retrieve work items.
  • WIQL Support: Execute raw WIQL queries and get typed results.
  • Project Management: List all projects in the organization.
  • Dependency Injection: Ready-to-use IServiceCollection extension.

📦 Installation

Add the project reference or NuGet package:

dotnet add package Jezda.Common.Integrations.AzureDevOps

⚙️ Configuration

Add the following section to your appsettings.json:

{
  "Integrations": {
    "AzureDevOps": {
      "BaseUrl": "https://dev.azure.com/your-organization/",
      "PersonalAccessToken": "your-pat-token",
      "ApiVersion": "7.1"
    }
  }
}

🚀 Usage

1. Register Services

In your Program.cs:

using Jezda.Common.Integrations.Extensions;

builder.Services.AddAzureDevOpsIntegration(builder.Configuration);

2. Inject and Use

Inject IAzureDevOpsClient into your service:

public class MyService
{
    private readonly IAzureDevOpsClient _adoClient;

    public MyService(IAzureDevOpsClient adoClient)
    {
        _adoClient = adoClient;
    }

    public async Task SyncDataAsync()
    {
        // 1. Get all projects
        var projects = await _adoClient.GetProjectsAsync();

        // 2. Get work items via WIQL
        var workItems = await _adoClient.GetWorkItemsByQueryAsync(
            "SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Active'");
    }
}

GetWorkItemsByQueryAsync takes flat queries only. A link query (FROM WorkItemLinks) throws NotSupportedException. The result can hold fewer items than the query matched: an item deleted or made unreadable after the query ran is left out, with a warning in the log that names the organisation and the missing ids. A batch answered without a list throws instead, because it says nothing about the items in it.

🔐 Errors and authentication

Both IAzureDevOpsClient and the IExternalTaskProvider for Azure DevOps report failures as HttpRequestException with StatusCode set, so a caller can tell a rejected token from anything else:

What happened Result
Token rejected (401, 403) StatusCode 401 / 403
Sign-in page instead of JSON (a redirect to /_signin, or 203 without JSON) StatusCode 401; for a redirect, the message names the target's host and path.
A body that is not JSON on any other success status (the base URL points at a website, a proxy or a captive portal answered), 204 included: every call reads JSON, so an empty answer is not read as no results or not found. StatusCode is the success status itself, e.g. 200 or 204. The token was not checked, so this is not 401.
Work item batch answered JSON without a list (a null body, or no value) StatusCode is null: the answer was a success, so the status says nothing. It is thrown after the response is read, so the resilience pipeline does not retry it. The message names the organisation and the batch's first and last id.
Base URL redirects anywhere else (3xx, e.g. after an organisation rename) StatusCode is the 3xx; the message names the target. Redirects are not followed, because .NET drops Authorization on the next hop and a valid PAT would read as expired.
Any other failure Its own status, or the original exception for dropped connections and cancellation. A timeout on IAzureDevOpsClient is TaskCanceledException from HttpClient.Timeout.
Resilience pipeline on the IExternalTaskProvider client gave up Polly's TimeoutRejectedException, BrokenCircuitException or RateLimiterRejectedException. None derives from HttpRequestException, so also catch their common base, ExecutionRejectedException.

ValidateConnectionAsync returns a rejection only for a rejected token. Every other failure propagates, so an outage is not shown to the user as a bad PAT.

Answer On _apis/projects On _apis/wit/fields
401 InvalidToken MissingScope
403 AccessDenied MissingScope
sign-in page or redirect to one SignInPage MissingScope

The work item check runs only once projects passed, with the same token from the same address, so any refusal there is the missing "Work Items (Read)" scope, not a bad token, Conditional Access or an address block. For a sign-in page, Detail names the redirect target (host and path), which tells Azure DevOps from a gateway with its own /_signin.

A 403 on _apis/projects is not always about the token: Conditional Access, an IP allow-list or a WAF can answer it for a valid PAT. Validation still returns a rejection, AccessDenied, and logs a warning with the start of the response body, which tells these apart.

Validation sends two requests: _apis/projects, which needs "Project and Team (Read)", and _apis/wit/fields, which needs "Work Items (Read)". A PAT missing either scope returns a rejection, instead of validating and failing later on the first work item call.

Keeping redirects off

AddAzureDevOpsIntegration turns off redirects on the default primary handler. Without that, a redirect drops Authorization and a valid PAT reads as expired. It also sets the X-TFS-FedAuthRedirect: Suppress header, which asks for 401 instead of a redirect to sign-in. Four setups need care:

  • A custom primary handler registered before AddAzureDevOpsIntegration: one that is not SocketsHttpHandler or HttpClientHandler stays as it is, and a warning is logged. Make sure it does not follow redirects.
  • ConfigurePrimaryHttpMessageHandler called after AddAzureDevOpsIntegration: the handler replaces the configured one and nothing warns, so set AllowAutoRedirect = false on it yourself.
  • AzureDevOpsClient given an HttpClient made outside DI: set AllowAutoRedirect = false on its handler, and send the header yourself.
  • AzureDevOpsTaskProvider built on your own IHttpClientFactory: the same two apply to the client it creates under AzureDevOpsTaskProvider.HttpClientName.

🛠️ Key Models

  • AdoWorkItem: Represents a simplified work item with generic Fields dictionary. Url is the REST resource, which answers JSON; HtmlUrl is the page a person opens (_links.html.href, asked for with $expand=links), null when the answer had no links.
  • AdoProject: Contains project details (Id, Name, Description, Url).
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.1 87 11/10/2026
3.1.0 80 11/10/2026
3.0.0 76 11/10/2026
2.0.0 91 11/8/2026
1.2.2 217 10/5/2026
1.2.1 103 10/5/2026
1.2.0 259 8/15/2026
1.1.0 229 7/5/2026
1.0.38 190 5/11/2026
1.0.37 635 3/12/2026
1.0.36 138 3/8/2026
1.0.35 164 2/17/2026