上一次试验了
C# .NET 给Web项目(MVC,RazorPage,Blazor等)添加一个系统托盘图标
这次来一个用 web 来给 webview2 提供数据
虽然可以像 其他语言一样,底层使用 webview2 提供的传输字符串的接口去做 js 的通讯。
不过比较麻烦,需要自己复刻一套 那样的算法和架构。
c# winform 拿来就用, 爽歪歪.
实现起来既简单又可以复用 winform 和 web api 生态, 真的是一举多得。
Github
https://github.com/xxxxue/Webview2Demo
创建项目
创建一个 .Net 的 winform 项目 (本文使用 .net 8 )
nuget 安装 webview2
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1264.42" />
</ItemGroup>
目录结构
代码
winform 项目中想要用 asp net core , 需要修改项目文件
Sdk= Microsoft.NET.Sdk.Web ( 可以让 winform 项目使用 web 相关的 .net 库 )
OutputType = WinExe ( 可以让exe 启动后 没有 控制台窗口. 只显示 winform 窗口 )
TargetFramework 需要指定 windows, 因为 winform 只能 在 win 上运行
ReactRoot = my-react-app/ ( 自定义的变量. 稍后会用到 )
手动创建一个 wwwroot 的目录
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<!--设置语言,则可以移除多余的语言文件夹-->
<SatelliteResourceLanguages>zh-Hant</SatelliteResourceLanguages>
<!--设置前端项目的目录-->
<ReactRoot>my-react-app/</ReactRoot>
</PropertyGroup>
// 下面后很多. 这里先省略. 完整版看最后
Program.cs
启动一个 web api, 并监听 localhost:5000
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Webview2Demo;
internal static class Program
{
[STAThread]
static void Main()
{
var builder = WebApplication.CreateBuilder();
// 指定 C# 服务的端口
builder.WebHost.UseUrls("http://localhost:5000");
builder.Services.AddControllers();
// 跨域配置
builder.Services.AddCors(options =>
{
options.AddPolicy("all", builder =>
{
builder
.WithMethods("GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS")
.AllowAnyHeader()
.AllowAnyOrigin();
//.AllowCredentials()
});
});
var app = builder.Build();
app.UseCors("all"); // 跨域
app.UseDefaultFiles(); // 静态文件相关 (index.html)
app.UseStaticFiles(); // 静态文件相关 (wwwroot)
app.MapControllers();
app.Start();// 启动 web 服务器, Start() 不会阻塞.
// 启动 winform 窗口
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
Form1 窗口拖入 webview2 控件,然后在窗口 Load 事件中指定 webview2 的请求地址
这里的 3000 是前端 React 项目的地址(稍后创建)
开发环境使用 vscode 运行前端项目, 热更新等等 全都有了。
(前后端分离开发,唯一的区别就是不用自己打开浏览器了,直接用 winform 就可以)
5000 是上面代码中指定的 web api 的地址
因为 MsBuild 构建 Release 会把前端编译后的文件放到wwwroot里。(稍后去做)
所以这里直接调用 5000 端口,就可以了。
private void Form1_Load(object sender, EventArgs e)
{
#if DEBUG
// 开发时 使用前端项目自己的服务器地址 (比如 webpack , vite 等)
webView2.Source = new Uri("http://localhost:3000");
#else
// 发布后, 会把html复制到 wwwroot 交给C#托管
// 所以这里填 c# 服务器的地址
webView2.Source = new Uri("http://localhost:5000");
#endif
}
Controller
创建一个 MyController.cs
继承 ControllerBase
, 直接开始写方法,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Webview2Demo;
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("/ShowMessageBox")]
public async Task<bool> ShowMessageBox([FromQuery] string msg = "我是消息")
{
MessageBox.Show(msg, "标题", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
return await Task.FromResult(true);
}
}
创建 前端项目
用你喜欢的方式创建一个 前端项目
这里用 React 举例
pnpm create vite
项目名 my-react-app
选择 react
, typescript
在 scripts 的 dev 命令 指定 端口号 3000
“dev”: “vite --port 3000”
将 测试代码 写到 App.tsx 中
核心代码 是 button 点击后, 发送 http 请求
import reactSvg from "./assets/react.svg";
function App() {
const handleClick = () => {
// 弹个框
fetch("http://localhost:5000/ShowMessageBox?msg=我是 React")
.then((r) => r.text())
.then((r) => {
console.log("返回值:", r);
});
};
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyItems: "center",
}}
>
<h1>.Net + WebView2 + React</h1>
<img src={reactSvg} width={100} height={100} />
<button style={{ width: "100px" }} onClick={handleClick}>
Click
</button>
</div>
);
}
export default App;
发布时把前端产物放入 wwwroot
设置 ReactRoot 属性
设置 wwwroot 如果较新则复制
写一个 Target 指定 AfterTargets 和 Condition
这样 非 debug模式下 就会编译前端 (pnpm build)
复制 前端 dist 目录到 wwwroot
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<!--设置语言,则可以移除多余的语言文件夹-->
<SatelliteResourceLanguages>zh-Hant</SatelliteResourceLanguages>
<!--设置前端项目的目录-->
<ReactRoot>my-react-app/</ReactRoot>
</PropertyGroup>
<ItemGroup>
<Compile Remove="my-react-app\**" />
<Content Remove="my-react-app\**" />
<EmbeddedResource Remove="my-react-app\**" />
<None Remove="my-react-app\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1264.42" />
</ItemGroup>
<ItemGroup>
<!--复制目录-->
<None Update="wwwroot/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<Target Name="build-js" AfterTargets="ComputeFilesToPublish" Condition="'$(Configuration)'!='Debug'">
<Message Text="开始编译 React 项目"></Message>
<!-- 执行命令,开始编译前端 -->
<Exec Command="pnpm build" WorkingDirectory="$(ReactRoot)" />
<!-- 把dist目录的文件复制到 wwwroot-->
<ItemGroup>
<DistFiles Include="$(ReactRoot)dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
发布项目,勾选上 单文件 与 删除现有文件
可以看到在编译完c#后,开始编译前端了。
最后会把所有的产物都输出到 publish 中
直接双击exe
点击按钮测试一下
🎉🎉🎉🎉
发布后, 压缩包 400Kb , 解压出来1M
图中 Webview2Demo.exe.WebView2 文件夹, 是运行后 webview2 生成的本机缓存,
打包发给别人时, 不需要这个文件夹