OrchardCMS/OrchardCore · error · InvalidOperationException

A virtual path is required

Error message

A virtual path is required

What it means

TemporaryFileBuilder.SetFileAsync writes a deployment/export file under a virtual (root-relative) folder. The path must be relative; a subpath starting with '/' means an absolute virtual path was supplied, which cannot be combined with the builder's Folder, so it throws InvalidOperationException("A virtual path is required").

Solutions

  1. Strip the leading '/' (or use .TrimStart('/')) from the subpath before calling SetFileAsync.
  2. Normalize path building so subpaths are always constructed relative to the deployment folder root.
  3. Add a guard in your deployment source that validates subpath does not start with '/'.

Example fix

// before
await fileBuilder.SetFileAsync("/media/logo.png", stream);
// after
await fileBuilder.SetFileAsync(subpath.TrimStart('/'), stream);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(subpath) || subpath.StartsWith('/'))
    throw new ArgumentException($"Subpath must be a relative virtual path, got '{subpath}'");

Type guard

bool IsRelativeSubpath(string p) => !string.IsNullOrEmpty(p) && !p.StartsWith('/');

Try / catch

catch (InvalidOperationException ex) when (ex.Message.Contains("virtual path")) { logger.LogError(ex, "Bad export path: {Path}", subpath); }

Prevention

When it happens

Trigger: Calling SetFileAsync with subpath like "/wwwroot/file.txt" or "/media/image.png" — i.e., a leading slash. Only relative subpaths ("media/image.png") are accepted.

Common situations: Custom deployment sources/steps building export file paths by concatenating a base path that already ends with '/'; using Path.Combine-style absolute paths from user input; recipes or import steps producing absolute virtual paths.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/eaf6a4eaf7e9e5f8. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Deployment.Core/Services/TemporaryFileBuilder.cs:35

    public string Folder { get; }

    public void Dispose()
    {
        if (_deleteOnDispose)
        {
            if (Directory.Exists(Folder))
            {
                Directory.Delete(Folder, true);
            }
        }
    }

    public async Task SetFileAsync(string subpath, Stream stream)
    {
        if (subpath.StartsWith('/'))
        {
            throw new InvalidOperationException("A virtual path is required");
        }

        var fullname = PathExtensions.Combine(Folder, subpath);

        var directory = new FileInfo(fullname).Directory;

        if (!directory.Exists)
        {
            directory.Create();
        }

        using var fs = File.Create(fullname, 4 * 1024, FileOptions.None);
        await stream.CopyToAsync(fs);
    }

    public override string ToString()
    {
        return Folder;

View on GitHub (pinned to 4306c0717f)