C#使用 WebView2 替代 Electron/Tauri 之 Web 核心

上一次试验了

C# .NET 给Web项目(MVC,RazorPage,Blazor等)添加一个系统托盘图标

C#使用WebView2替代Electron

这次来一个用 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 生成的本机缓存,
打包发给别人时, 不需要这个文件夹

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值