{"record":{"id":"eaf6a4eaf7e9e5f8","repo":"OrchardCMS/OrchardCore","slug":"a-virtual-path-is-required","errorCode":null,"errorMessage":"A virtual path is required","messagePattern":"A virtual path is required","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.Deployment.Core/Services/TemporaryFileBuilder.cs","lineNumber":35,"sourceCode":"\n    public string Folder { get; }\n\n    public void Dispose()\n    {\n        if (_deleteOnDispose)\n        {\n            if (Directory.Exists(Folder))\n            {\n                Directory.Delete(Folder, true);\n            }\n        }\n    }\n\n    public async Task SetFileAsync(string subpath, Stream stream)\n    {\n        if (subpath.StartsWith('/'))\n        {\n            throw new InvalidOperationException(\"A virtual path is required\");\n        }\n\n        var fullname = PathExtensions.Combine(Folder, subpath);\n\n        var directory = new FileInfo(fullname).Directory;\n\n        if (!directory.Exists)\n        {\n            directory.Create();\n        }\n\n        using var fs = File.Create(fullname, 4 * 1024, FileOptions.None);\n        await stream.CopyToAsync(fs);\n    }\n\n    public override string ToString()\n    {\n        return Folder;","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.Deployment.Core/Services/TemporaryFileBuilder.cs#L17-L53","documentation":"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\").","triggerScenarios":"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.","commonSituations":"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.","solutions":["Strip the leading '/' (or use .TrimStart('/')) from the subpath before calling SetFileAsync.","Normalize path building so subpaths are always constructed relative to the deployment folder root.","Add a guard in your deployment source that validates subpath does not start with '/'."],"exampleFix":"// before\nawait fileBuilder.SetFileAsync(\"/media/logo.png\", stream);\n// after\nawait fileBuilder.SetFileAsync(subpath.TrimStart('/'), stream);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrEmpty(subpath) || subpath.StartsWith('/'))\n    throw new ArgumentException($\"Subpath must be a relative virtual path, got '{subpath}'\");","typeGuard":"bool IsRelativeSubpath(string p) => !string.IsNullOrEmpty(p) && !p.StartsWith('/');","tryCatchPattern":"catch (InvalidOperationException ex) when (ex.Message.Contains(\"virtual path\")) { logger.LogError(ex, \"Bad export path: {Path}\", subpath); }","preventionTips":["Always TrimStart('/') paths destined for the deployment file builder","Build export paths from relative segments, not absolute user paths","Unit-test path building helpers for leading-slash input"],"tags":["deployment","path","validation"],"backgroundTag":"invalid-url-format","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}