Azure DevOps에 대한 SOAP 클라이언트 라이브러리 샘플

Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022

경고

레거시 기술 - 최신 대안 권장

이러한 SOAP 기반 클라이언트는 레거시 기술이며 다음 용도로만 사용해야 합니다.

  • 현대화할 수 없는 기존 애플리케이션 유지 관리
  • SOAP 관련 기능이 필요한 .NET Framework 애플리케이션

새 개발의 경우 다음을 제공하는 최신 REST 기반 .NET 클라이언트 라이브러리를 사용합니다.

  • ✅ 성능 및 안정성 향상
  • ✅ .NET Core, .NET 5 이상 및 .NET Framework 지원
  • ✅ 최신 인증 방법(관리 ID, 서비스 주체)
  • ✅ 비동기/await 패턴 및 최신 C# 기능
  • ✅ 활성 개발 및 지원

이 문서에는 레거시 SOAP 클라이언트를 사용하여 Azure DevOps Server 및 Azure DevOps Services와 통합하기 위한 샘플이 포함되어 있습니다. 이러한 클라이언트는 .NET Framework 버전에서만 사용할 수 있으며 온-프레미스 또는 레거시 인증 방법이 필요합니다.

필수 구성 요소 및 제한 사항

요구 사항:

  • .NET Framework 4.6.1 이상
  • 레거시 NuGet 패키지
  • SOAP 클라이언트 지원을 위한 Windows 환경

제한 사항:

  • ❌ .NET Core 또는 .NET 5 이상 지원 없음
  • ❌ 제한된 최신 인증 옵션
  • ❌ 비동기/대기 패턴 없음
  • ❌ REST 클라이언트에 비해 성능 저하
  • ❌ 제한된 향후 지원 및 업데이트

필수 NuGet 패키지:

마이그레이션 지침

1단계: 현재 사용량 평가

  • 애플리케이션에서 사용하는 SOAP 관련 기능 식별
  • 동등한 REST API를 사용할 수 있는지 확인
  • 인증 요구 사항 평가

2단계: 마이그레이션 전략 계획

  • 즉시 실행: Microsoft Entra ID를 사용하도록 인증 업데이트
  • 단기: .NET Framework를 유지하면서 REST 기반 클라이언트로 마이그레이션
  • 장기: REST 클라이언트를 사용하여 .NET Core/.NET 5 이상으로 현대화

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;
        }
    }
}

레거시 인증 방법

경고

이러한 인증 방법에는 보안 제한이 있습니다. 가능하면 최신 인증 으로 마이그레이션합니다.

중요합니다

위험 수준이 높은 개인용 액세스 토큰보다 더 안전한 Microsoft Entra토큰을 사용하는 것이 좋습니다. 자세한 내용은 PAT 사용량 줄이기를 참조하세요. 인증 지침을 검토하여 요구 사항에 적합한 인증 메커니즘을 선택합니다.

PAT를 사용해야 하는 경우 개인용 액세스 토큰을 사용하여 만들기를 참조하세요. 그런 다음 VssBasicCredential로 전달합니다.

var credentials = new VssBasicCredential(string.Empty, personalAccessToken);

using (var tpc = new TfsTeamProjectCollection(new Uri(collectionUri), credentials))
{
    tpc.Authenticate();
}

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;
    }
}

사용자 이름/암호 인증(사용되지 않음)

주의

사용자 이름/암호 인증은 더 이상 사용되지 않으며 안전하지 않습니다. 대신 최신 인증 방법을 사용합니다.

/// <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 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. Migrate to 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))
            {
                // Use PAT authentication (consider migrating to modern auth)
                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 검사 완료
향후 지원 유지 관리만 활성 개발
코드 패턴 동기 차단 비동기/대기 패턴

레거시 클라이언트 문제 해결

일반적인 문제 및 해결 방법

인증 실패:

  • PAT에 적절한 범위가 있는지 확인
  • 조직 URL 형식 확인(온-프레미스에 대한 컬렉션 포함)
  • SOAP 엔드포인트에 대한 방화벽 및 프록시 설정 확인

성능 문제:

  • SOAP 클라이언트는 기본적으로 REST보다 느립니다.
  • 가능한 경우 일괄 처리 작업 고려
  • 성능 향상을 위해 REST 클라이언트로 마이그레이션

플랫폼 호환성:

  • SOAP 클라이언트는 .NET Framework에서만 작동합니다.
  • 플랫폼 간 지원을 위해 REST 클라이언트 사용

도움 받기

레거시 SOAP 클라이언트 문제의 경우:

  1. Azure DevOps 개발자 커뮤니티 확인
  2. 최신 대안에 대한 마이그레이션 지침 검토
  3. 대규모 애플리케이션에 대한 전문 마이그레이션 서비스 고려

마이그레이션 리소스:

레거시 설명서:

중요합니다

마이그레이션을 계획하고 있나요? 최신 .NET 클라이언트 라이브러리 샘플부터 시작하여 현재 모범 사례 및 인증 옵션을 확인합니다.