C# + Photino:轻量级 Web UI 桌面应用指南
有很大一部分人不习惯使用浏览器搜索,而是更喜欢使用应用程序或者超级应用程序内的小程序。于是从web技术栈到桌面应用开发有一个丝滑的过渡,本次尝试使用photino进行开发。
官网地址
开始使用
下载项目模板
https://marketplace.visualstudio.com/items?itemName=TryPhotino.PhotinoSamplesVSExtension
在VisualStudio中使用项目模板创建一个新项目
创建完成后会得到以下项目结构

运行项目
此时Resources中的wwwroot中已经放置好了打包之后的内容,同时/wwwroot下也有打包好的内容,所以第一次运行是没问题的,如果有问题,可以修改Main中的url以重新匹配。 运行结果如图:

一些问题
虽然项目模板中是把前端项目放在了UserInterface中,但是我强烈建议将前端项目从C#项目中分离。
原因如下:
- C#和前端的git提交内容会混杂在一起。
- 前端的node_modules文件层级过长,在C#项目中会导致解决方案资源管理器崩溃
- 发布产物的清晰度,如果混在一起,发布项目时很容易把不必要的 package.json、source maps 甚至 node_modules 里的碎片文件打包进最终的 bin 目录。分离后,C# 项目只需要在编译前通过脚本将前端 dist 目录下的静态资源拷贝到自己的 wwwroot 即可,产物极其干净。
在第一次运行的时候,除了会有一个vue的窗口,还会有一个控制台窗口。如果想关闭这个控制台窗口,需要修改项目文件中的OutputType。
Exe => WinExe
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Platforms>AnyCPU</Platforms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Generate embedded file manifest when using the Photino.NET StaticFileServer -->
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<PublishTrimmed>True</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.10" />
<PackageReference Include="Photino.NET" Version="3.2.3" />
<PackageReference Include="Photino.NET.Server" Version="3.0.11" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
</Project>
深入本质:Photino 到底是怎么工作的?
很多初学者甚至经验识丰富的开发者,初识 Photino 时常会将其类比为一个“内置了静态文件服务器的壳子”。这种理解虽然在宏观逻辑上能跑通,但从底层工程实践来看,它其实比这要优雅且“鸡贼”得多。
如果我们剥开 Photino 的外壳,它的本质可以概括为:原生 WebView 宿主 + 内存级资源拦截 + 跨进程消息总线。
- 它不是真正的“服务器”
与 Electron 开发者习惯启动一个 localhost:3000 不同,Photino 默认情况下并不占用任何网络端口。它利用了现代操作系统 WebView 内核(如 Windows 的 WebView2)的资源拦截(Resource Intercept)能力。 当你访问 index.html 时,WebView 发出的请求被 Photino 的底层接口直接截获,并从本地磁盘或嵌入式资源中以“数据流”的形式喂给浏览器。这意味着:
没有端口冲突:你永远不会遇到“Address already in use”的尴尬。
零网络开销:数据传输在进程内存中完成,不经过网络栈,性能上限更高。
- “借刀杀人”的渲染策略
Photino 本身不包含任何浏览器内核,它是一个极简的 C# 包装器。
在 Windows 上,它调用 Edge (Chromium);
在 macOS 上,它调用 Safari (WebKit);
在 Linux 上,它调用 WebKitGTK。
这种“借用”系统基础建设的策略,使得它的安装包体积可以维持在几百 KB 到几 MB 之间,彻底解决了 Electron 动辄数百 MB 的分发痛点。3. 跨越边境的通信通道(Interoperability)
C# 与 JavaScript 的交互是 Photino 的灵魂所在。 它建立了一个异步消息总线:JavaScript 侧通过 window.external.sendMessage 发送信号,C# 侧通过注册特定的处理函数进行捕获并逻辑回执。这种通信方式是强类型的(在 C# 端),且完全解耦了 UI 渲染逻辑与业务数据逻辑。 我的架构建议:物理层面的彻底分离
正因为 Photino 本质上是一个“通过拦截获取资源的壳子”,我强烈建议在工程结构上将前端项目(Vite/React/Vue)从 C# 解决方案中彻底剥离。
这样做不仅是为了 Git 提交记录的整洁,更重要的是:我们应该把 Photino 的 wwwroot 仅仅看作是一个“发布目标”,而不是“源码存放地”。 前端工程有其独立的构建生命周期,让 C# 专注处理系统交互,让 Node.js 专注处理 UI 构建,这才是现代 Hybrid 应用的开发范式。