Search code examples
apisdkclientazure-devops-server-2019

Error 'Value cannot be null:nodeName' when creating new iteration


I'm getting an error when attempting to create a new Iteration using the Client SDK:

Value cannot be null. Parameter name: nodeName

As a test, I tried creating it using Postman and the REST API—as suggested here—and it succeeded.

I've been using this successfully for quite a while now to stub out my sprint hierarchy for the year. This is the first such occurrence of this error—past year runs have gone off without a hitch. I haven't changed anything (that I know of) since last year's successful run.

As we can see, the iteration's Name property is being properly set. I tried Overloads instead of Shadows, but that didn't help.

How can I troubleshoot this to find out what nodeName is and how to populate it using the Client SDK?

Here's my code:

Module Main()
  Private Sub AddYear(Year As Integer, Client As WorkItemTrackingHttpClient)
    Dim oIterationYear As Classifications.Iteration
    Dim dFinishDate As Date
    Dim dStartDate As Date

    Console.WriteLine($"Year:{vbTab}{vbTab}{Year}")

    dFinishDate = New Date(Year, 12, 31)
    dStartDate = New Date(Year, 1, 1)

    oIterationYear = New Classifications.Iteration(Client, TeamProject, Year, dStartDate, dFinishDate)
    oIterationYear.Save()

    ...

  End Sub
End Module

Public Class Iteration
  Inherits Base

  Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date)
    Me.New(Client, TeamProject, Name, StartDate, FinishDate, Nothing)
  End Sub

  Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date, Parent As Iteration)
    MyBase.New(Client, TeamProject, Parent)

    Me.StructureType = TreeNodeStructureType.Iteration
    Me.FinishDate = FinishDate
    Me.StartDate = StartDate
    Me.Name = Name
  End Sub

  ...

End Class

Public MustInherit Class Base
  Inherits WorkItemClassificationNode

  Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
    Me.ProjectName = TeamProject.ToDescription
    Me.Parent = Parent
    Me.Client = Client
  End Sub

  Public Sub Save()
    If Me.Parent.IsNothing Then
      Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType).Result <-- Error
    Else
      Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
    End If
  End Sub

  ...

  Public Shadows Property Name As String
    Get
      If Me.Node.IsNothing Then
        Name = Me._Name
      Else
        Name = Me.Node.Name
      End If
    End Get
    Set(Value As String)
      Me._Name = Value
    End Set
  End Property
  Private _Name As String
End Class

Note: this is a language-agnostic question, thus I've intentionally omitted the VB.NET tag. An answer can come in either VB.NET or C#—I'm fine with either one.

-- EDIT --

Based on the design suggestions found in the accepted answer, I've come up with this solution that works:

Public MustInherit Class Base
  Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
    Me.Node = New WorkItemClassificationNode With {
      .StructureType = StructureType,
      .Name = Name
    }

    Me.ProjectName = TeamProject.ToDescription
    Me.Parent = Parent
    Me.Client = Client
    Me.Name = Name
  End Sub

  Public Sub Save()
    If Me.Parent.IsNothing Then
      Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType).Result
    Else
      Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
    End If
  End Sub

  ...

  Public Property Name As String
    Get
      Return Me.Node.Name
    End Get
    Private Set(Value As String)
      Me.Node.Name = Value
    End Set
  End Property
End Class

Essentially all I did was remove the base class' inheritance from WorkItemClassificationNode and store a node reference internally in all cases. I also simplified the Name property implementation.

As for why it suddenly stopped working with no change in my code, the only thing I can think of is the remote possibility that there was a change in the compiler that affected how the SDK evaluates the Shadows and Overloads keywords. That's a long shot, I know, but I'm at a complete loss otherwise.

Bottom line, it works now.


Solution

  • I can create new iteration using Microsoft.TeamFoundation.WorkItemTracking.WebApi in Azure DevOps Services .NET SDK.

    Please check out below example:

     class Program
        {
            static void Main(string[] args)
            {
               
                Uri accountUri = new Uri("https://dev.azure.com/org/");
                string personalAccessToken = "pat";
               
                VssConnection _connection = new VssConnection(accountUri, new VssBasicCredential(string.Empty, personalAccessToken));
                WorkItemTrackingHttpClient workItemTrackingHttpClient = _connection.GetClient<WorkItemTrackingHttpClient>();
    
                Iteration iteration = new Iteration(workItemTrackingHttpClient,2021, "projectName");
                iteration.SaveNode();
            }
              
        }
    
    public class Iteration
        {
            WorkItemTrackingHttpClient client;
            WorkItemClassificationNode node;
            string project;
            string path;
            public Iteration(WorkItemTrackingHttpClient client, int Year, string project, string path=null) {
                this.client = client;
                node = new WorkItemClassificationNode();
                this.project = project;
                this.path = path;
                IDictionary<string, object> DateAttr = new Dictionary<string, object>();
                DateAttr.Add("startDate", new DateTime(Year, 1, 1));
                DateAttr.Add("finishDate", new DateTime(Year, 12, 31));
                node.Attributes = DateAttr;
                node.Name = Year.ToString();
                node.StructureType = TreeNodeStructureType.Iteration;
    
            }
    
            public void SaveNode()
            {
                var res = client.CreateOrUpdateClassificationNodeAsync(node, project, TreeStructureGroup.Iterations, path).Result;
                Console.WriteLine(res.Id);
            }
        }
    

    See below result:

    enter image description here

    I can reproduce above error Value cannot be null. Parameter name: nodeName. If i intentionally didnot set the node.Name = null; You can debug your code to check why the node name was not set.