From 2b60370a3f2574543639aa102a62244755b4914b Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 29 Sep 2026 17:17:38 -0700 Subject: [PATCH] Add Reactor single-file app template (reactor-singlefile) Adds a "Reactor Single-File App (Experimental)" template to the Microsoft.WindowsAppSDK.WinUI.CSharp.Templates dotnet new pack. It scaffolds the blank Microsoft.UI.Reactor app as a single-file .NET 10 file-based app: one .cs whose #:package / #:property directives replace the .csproj. It runs packaged by default: for `dotnet run .cs`, Microsoft.Windows.SDK.BuildTools.WinApp 0.7.0+ synthesizes an appxmanifest from the directives and launches the app with package identity. The name follows Aspire's aspire-apphost-singlefile, the only first-party dotnet new template for a .NET file-based app. - templates.props: new DotnetNewOnlyProjectTemplate row (reactor.singlefile) and an IncludeGitignore metadata opt-out (default true) so the scaffolded output stays that single file. - WinAppSdk.CSharp.DotnetNewTemplates.csproj: honor IncludeGitignore=false. - reactor-singlefile-app/.template.config: short names reactor-singlefile and winui-reactor-singlefile (winapp new enumerates with `dotnet new list winui`), and hidden from the Visual Studio New Project dialog through ide.host.json unsupportedHosts, since VS cannot open a file-based app. - The file references Microsoft.WindowsAppSDK: with Microsoft.UI.Reactor 0.1.0-preview.16 alone the Windows App SDK defaults to self-contained and the build fails. - README and Test-DotnetNewTemplates.ps1 (new FileBasedApp kind) updated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dev/Templates/Dotnet/README.md | 26 ++++- .../Dotnet/Test-DotnetNewTemplates.ps1 | 25 ++++- ...WinAppSdk.CSharp.DotnetNewTemplates.csproj | 8 +- .../.template.config/dotnetcli.host.json | 24 +++++ .../.template.config/icon.png | Bin 0 -> 13663 bytes .../.template.config/ide.host.json | 7 ++ .../.template.config/template.json | 90 ++++++++++++++++++ .../ReactorSingleFileApp/ProjectTemplate.cs | 37 +++++++ dev/Templates/templates.props | 28 +++++- 9 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/dotnetcli.host.json create mode 100644 dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/icon.png create mode 100644 dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/ide.host.json create mode 100644 dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/template.json create mode 100644 dev/Templates/Source/ProjectTemplates/Desktop/CSharp/ReactorSingleFileApp/ProjectTemplate.cs diff --git a/dev/Templates/Dotnet/README.md b/dev/Templates/Dotnet/README.md index cfede35b79..0a1c4bba5a 100644 --- a/dev/Templates/Dotnet/README.md +++ b/dev/Templates/Dotnet/README.md @@ -56,6 +56,7 @@ Get the list via `dotnet new list reactor` | `reactor-mvu` | Reactor app using the Model-View-Update pattern with `UseReducer` | | `reactor-navview` | Reactor app with a `NavigationView` shell and multiple pages | | `reactor-tabview` | Reactor app with a `TabView` shell whose tabs live in the title bar | +| `reactor-singlefile` | Blank Reactor app as a single `.cs` [file-based app](https://learn.microsoft.com/dotnet/core/sdk/file-based-apps), no `.csproj` | Each also has a `winui-reactor*` alias (e.g. `winui-reactor`, `winui-reactor-mvu`) so they show up under `dotnet new list winui` alongside the other WinUI templates. @@ -66,7 +67,8 @@ preview NuGet package. They require .NET 10 or later. Like the WinUI templates, they reference `Microsoft.Windows.SDK.BuildTools.WinApp` so `dotnet run` launches the app with full MSIX package identity. Pin a specific Reactor package version at scaffold time with `--reactor-version`, or the Windows App SDK version with -`--wasdk-version`. +`--wasdk-version`. `reactor-singlefile` is the one exception to the project +layout: see [Quickstart a single-file Reactor app](#quickstart-a-single-file-reactor-app). ### Item Templates @@ -114,6 +116,25 @@ learn the component model, hooks, and layout system. Swap `reactor` for `reactor-mvu`, `reactor-navview`, or `reactor-tabview` to start from a richer shell. +### Quickstart a single-file Reactor app + +```powershell +dotnet new reactor-singlefile -n MyReactorApp +cd MyReactorApp +dotnet run MyReactorApp.cs +``` + +`reactor-singlefile` scaffolds a single `MyReactorApp.cs`: a .NET 10 +[file-based app](https://learn.microsoft.com/dotnet/core/sdk/file-based-apps) +whose `#:package` and `#:property` directives take the place of a `.csproj`. +It still runs packaged: `Microsoft.Windows.SDK.BuildTools.WinApp` (0.7.0 or +later) makes `dotnet run MyReactorApp.cs` synthesize an appxmanifest from those +directives, register the app, and launch it with package identity (Developer +Mode required). `winapp run MyReactorApp.cs` does the same. To run it +unpackaged instead, add `#:property WindowsPackageType=None` to the file. When +the app outgrows one file, `dotnet project convert MyReactorApp.cs` turns it +into a regular project. + ### Add items to an existing project ```powershell @@ -155,6 +176,9 @@ generated `.csproj` to tweak how `dotnet run` launches the app: These properties are provided by the [`Microsoft.Windows.SDK.BuildTools.WinApp`](https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools.WinApp) NuGet package, which is referenced automatically by the project templates. +A single-file app (`reactor-singlefile`) has no `.csproj`: set them with +`#:property` directives in the `.cs` file instead (for example +`#:property WinAppRunNoLaunch=true`), or pass them to `dotnet run` with `-p:`. ## Prerequisites diff --git a/dev/Templates/Dotnet/Test-DotnetNewTemplates.ps1 b/dev/Templates/Dotnet/Test-DotnetNewTemplates.ps1 index ce692812dd..40036b6a8b 100644 --- a/dev/Templates/Dotnet/Test-DotnetNewTemplates.ps1 +++ b/dev/Templates/Dotnet/Test-DotnetNewTemplates.ps1 @@ -350,7 +350,7 @@ function Write-ResolvedPackageVersions { function Test-WinUiProjectTemplate { param( [string]$TemplateShortName, - [ValidateSet('App', 'Library', 'Test')] + [ValidateSet('App', 'FileBasedApp', 'Library', 'Test')] [string]$Kind, [string]$Platform, [string]$WorkingRoot @@ -361,7 +361,9 @@ function Test-WinUiProjectTemplate { New-ProjectFromTemplate -TemplateShortName $TemplateShortName -ProjectName $projectName -OutputPath $projectPath -WorkingDirectory $WorkingRoot -WindowsAppSdkVersion $script:windowsAppSdkVersion Add-Result -Template $TemplateShortName -Platform $Platform -Step 'create' -Status 'Succeeded' -Path $projectPath - $projectFile = Join-Path -Path $projectPath -ChildPath "$projectName.csproj" + # A file-based app template scaffolds a single .cs whose #: directives replace the .csproj. + $projectFileExtension = if ($Kind -eq 'FileBasedApp') { '.cs' } else { '.csproj' } + $projectFile = Join-Path -Path $projectPath -ChildPath "$projectName$projectFileExtension" Write-ResolvedPackageVersions -ProjectFile $projectFile -ProjectPath $projectPath @@ -382,6 +384,20 @@ function Test-WinUiProjectTemplate { Write-Warning "Unable to locate executable for template '$TemplateShortName' at '$projectPath'." } } + 'FileBasedApp' { + # The scaffold must stay a single file: no .csproj, .gitignore, or assets. + $scaffolded = @(Get-ChildItem -Path $projectPath -Recurse -Force -File) + if ($scaffolded.Count -ne 1 -or $scaffolded[0].Name -ne "$projectName.cs") { + $scaffoldedNames = ($scaffolded | ForEach-Object { $_.Name }) -join ', ' + throw "Template '$TemplateShortName' should scaffold only '$projectName.cs', but produced: $scaffoldedNames" + } + + # Builds the default (packaged) configuration. The output goes to the SDK's + # file-based app cache (%TEMP%\dotnet\runfile), not $projectPath, so there is + # no executable to record for launching. + Invoke-DotnetCommand -Arguments @('build', $projectFile, '-p:Configuration=Debug', "-p:Platform=$Platform") -WorkingDirectory $projectPath -Description 'build file-based app template' + Add-Result -Template $TemplateShortName -Platform $Platform -Step 'build' -Status 'Succeeded' -Path $projectFile + } 'Library' { Invoke-DotnetCommand -Arguments @('build', $projectFile, '-c', 'Debug') -WorkingDirectory $projectPath -Description 'build library template' Add-Result -Template $TemplateShortName -Platform 'AnyCPU' -Step 'build' -Status 'Succeeded' -Path $projectFile @@ -670,7 +686,10 @@ try { @{ ShortName = 'reactor'; Kind = 'App' }, @{ ShortName = 'reactor-mvu'; Kind = 'App' }, @{ ShortName = 'reactor-navview'; Kind = 'App' }, - @{ ShortName = 'reactor-tabview'; Kind = 'App' } + @{ ShortName = 'reactor-tabview'; Kind = 'App' }, + # Reactor single-file app: a .NET file-based app, i.e. a single .cs + # whose #: directives replace the .csproj, built with 'dotnet build .cs'. + @{ ShortName = 'reactor-singlefile'; Kind = 'FileBasedApp' } ) foreach ($template in $projectTemplates) { diff --git a/dev/Templates/Dotnet/WinAppSdk.CSharp.DotnetNewTemplates.csproj b/dev/Templates/Dotnet/WinAppSdk.CSharp.DotnetNewTemplates.csproj index be907de907..ab86fdc422 100644 --- a/dev/Templates/Dotnet/WinAppSdk.CSharp.DotnetNewTemplates.csproj +++ b/dev/Templates/Dotnet/WinAppSdk.CSharp.DotnetNewTemplates.csproj @@ -146,8 +146,10 @@ foreach (var t in Templates) // 3. dotnet-new template configuration. EmitTree(Path.Combine(configDir, ".template.config"), contentRoot + "/.template.config"); - // 4. Shared .gitignore (one copy per template). - if (!string.IsNullOrEmpty(GitignoreSource) && File.Exists(GitignoreSource)) + // 4. Shared .gitignore (one copy per template), unless the template opts out with + // IncludeGitignore=false (e.g. a file-based app whose output is a single .cs file). + bool includeGitignore = !string.Equals(t.GetMetadata("IncludeGitignore"), "false", StringComparison.OrdinalIgnoreCase); + if (includeGitignore && !string.IsNullOrEmpty(GitignoreSource) && File.Exists(GitignoreSource)) { Emit(GitignoreSource, contentRoot + "/gitignore.txt"); } @@ -161,7 +163,7 @@ ExpandedItems = output.ToArray(); - + diff --git a/dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/dotnetcli.host.json b/dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/dotnetcli.host.json new file mode 100644 index 0000000000..d143d64a6e --- /dev/null +++ b/dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/dotnetcli.host.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "dotnetVersion": { + "longName": "dotnet-version", + "shortName": "tfm", + "description": "Base TFM for the app (file-based apps and Microsoft.UI.Reactor require net10.0 or later)." + }, + "reactorVersion": { + "longName": "reactor-version", + "shortName": "rv", + "description": "Pin to a specific Microsoft.UI.Reactor NuGet version." + }, + "windowsAppSdkVersion": { + "longName": "wasdk-version", + "shortName": "wasdkv", + "description": "Pin to a specific Windows App SDK NuGet version." + }, + "skipRestore": { + "longName": "no-restore", + "shortName": "" + } + } +} diff --git a/dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/icon.png b/dev/Templates/Dotnet/templates/reactor-singlefile-app/.template.config/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1e450cb9b3bea5e6a01452434e66b5c289e97d6c GIT binary patch literal 13663 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.cs'. Microsoft.UI.Reactor is prerelease and its API may change.", + "preferNameDirectory": true, + "constraints": { + "windows-only": { + "type": "os", + "args": "Windows" + }, + "sdk-version": { + "type": "sdk-version", + "args": [ "[10.0,)" ] + } + }, + "symbols": { + "projectName": { + "type": "derived", + "valueSource": "name", + "valueTransform": "identity", + "replaces": "$projectname$" + }, + "dotnetVersion": { + "type": "parameter", + "datatype": "choice", + "description": "Base target framework moniker (TFM). File-based apps and Microsoft.UI.Reactor require .NET 10 or later.", + "choices": [ + { "choice": "net10.0", "description": "Target .NET 10" } + ], + "defaultValue": "net10.0", + "replaces": "$DotNetVersion$" + }, + "reactorVersion": { + "type": "parameter", + "datatype": "text", + "defaultValue": "0.1.0-preview.16", + "replaces": "$ReactorVersion$", + "description": "Microsoft.UI.Reactor NuGet version to reference." + }, + "windowsAppSdkVersion": { + "type": "parameter", + "datatype": "text", + "defaultValue": "*", + "replaces": "$WindowsAppSdkVersion$", + "description": "Microsoft.WindowsAppSDK NuGet version. Requires 2.2.0 or later; use * for the latest stable." + }, + "windowsSdkBuildToolsWinAppVersion": { + "type": "parameter", + "datatype": "text", + "defaultValue": "*", + "replaces": "$WindowsSdkBuildToolsWinAppVersion$", + "description": "Microsoft.Windows.SDK.BuildTools.WinApp NuGet version. Requires 0.7.0 or later (file-based app support); use * for the latest stable." + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "If specified, skips the automatic restore of the app on create." + } + }, + "primaryOutputs": [ + { "path": "ProjectTemplate.cs" } + ], + "forms": { + "identity": { + "identifier": "identity" + } + }, + "postActions": [ + { + "id": "restore", + "condition": "(!skipRestore)", + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true, + "description": "Restore NuGet packages required by this app.", + "manualInstructions": [ { "text": "Run 'dotnet restore .cs'" } ] + } + ] +} diff --git a/dev/Templates/Source/ProjectTemplates/Desktop/CSharp/ReactorSingleFileApp/ProjectTemplate.cs b/dev/Templates/Source/ProjectTemplates/Desktop/CSharp/ReactorSingleFileApp/ProjectTemplate.cs new file mode 100644 index 0000000000..e3d6c48cb1 --- /dev/null +++ b/dev/Templates/Source/ProjectTemplates/Desktop/CSharp/ReactorSingleFileApp/ProjectTemplate.cs @@ -0,0 +1,37 @@ +#:package Microsoft.UI.Reactor@$ReactorVersion$ +#:package Microsoft.WindowsAppSDK@$WindowsAppSdkVersion$ +#:package Microsoft.Windows.SDK.BuildTools.WinApp@$WindowsSdkBuildToolsWinAppVersion$ +#:property OutputType=WinExe +#:property TargetFramework=$DotNetVersion$-windows10.0.26100.0 +#:property UseWinUI=true + +// A .NET file-based app: the #: directives above take the place of a .csproj. +// `dotnet run $projectname$.cs` launches it with package identity (needs Developer Mode). +// To run it unpackaged instead, add `#:property WindowsPackageType=None` above. + +using Microsoft.UI.Reactor; +using Microsoft.UI.Reactor.Core; +using Microsoft.UI.Reactor.Layout; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using static Microsoft.UI.Reactor.Factories; + +// To learn more about Reactor, the Reactor project structure, and more about +// our project templates, see: https://github.com/microsoft/microsoft-ui-reactor +ReactorApp.Run("$projectname$"); + +class App : Component +{ + public override Element Render() + { + var titleBar = TitleBar("$projectname$") + .Flex(shrink: 0); + + // This is the main content area of your application. + // Add your UI elements here. + var content = Border(Empty()).Flex(grow: 1, basis: 0); + + return FlexColumn(titleBar, content) + .Backdrop(BackdropKind.Mica); + } +} diff --git a/dev/Templates/templates.props b/dev/Templates/templates.props index 48d8a10a94..7e9396fde8 100644 --- a/dev/Templates/templates.props +++ b/dev/Templates/templates.props @@ -16,7 +16,7 @@ ItemTemplate yes yes single-file 'Add New Item' templates (Page, UserControl, ...) VsixOnlyProjectTemplate no yes e.g. .wapproj-based packaging not usable from the CLI - DotnetNewOnlyProjectTemplate yes no (framework slot; no instances today) + DotnetNewOnlyProjectTemplate yes no e.g. the experimental Reactor (Microsoft.UI.Reactor) templates VsixOnlyItemTemplate no yes (framework slot; no instances today) DotnetNewOnlyItemTemplate yes no (framework slot; no instances today) @@ -33,11 +33,16 @@ RenameManifestFrom Optional. Source-side manifest filename (typically Package-managed.appxmanifest) to repack as the reserved Package.appxmanifest. Leave unset for templates that have no manifest. + IncludeGitignore Optional, dotnet-new project templates only. Defaults to true: the pack + adds the shared templates\gitignore.txt, which template.json renames to + .gitignore. Set to false for templates whose output must stay a single + file, such as a .NET file-based app. --> + true @@ -48,6 +53,7 @@ + true @@ -151,6 +157,26 @@ $(MSBuildThisFileDirectory)Dotnet\templates\reactor-tabview-app Package-managed.appxmanifest + + + + Reactor.Desktop.Cs.SingleFileApp + $(MSBuildThisFileDirectory)Source\ProjectTemplates\Desktop\CSharp\ReactorSingleFileApp + $(MSBuildThisFileDirectory)Dotnet\templates\reactor-singlefile-app + false +