Azure DevOps Services |Azure DevOps Server |Azure DevOps Server 2022 |Azure DevOps Server 2020
警告
旧技术 - 建议使用新式替代方法
这些基于 SOAP 的客户端是旧技术,只能用于:
- 维护无法现代化的现有应用程序
- 需要特定于 SOAP 功能的 .NET Framework 应用程序
对于新开发,请使用提供以下功能的新 式基于 REST 的 .NET 客户端库 :
- ✅ 更好的性能和可靠性
- ✅ 支持 .NET Core、.NET 5+和 .NET Framework
- ✅ 新式身份验证方法(托管标识、服务主体)
- ✅ 异步/等待模式和新式 C# 功能
- ✅ 主动开发和支持
本文包含使用旧版 SOAP 客户端与 Azure DevOps Server 和 Azure DevOps Services 集成的示例。 这些客户端仅在 .NET Framework 版本中可用,需要本地或旧式身份验证方法。
先决条件和限制
要求:
- .NET Framework 4.6.1 或更高版本
- 旧版 NuGet 包
- 适用于 SOAP 客户端支持的 Windows 环境
局限性:
- ❌ 不支持 .NET Core 或 .NET 5+
- ❌ 有限的新式身份验证选项
- ❌ 无异步/等待模式
- ❌ 与 REST 客户端相比,性能降低
- ❌ 有限的未来支持和更新
所需的 NuGet 包:
- Microsoft.TeamFoundationServer.ExtendedClient - 旧版 SOAP 客户端
- Microsoft.TeamFoundationServer.Client - Core Azure DevOps API
- Microsoft.VisualStudio.Services.Client - 连接和身份验证
- Microsoft.VisualStudio.Services.InteractiveClient - 交互式身份验证流
迁移指南
建议的迁移路径
步骤 1:评估当前使用情况
- 确定应用程序使用的特定于 SOAP 的功能
- 确定等效的 REST API 是否可用
- 评估身份验证要求
步骤 2:规划迁移策略
- 立即:更新身份验证以使用 PAT 或 Microsoft Entra ID
- 短期:在保留 .NET Framework 的同时迁移到基于 REST 的客户端
- 长期:将系统现代化至 .NET Core/.NET 5+,并使用 REST 客户端
步骤 3:实现迁移
- 从身份验证更新开始。 请参阅以下示例。
- 以增量方式将 SOAP 客户端替换为 REST 等效项
- 在部署到生产环境之前进行全面测试
有关详细的迁移指南,请参阅 .NET 客户端库示例。
旧版 SOAP 客户端示例
基本 SOAP 客户端用法
重要
此示例仅显示用于引用的旧模式。 使用 基于 REST 的示例 进行新开发。
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Linq;
/// <summary>
/// Legacy SOAP client example - use REST clients for new development
/// Creates a work item query, runs it, and displays results
/// </summary>
public static class LegacySoapExample
{
public static void ExecuteWorkItemQuery(string collectionUri, string teamProjectName, VssCredentials credentials)
{
try
{
// Create TfsTeamProjectCollection instance with credentials
using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
// Authenticate the connection
tpc.Authenticate();
// Get the WorkItemStore service (SOAP-based)
var workItemStore = tpc.GetService<WorkItemStore>();
// Get the project context
var workItemProject = workItemStore.Projects[teamProjectName];
// Find 'My Queries' folder
var myQueriesFolder = workItemProject.QueryHierarchy
.OfType<QueryFolder>()
.FirstOrDefault(qh => qh.IsPersonal);
if (myQueriesFolder != null)
{
const string queryName = "Legacy SOAP Sample";
// Check if query already exists
var existingQuery = myQueriesFolder
.OfType<QueryDefinition>()
.FirstOrDefault(qi => qi.Name.Equals(queryName, StringComparison.OrdinalIgnoreCase));
QueryDefinition queryDefinition;
if (existingQuery == null)
{
// Create new query with proper WIQL
queryDefinition = new QueryDefinition(
queryName,
@"SELECT [System.Id], [System.WorkItemType], [System.Title],
[System.AssignedTo], [System.State], [System.Tags]
FROM WorkItems
WHERE [System.TeamProject] = @project
AND [System.WorkItemType] = 'Bug'
AND [System.State] = 'New'
ORDER BY [System.CreatedDate] DESC");
myQueriesFolder.Add(queryDefinition);
workItemProject.QueryHierarchy.Save();
}
else
{
queryDefinition = existingQuery;
}
// Execute the query
var workItems = workItemStore.Query(queryDefinition.QueryText);
Console.WriteLine($"Found {workItems.Count} work items:");
foreach (WorkItem workItem in workItems)
{
var title = workItem.Fields["System.Title"].Value;
var state = workItem.Fields["System.State"].Value;
Console.WriteLine($"#{workItem.Id}: {title} [{state}]");
}
if (workItems.Count == 0)
{
Console.WriteLine("No work items found matching the query criteria.");
}
}
else
{
Console.WriteLine("'My Queries' folder not found.");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error executing SOAP query: {ex.Message}");
throw;
}
}
}
旧式身份验证方法
警告
这些身份验证方法具有安全限制。 尽可能迁移到 新式身份验证 。
个人访问令牌身份验证(不建议)
/// <summary>
/// Authenticate SOAP client using Personal Access Token
/// Most secure option for legacy SOAP clients
/// </summary>
public static void AuthenticateWithPAT(string collectionUri, string personalAccessToken)
{
try
{
var credentials = new VssBasicCredential(string.Empty, personalAccessToken);
using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
tpc.Authenticate();
Console.WriteLine($"Successfully authenticated to: {tpc.DisplayName}");
Console.WriteLine($"Instance ID: {tpc.InstanceId}");
}
}
catch (Exception ex)
{
Console.WriteLine($"PAT authentication failed: {ex.Message}");
throw;
}
}
Microsoft Entra 身份验证(有限支持)
/// <summary>
/// Microsoft Entra authentication for SOAP services
/// Limited to specific scenarios - prefer REST clients for modern auth
/// </summary>
public static void AuthenticateWithEntraID(string collectionUri)
{
try
{
// Note: Limited authentication options compared to REST clients
var credentials = new VssAadCredential();
using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
tpc.Authenticate();
Console.WriteLine($"Successfully authenticated with Microsoft Entra ID");
Console.WriteLine($"Collection: {tpc.DisplayName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Microsoft Entra authentication failed: {ex.Message}");
Console.WriteLine("Consider migrating to REST clients for better authentication support.");
throw;
}
}
交互式身份验证(仅限 .NET Framework)
/// <summary>
/// Interactive authentication with Visual Studio sign-in prompt
/// Only works in .NET Framework with UI context
/// </summary>
public static void AuthenticateInteractively(string collectionUri)
{
try
{
var credentials = new VssClientCredentials();
using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
tpc.Authenticate();
Console.WriteLine($"Interactive authentication successful");
Console.WriteLine($"Authenticated user: {tpc.AuthorizedIdentity.DisplayName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Interactive authentication failed: {ex.Message}");
Console.WriteLine("Ensure application has UI context and user interaction is possible.");
throw;
}
}
用户名/密码身份验证(已弃用)
谨慎
用户名/密码身份验证已弃用,不安全。 请改用 PAT 或新式身份验证方法。
/// <summary>
/// Username/password authentication - DEPRECATED AND INSECURE
/// Only use for legacy on-premises scenarios where no alternatives exist
/// </summary>
[Obsolete("Username/password authentication is deprecated. Use PATs or modern authentication.")]
public static void AuthenticateWithUsernamePassword(string collectionUri, string username, string password)
{
try
{
var credentials = new VssAadCredential(username, password);
using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
tpc.Authenticate();
Console.WriteLine("Username/password authentication successful (DEPRECATED)");
}
}
catch (Exception ex)
{
Console.WriteLine($"Username/password authentication failed: {ex.Message}");
Console.WriteLine("This method is deprecated. Please migrate to PATs or modern authentication.");
throw;
}
}
完整遗留示例
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Configuration;
/// <summary>
/// Complete example showing legacy SOAP client usage
/// For reference only - use REST clients for new development
/// </summary>
class LegacySoapProgram
{
static void Main(string[] args)
{
try
{
// Get configuration (prefer environment variables or secure config)
var collectionUri = ConfigurationManager.AppSettings["CollectionUri"];
var projectName = ConfigurationManager.AppSettings["ProjectName"];
var personalAccessToken = ConfigurationManager.AppSettings["PAT"]; // Store securely
if (string.IsNullOrEmpty(collectionUri) || string.IsNullOrEmpty(projectName))
{
Console.WriteLine("Please configure CollectionUri and ProjectName in app.config");
return;
}
Console.WriteLine("=== Legacy SOAP Client Example ===");
Console.WriteLine("WARNING: This uses deprecated SOAP clients.");
Console.WriteLine("Consider migrating to REST clients for better performance and support.");
Console.WriteLine();
VssCredentials credentials;
if (!string.IsNullOrEmpty(personalAccessToken))
{
// Recommended: Use PAT authentication
credentials = new VssBasicCredential(string.Empty, personalAccessToken);
Console.WriteLine("Using Personal Access Token authentication");
}
else
{
// Fallback: Interactive authentication (requires UI)
credentials = new VssClientCredentials();
Console.WriteLine("Using interactive authentication");
}
// Execute the legacy SOAP example
LegacySoapExample.ExecuteWorkItemQuery(collectionUri, projectName, credentials);
Console.WriteLine();
Console.WriteLine("Example completed successfully.");
Console.WriteLine("For new development, see: https://docs.microsoft.com/azure/devops/integrate/concepts/dotnet-client-libraries");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine();
Console.WriteLine("Migration recommendations:");
Console.WriteLine("1. Update to REST-based client libraries");
Console.WriteLine("2. Use modern authentication (managed identities, service principals)");
Console.WriteLine("3. Migrate to .NET Core/.NET 5+ for better performance");
Environment.Exit(1);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
迁移到新式客户端
并列比较
旧版 SOAP 方法:
// ❌ Legacy SOAP pattern
using (var tpc = new TfsTeamProjectCollection(uri, credentials))
{
var workItemStore = tpc.GetService<WorkItemStore>();
var workItems = workItemStore.Query("SELECT * FROM WorkItems");
// Synchronous, blocking operations
}
新式 REST 方法:
// ✅ Modern REST pattern
using var connection = new VssConnection(uri, credentials);
var witClient = connection.GetClient<WorkItemTrackingHttpClient>();
var workItems = await witClient.QueryByWiqlAsync(new Wiql { Query = "SELECT * FROM WorkItems" });
// Asynchronous, non-blocking operations
主要差异
| 功能 / 特点 | 旧式 SOAP | 新式 REST |
|---|---|---|
| 平台支持 | 仅限 .NET Framework | .NET Framework、.NET Core、.NET 5+ |
| 性能 | 速度较慢,同步 | 更快、异步 |
| 身份验证 | 受限选项 | 完全新式身份验证支持 |
| API 覆盖范围 | 仅限旧版 API | 完整的 REST API 覆盖范围 |
| 未来支持 | 仅限维护 | 主动开发 |
| 代码模式 | 同步阻塞 | Async/await 模式 |
对旧客户端进行故障排除
常见问题和解决方案
身份验证失败:
- 确保 PAT 具有适当的范围
- 验证组织 URL 格式(包括内部部署环境的集合)
- 检查 SOAP 终结点的防火墙和代理设置
性能问题:
- SOAP 客户端本质上比 REST 慢
- 尽可能考虑批处理操作
- 迁移到 REST 客户端以提高性能
平台兼容性:
- SOAP 客户端仅适用于 .NET Framework
- 使用 REST 客户端进行跨平台支持
获取帮助
对于旧版 SOAP 客户端问题:
- 访问Azure DevOps 开发人员社区
- 查阅现代替代方案的迁移指南
- 考虑大型应用程序的专业迁移服务
相关资源
迁移资源:
- 新式 .NET 客户端库示例 - 建议替换
- 身份验证指南 - 新式身份验证选项
- Azure DevOps REST API 参考 - 完整的 API 文档
旧版文档:
重要
规划迁移? 从 新式 .NET 客户端库示例 开始,查看当前的最佳做法和身份验证选项。