WPF 引用 ASP.NET Core 的 AOT 版本
<p>现在 ASP.NET Core 早已支持 AOT 发布了,只是绝大部分教程都是教大家将其作为应用发布。在本文里面,咱将尝试进行类库发布,发布之后是一个 DLL 文件。通过 UnmanagedCallersOnly 导出函数被其他应用程序所使用</p><p>混合 WPF 和 ASP.NET Core 两个框架到一个进程里面是比较舒服的事情,让 WPF 负责界面显示逻辑和一些交互控制,让 ASP.NET Core 负责提供 HTTP 服务,各干各的,各自发挥优势</p>
<p>在上篇博客中,介绍了直接项目引用的方式,在一个进程里面跑起来 WPF 和 ASP.NET Core 框架,详细请看 dotnet 简单方法在一个进程内同时跑起 WPF 和 ASP.NET Core 框架</p>
<p>在本文这里将着重和大家介绍在 WPF 里面调用的是 AOT 发布的 ASP.NET Core 类库。此方式可以将整个 ASP.NET Core 负载打成一个 DLL 文件,不会在输出路径上带上一些 ASP.NET Core 的 DLL 文件,适用于 HTTP 服务属于业务边缘模块,不想为边缘的模块添加依赖。也适用于让 WPF 和 ASP.NET Core 各自依赖不同的 .NET 版本</p>
<p>首先通过如下命令新建支持 AOT 的 ASP.NET Core 应用</p>
<pre><code>dotnet new webapiaot -o Lib1
</code></pre>
<p>默认模版创建的是顶层语句,即没有在 Program.cs 写 Main 函数等做法。咱需要将其进行改造,改造包含将默认的代码放在一个函数里,和删除其中的演示代码。对于本文的演示需求来说,只需保留 MapGet 一条到 <code>/</code> 路径即可,返回一个字符串字段的值,代码如下</p>
<pre><code class="language-csharp">public static class Program
{
private static string _greetText = "Hello from Lib1!";
private static void StartInner()
{
var builder = WebApplication.CreateSlimBuilder([]);
var app = builder.Build();
app.MapGet("/", () => _greetText);
app.Run();
}
}
</code></pre>
<p>大家可以看到,我删除了 Main 方法,这是因为此项目将作为类库发布,不能有 Main 入口函数,有了也没用。因此只将代码放在 StartInner 里面。附带也能看到 ASP.NET Core 非常简洁,短短 5 行有效代码就可以完成 HTTP 服务</p>
<p>为了能够调用 StartInner 开启辅助,咱再包装 Start 方法,且用 UnmanagedCallersOnly 进行公开,代码如下</p>
<pre><code class="language-csharp">public static class Program
{
)]
public static int Start()
{
Console.WriteLine($"Start run");
Task.Run(StartInner);
return 2;
}
}
</code></pre>
<p>以上代码有一个不良实践,那就是将 StartInner 这个长时间执行的方法放在 <code>Task.Run</code> 里面,这样做将会占用线程池资源</p>
<p>继续再添加一个方法,用于给 WPF 层修改返回的 <code>_greetText</code> 字段,代码如下</p>
<pre><code class="language-csharp"> )]
public static void SetGreetText(IntPtr greetText, int charCount)
{
_greetText = Marshal.PtrToStringUni(greetText, charCount);
}
</code></pre>
<p>当前的 .NET 的类库 AOT 只支持基础数据类型,不能传递 .NET 的对象,这也就是为什么参数需要使用指针而不是字符串的原因。在后文将告诉大家简单的调用方法</p>
<p>如此就完成了简单的 ASP.NET Core 部分的活了,全部代码都在 Program.cs 里面,代码如下</p>
<pre><code class="language-csharp">using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Lib1;
public static class Program
{
)]
public static int Start()
{
Console.WriteLine($"Start run");
Task.Run(StartInner);
return 2;
}
)]
public static void SetGreetText(IntPtr greetText, int charCount)
{
_greetText = Marshal.PtrToStringUni(greetText, charCount);
}
private static string _greetText = "Hello from Lib1!";
private static void StartInner()
{
var builder = WebApplication.CreateSlimBuilder([]);
var app = builder.Build();
app.MapGet("/", () => _greetText);
app.Run();
}
}
</code></pre>
<p>由于当前的 .NET 存在一个已知问题,将导致 ASP.NET Core 作为类库发布之后,再被另一个 .NET 应用引用时,会在控制台输出卡住。解决此问题需要在 csproj 里面添加如下代码配置,详细请参阅 https://github.com/dotnet/runtime/issues/118773</p>
<pre><code class="language-xml"><PropertyGroup>
<EventSourceSupport>false</EventSourceSupport>
</PropertyGroup>
</code></pre>
<p>如果不知道以上代码应该如何加到 csproj 上,还请使用本文末尾提供的命令行获取本文的所有代码,通过对比我的代码了解应该如何编写</p>
<p>完成准备工作之后,执行如下命令进行发布</p>
<pre><code>dotnet publish -r win-x64
</code></pre>
<p>发布完成之后,拷贝发布出来的 Lib1.dll 文件,将其放入到 WPF 应用的输出文件夹路径。此时的 Lib1.dll 不是一个包含 IL 的 .NET 程序集的 DLL 文件,因此不能作为 WPF 项目的引用 DLL 文件,最多只能作为 None 方式作为引用如果较新则复制到输出路径</p>
<p>这样发布出来的简单 ASP.NET Core 类库只有 7MB 左右,以下截图中,只有 Lib1.dll 是必需的,其他都可以丢掉</p>
<p><img src="https://img2024.cnblogs.com/blog/1080237/202508/1080237-20250821085008640-1671470634.png" alt="" loading="lazy"></p>
<p>按照以往的方式创建 WPF 空白应用。编辑 MainWindow.xaml 添加一点界面代码</p>
<pre><code class="language-xml"> <Grid>
<StackPanel VerticalAlignment="Center">
<Grid MinWidth="300" HorizontalAlignment="Center">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0 5 5 5"></Setter>
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="0 5 0 5"></Setter>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="欢迎词:"></TextBlock>
<TextBox x:Name="GreetTextBox" Grid.Column="1" Text="Hello"></TextBox>
</Grid>
<Button x:Name="UpdateButton" Width="100" Height="30" Margin="10" Click="UpdateButton_OnClick">更新</Button>
</StackPanel>
</Grid>
</code></pre>
<p>在 MainWindow.xaml.cs 里面添加两个 P/Invoke 定义函数,代码如下</p>
<pre><code class="language-csharp">
private static extern int Start();
private static extern void SetGreetText(IntPtr greetText, int charCount);
</code></pre>
<p>通过以上代码可见此时的对 Lib1.dll 的调用和对其他的 Win32 DLL 调用是一样的 P/Invoke 写法</p>
<p>在窗口启动完成之后,调用 Start 启动 ASP.NET Core 服务,代码如下</p>
<pre><code class="language-csharp"> public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Start();
}
</code></pre>
<p>点击更新按钮时,将文本框的内容更新同步到 ASP.NET Core 上,代码如下</p>
<pre><code class="language-csharp"> private unsafe void UpdateButton_OnClick(object sender, RoutedEventArgs e)
{
var greetText = GreetTextBox.Text;
fixed (char* c = greetText)
{
SetGreetText(new IntPtr(c), greetText.Length);
}
}
</code></pre>
<p>如以上代码所示,调用的时候需要将字符串对象固定,取出指针传递过去</p>
<p>如此简单代码就完成了 WPF 调用 AOT 版本的 ASP.NET Core 服务,开启 HTTP 服务</p>
<p>尝试运行 WPF 项目,运行之前别忘了拷贝 Lib1.dll 文件到输出文件夹路径,然后用 Lib1 里面自带的 .http 文件发送请求,可见如下界面</p>
<p><img src="https://img2024.cnblogs.com/blog/1080237/202508/1080237-20250821085009241-870798364.png" alt="" loading="lazy"></p>
<p>点击按钮,更新一下文本内容,再次发送 http 请求,可见如下界面</p>
<p><img src="https://img2024.cnblogs.com/blog/1080237/202508/1080237-20250821085009614-1258072230.png" alt="" loading="lazy"></p>
<p>通过以上界面可以知道,此时的 ASP.NET Core 部分已经收到了来自 WPF 部分的数据,且将其通过 HTTP 对外发射数据</p>
<p>本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快</p>
<p>先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码</p>
<pre><code>git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
</code></pre>
<p>以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码</p>
<pre><code>git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 3acde0686b53ca3122cb0fe28838624e1101500c
</code></pre>
<p>获取代码之后,进入 WPFDemo/BarnemwheanejayHelbellacall 文件夹,即可获取到源代码</p>
<p>更多技术博客,请参阅 博客导航</p>
</div>
<div id="MySignature" role="contentinfo">
<p>博客园博客只做备份,博客发布就不再更新,如果想看最新博客,请访问 https://blog.lindexi.com/</p>
<p>如图片看不见,请在浏览器开启不安全http内容兼容</p>
<img alt="知识共享许可协议" style="border-width: 0" src="https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png"><br>本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名[林德熙](https://www.cnblogs.com/lindexi)(包含链接:https://www.cnblogs.com/lindexi ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我[联系](mailto:lindexi_gd@163.com)。<br><br>
来源:https://www.cnblogs.com/lindexi/p/19049877
頁:
[1]