.NET Maui中<WindowsPackageType>None时FileSaver功能失效问题
开发.NET Maui应用时,项目配置中设置了<WindowsPackageType>None</WindowsPackageType>,launchsettings.json配置如下:
{ "profiles": { "Windows Machine": { "commandName": "Project", "nativeDebugging": false } } }
使用CommunityToolkit.Maui.Storage.FileSaver的文件保存功能时出现报错,错误信息:
{FileSaverResult { FilePath = , Exception = System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
at CommunityToolkit.Maui.Storage.FileSaverImplementation.InternalSaveAsync(String initialPath, String fileName, Stream stream, CancellationToken cancellationToken) in //src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs:line 31
at CommunityToolkit.Maui.Storage.FileSaverImplementation.InternalSaveAsync(String initialPath, String fileName, Stream stream, CancellationToken cancellationToken) in //src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.windows.cs:line 38
at CommunityToolkit.Maui.Storage.FileSaverImplementation.SaveAsync(String fileName, Stream stream, CancellationToken cancellationToken) in /_/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.shared.cs:line 26, IsSuccessful = False }}
但打包为MSIX包运行时,FileSaver功能正常。
原因分析
当设置<WindowsPackageType>None</WindowsPackageType>时,应用以非打包桌面模式运行,而CommunityToolkit.Maui的FileSaver针对Windows平台的默认实现依赖打包环境的COM组件权限支持,非打包模式下无法正常调用相关组件,从而触发E_FAIL错误。
解决方法
方法1:切换调试配置为打包模式
修改launchsettings.json,将commandName改为MsixPackage:
{ "profiles": { "Windows Machine": { "commandName": "MsixPackage", "nativeDebugging": false } } }
同时将项目配置中的<WindowsPackageType>设为MSIX(或直接移除该配置,默认即为MSIX),调试时应用将以打包方式运行,FileSaver功能即可恢复正常。
方法2:自定义非打包模式下的文件保存逻辑
若需保持非打包模式运行,可针对Windows平台重写FileSaver实现,使用传统Win32文件对话框:
- 在项目的Windows平台目录下添加自定义实现类:
using CommunityToolkit.Maui.Storage; using Microsoft.UI.Xaml; using System.IO; using System.Threading; using System.Threading.Tasks; using WinRT.Interop; using Microsoft.Win32; namespace YourAppNamespace.Windows; public class CustomWindowsFileSaver : IFileSaver { public async Task<FileSaverResult> SaveAsync(string fileName, Stream stream, CancellationToken cancellationToken) { return await SaveAsync(string.Empty, fileName, stream, cancellationToken); } public async Task<FileSaverResult> SaveAsync(string initialPath, string fileName, Stream stream, CancellationToken cancellationToken) { var saveFileDialog = new SaveFileDialog(); saveFileDialog.FileName = fileName; // 获取当前窗口句柄 var windowHandle = WindowNative.GetWindowHandle(Application.Current.Windows[0].Handler.PlatformView); var hwnd = new System.IntPtr(windowHandle); var dialogResult = saveFileDialog.ShowDialog(new WindowWrapper(hwnd)); if (dialogResult == true) { try { using var fileStream = new FileStream(saveFileDialog.FileName, FileMode.Create); await stream.CopyToAsync(fileStream, cancellationToken); return FileSaverResult.Success(saveFileDialog.FileName); } catch (Exception ex) { return FileSaverResult.Failure(ex); } } return FileSaverResult.Failure(new TaskCanceledException("用户取消了保存操作")); } // 辅助类用于传递窗口句柄 private class WindowWrapper : System.Windows.IWin32Window { public WindowWrapper(IntPtr handle) => Handle = handle; public IntPtr Handle { get; } } }
- 在
MauiProgram.cs中注册自定义实现:
builder.Services.AddSingleton<IFileSaver>(sp => { #if WINDOWS return new YourAppNamespace.Windows.CustomWindowsFileSaver(); #else return new CommunityToolkit.Maui.Storage.FileSaverImplementation(); #endif });
此方案在非打包模式下会调用Win32原生文件对话框,绕开对打包环境COM组件的依赖,解决报错问题。
内容的提问来源于stack exchange,提问作者Tom

