dotnet/reactive · error · ArgumentException

Template csproj path should be absolute

Error message

Template csproj path should be absolute

What it means

ComponentBuilder.CreateModifiedProjectClone requires the template .csproj path to be an absolute path so that Path.GetDirectoryName yields a non-null folder. When a null/relative path yields null from GetDirectoryName, it throws ArgumentException('Template csproj path should be absolute', nameof(templateCsProj)).

Solutions

  1. Pass a fully rooted path: Path.GetFullPath(relativePath) or combine with the repo root before calling
  2. Ensure the argument points to a .csproj file, not a directory
  3. Log/assert the resolved path before calling CreateModifiedProjectClone during development
  4. Fix the caller configuration so templateCsProj is populated with an absolute path

Example fix

// before
await builder.BuildLocalNuGetPackageAsync("Templates\Lib.csproj", ...);
// after
string template = Path.GetFullPath(Path.Combine(repoRoot, "Templates", "Lib.csproj"));
await builder.BuildLocalNuGetPackageAsync(template, ...);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(templateCsProj) || !Path.IsPathRooted(templateCsProj))
    throw new ArgumentException("templateCsProj must be an absolute file path", nameof(templateCsProj));
if (!File.Exists(templateCsProj)) throw new FileNotFoundException(templateCsProj);

Type guard

bool IsAbsoluteFilePath(string? p) => !string.IsNullOrWhiteSpace(p) && Path.IsPathRooted(p) && File.Exists(p);

Try / catch

try
{
    await builder.BuildLocalNuGetPackageAsync(templateCsProj, ...);
}
catch (ArgumentException ex) when (ex.ParamName == nameof(templateCsProj))
{
    // resolve path with Path.GetFullPath and retry
}

Prevention

When it happens

Trigger: CreateModifiedProjectClone (called by BuildLocalNuGetPackageAsync / BuildAppAsync) is passed a templateCsProj like 'Templates/My.csproj' or an empty string, so Path.GetDirectoryName returns null.

Common situations: Caller built the path from a relative working directory; passed a bare file name; passed a directory instead of a file path; misconfigured template location in test settings.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/d0952cf3ffaefb27. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Test/Gauntlet/RxGauntlet.Common/Build/ComponentBuilder.cs:76

            [
                ("DynamicallyBuiltPackages", LocalNuGetPackageFolderPath),
                ..(additionalPackageSources ?? [])
            ];

        (string projectTemplateFileName, ModifiedProjectClone project) = CreateModifiedProjectClone(
            appBuildTempFolderName, templateCsProj, modifyProjectFile, packageSourcesIncludingDynamicallyBuiltPackages);

        return await project.RunDotnetBuild(projectTemplateFileName);
    }

    private (string ProjectTemplateFileName, ModifiedProjectClone ProjectClone) CreateModifiedProjectClone(
        string tempParentFolderName,
        string templateCsProj,
        Action<ProjectFileRewriter> modifyProjectFile,
        (string FeedName, string FeedLocation)[]? additionalPackageSources)
    {
        string projectTemplateFolder = Path.GetDirectoryName(templateCsProj)
            ?? throw new ArgumentException("Template csproj path should be absolute", nameof(templateCsProj));
        string projectTemplateFileName = Path.GetFileName(templateCsProj)
            ?? throw new ArgumentException("Template csproj path should refer to a file, not a directory", nameof(templateCsProj));
        var projectClone = ModifiedProjectClone.Create(
            projectTemplateFolder,
            tempParentFolderName,
            modifyProjectFile,
            additionalPackageSources);
        _projectClones.Add(projectClone);

        return (projectTemplateFileName, projectClone);
    }

    public void Dispose()
    {
        foreach (ModifiedProjectClone clone in _projectClones)
        {
            clone.Dispose();
        }

View on GitHub (pinned to 94b5d5ab91)