前言

好久没写文章了,水一篇

关于MAUI Blazor 显示本地图片这个问题,有大佬发过了。

就是 token 大佬的那篇

Blazor Hybrid (Blazor混合开发)更好的读取本地图片

主要思路就是读取本地图片,通过C#与JS互操作,将byte[]传给js,生成blob,图片的src中填写根据blob生成的url。

我之前一直使用这个办法,简单的优化了一下,无非也就是增加缓存。

但是这种方法的弊端也是很明显的:

  1. img的src每一次并不固定,需要替换

  2. Android端加载体积比较大的图片的速度,特别特别慢

所以有没有一种办法能够解决这两个问题

思考了很久,终于有了思路,

拦截网络请求/响应,读取本地文件并返回响应

搜索了一下,C#/MAUI中没有太好的拦截办法,只能从Webview下手

理论已有,实践开始

准备工作

新建一个MAUI Blazor项目

参考 配置基于文件名的多目标 ,更改项目文件(以.csproj结尾的文件),添加以下代码

<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-android')) != true">
<Compile Remove="**\**\*.Android.cs" />
<None Include="**\**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup> <!-- Both iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-ios')) != true AND $(TargetFramework.StartsWith('net7.0-maccatalyst')) != true">
<Compile Remove="**\**\*.MaciOS.cs" />
<None Include="**\**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup> <!-- iOS -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-ios')) != true">
<Compile Remove="**\**\*.iOS.cs" />
<None Include="**\**\*.iOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup> <!-- Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net7.0-maccatalyst')) != true">
<Compile Remove="**\**\*.MacCatalyst.cs" />
<None Include="**\**\*.MacCatalyst.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup> <!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true">
<Compile Remove="**\*.Windows.cs" />
<None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

分别添加MainPage.xaml.Android.csMainPage.xaml.MaciOS.csMainPage.xaml.Windows.cs

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent(); blazorWebView.BlazorWebViewInitializing += BlazorWebViewInitializing;
blazorWebView.BlazorWebViewInitialized -= BlazorWebViewInitialized;
} private partial void BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e);
private partial void BlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e);
}

MainPage.xaml.Android.cs,MainPage.xaml.MaciOS.cs,MainPage.xaml.Windows.cs

public partial class MainPage
{
private partial void BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e)
{
} private partial void BlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e)
{
}
}

Android

https://github.com/dotnet/maui/issues/11382

从这个issue中找到了拦截请求的办法

在ShouldInterceptRequest中添加请求不到时的一些处理。

因为这里填写的,是图片文件的本机绝对路径,安卓中的文件路径是符合浏览器url格式的,所以会被视为基于 https://0.0.0.0 这个基地址的相对路径去发起请求。

当然,它是请求不到的,因为压根就不存在。

所以我们去判断该路径的文件是否存在,存在就读取文件,返回一个新的响应。

注意,不是任意文件都可以的,你的App要对这个文件有访问权限。

MainPage.xaml.Android.cs

using Android.Webkit;
using Microsoft.AspNetCore.Components.WebView;
using Microsoft.AspNetCore.Components.WebView.Maui; namespace MauiBlazorLocalImage
{
public partial class MainPage
{
private partial void BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e)
{
} private partial void BlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e)
{ e.WebView.SetWebViewClient(new MyWebViewClient(e.WebView.WebViewClient));
} private class MyWebViewClient : WebViewClient
{
private WebViewClient WebViewClient { get; } public MyWebViewClient(WebViewClient webViewClient)
{
WebViewClient = webViewClient;
} public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return WebViewClient.ShouldOverrideUrlLoading(view, request);
} public override WebResourceResponse ShouldInterceptRequest(Android.Webkit.WebView view, IWebResourceRequest request)
{
var resourceResponse = WebViewClient.ShouldInterceptRequest(view, request);
if (resourceResponse == null)
return null;
if (resourceResponse.StatusCode == 404)
{
var path = request.Url.Path;
if (File.Exists(path))
{
string mime = MimeTypeMap.Singleton.GetMimeTypeFromExtension(Path.GetExtension(path));
string encoding = "UTF-8";
Stream stream = File.OpenRead(path);
return new(mime, encoding, stream);
}
}
//Debug.WriteLine("路径:" + request.Url.ToString());
return resourceResponse;
} public override void OnPageFinished(Android.Webkit.WebView view, string url)
=> WebViewClient.OnPageFinished(view, url); protected override void Dispose(bool disposing)
{
if (!disposing)
return; WebViewClient.Dispose();
}
}
}
}

下面做一个小例子

用MAUI的 MediaPicker.Default.PickPhotoAsync 去选择图片

这里不做过多的处理,Android中选择图片得到的路径实际上是复制到App的Cache文件夹下的图片文件路径

App对自己的FileSystem.Current.AppDataDirectory和FileSystem.Current.CacheDirectory这两个文件夹是有完全的读写权限的。

这里不做过多解释

Pages/Index.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<div>
<img src="@photoPath" style="max-width: 100%;" />
</div> <div style="word-wrap: break-word;">
@photoPath
</div> <div>
<button @onclick="PickPhoto">PickPhoto</button>
</div> @code
{
string photoPath;
private async Task PickPhoto()
{
var fileResult = await MediaPicker.Default.PickPhotoAsync();
var path = fileResult?.FullPath;
if (path is null)
{
return;
} photoPath = path;
await InvokeAsync(StateHasChanged);
}
}

看一下效果

(下面调试工具这个截图是后补的,所以路径不一致,忽略这些细节)

由此可以看到,已经成功拦截,并且把响应换成了我们自己创建的。

换一张大一点的图片,看看速度

特意选了一张13.28 MB的4k图片,速度还可以

Windows

在之前那个issue https://github.com/dotnet/maui/issues/11382 中,并没有关于Windows如何拦截Webview请求的方法。

Windows上的Webview是使用的微软自家的WebView2。

于是我在Webview2的官方文档中找到了这个

重写响应,以主动替换它

但有个难题,Windows上的文件路径不符合浏览器url格式,它会被视为文件请求自动变成file:///开头的路径

file:///开头的路径是请求不到的,这里不过多解释。

所以我们在使用Windows上的文件路径之前,先把它转义一下 Uri.EscapeDataString()

等到拦截请求后,再变回去 Uri.UnescapeDataString()

MainPage.xaml.Windows.cs

using Microsoft.AspNetCore.Components.WebView;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage.Streams; namespace MauiBlazorLocalImage
{
public partial class MainPage
{
private partial void BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e)
{
} private partial void BlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e)
{
var webview2 = e.WebView.CoreWebView2; webview2.WebResourceRequested += async (sender, args) =>
{
string path = new Uri(args.Request.Uri).AbsolutePath.TrimStart('/');
path = Uri.UnescapeDataString(path);
if (File.Exists(path))
{
using var contentStream = File.OpenRead(path);
IRandomAccessStream stream = await CopyContentToRandomAccessStreamAsync(contentStream);
var response = webview2.Environment.CreateWebResourceResponse(stream, 200, "OK", null);
args.Response = response;
}
}; //为什么这么写?我也不知道,Maui源码就是这么写的
async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream content)
{
using var memStream = new MemoryStream();
await content.CopyToAsync(memStream);
var randomAccessStream = new InMemoryRandomAccessStream();
await randomAccessStream.WriteAsync(memStream.GetWindowsRuntimeBuffer());
return randomAccessStream;
}
}
}
}

例子中的路径也要处理一下

Pages/Index.razor

     var fileResult = await MediaPicker.Default.PickPhotoAsync();
var path = fileResult.FullPath;
#if WINDOWS
path = Uri.UnescapeDataString(path);
#endif

看一下效果

(这个截图也是后补的,所以路径不一致,忽略这些细节)

iOS / mac OS

在这篇文章最开始写的时候,笔者并没有找到iOS / mac OS中如何拦截请求

本来已经要放弃了,但天无绝人之路

抱着严谨的态度,又做了一些努力,看 Maui 源码,看 issue

克服了种种困难之后,终于有办法了

MainPage.xaml.Windows.cs

using Foundation;
using Microsoft.AspNetCore.Components.WebView;
using System.Runtime.Versioning;
using WebKit; namespace MauiBlazorLocalImage
{
public partial class MainPage
{
private partial void BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e)
{
e.Configuration.SetUrlSchemeHandler(new MySchemeHandler(), "myfile");
} private partial void BlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e)
{
} private class MySchemeHandler : NSObject, IWKUrlSchemeHandler
{
[Export("webView:startURLSchemeTask:")]
[SupportedOSPlatform("ios11.0")]
public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
{
if (urlSchemeTask.Request.Url == null)
{
return;
} var path = urlSchemeTask.Request.Url?.Path ?? "";
if (File.Exists(path))
{
byte[] bytes = File.ReadAllBytes(path);
using var response = new NSHttpUrlResponse(urlSchemeTask.Request.Url, 200, "HTTP/1.1", null);
urlSchemeTask.DidReceiveResponse(response);
urlSchemeTask.DidReceiveData(NSData.FromArray(bytes));
urlSchemeTask.DidFinish();
}
} [Export("webView:stopURLSchemeTask:")]
public void StopUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
{
}
}
}
}

iOS / mac OS中不能拦截 http 和 https 协议,但是可以拦截自定义协议

所以我们这里添加一个自定义协议 myfile

(不能用file,因为已经被注册过了,被注册过的协议在这里是不能设置的)

实际上,iOS / mac OS中,页面的协议头也是自定义的 app协议,而不是像windows或Android中的https

例子中的路径也要处理一下

Pages/Index.razor

        var fileResult = await MediaPicker.Default.PickPhotoAsync();
var path = fileResult?.FullPath; if (path is null)
{
return;
} #if WINDOWS
path = Uri.EscapeDataString(path);
#elif IOS || MACCATALYST
path = "myfile://" + path;
#endif

看一下效果

mac OS

iOS

mac上的浏览器开发者工具最近有bug,用不了,所以就没有开发者工具的截图了

cannot use developer tools to debug blazor hybrid MAUI application in Mac OS

后记

虽然已经基本实现了最开始的目标,不过受限于笔者水平,可能还是不够完美。

文章到这里就结束了,感谢你的阅读

源码地址

本文中的例子的源码放到 Github 和 Gitee 了

有需要的可以去看一下

Github: https://github.com/Yu-Core/MauiBlazorLocalImage

Gitee: https://gitee.com/Yu-core/MauiBlazorLocalImage

MAUI Blazor 显示本地图片的新思路的更多相关文章

  1. Android ImageView显示本地图片

    Android ImageView 显示本地图片 布局文件 <?xml version="1.0" encoding="utf-8"?> <R ...

  2. Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果

    版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/1873 ...

  3. Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果

    大家好!过完年回来到现在差不多一个月没写文章了,一是觉得不知道写哪些方面的文章,没有好的题材来写,二是因为自己的一些私事给耽误了,所以过完年的第一篇文章到现在才发表出来,2014年我还是会继续在CSD ...

  4. Atitit. html 使用js显示本地图片的设计方案.doc

    Atitit. html 使用js显示本地图片的设计方案.doc 1.  Local mode  是可以的..web模式走有的不能兰.1 2. IE8.0 显示本地图片 img.src=本地图片路径无 ...

  5. Atitit. IE8.0 显示本地图片预览解决方案 img.src=本地图片路径无效的解决方案

    Atitit. IE8.0 显示本地图片预览解决方案 img.src=本地图片路径无效的解决方案 1. IE8.0 显示本地图片 img.src=本地图片路径无效的解决方案1 1.1. div来完成  ...

  6. 在InternetExplorer.Application中显示本地图片

    忘记了,喜欢一个人的感觉 Demon's Blog  »  程序设计  »  在InternetExplorer.Application中显示本地图片 « 对VBS效率的再思考——处理二进制数据 Wo ...

  7. Slog71_选取、上传和显示本地图片GET !(微信小程序之云开发-全栈时代3)

    ArthurSlog SLog-71 Year·1 Guangzhou·China Sep 12th 2018 ArthurSlog Page GitHub NPM Package Page 掘金主页 ...

  8. tomcat中显示本地图片①(未解决)

    <本模块文仅作为学习过程中的自我总结,有需要可参看,欢迎指导与提出建议,很多地方可能断章取义,理解不到位,虚心求学.谢谢!> 资料查阅原因:2018/7/10(做项目中显示详情页面,从数据 ...

  9. SpringBoot之显示本地图片范例

    controller // 扫描指定目录下的图片进行展示 @RequestMapping("/showPics") public ModelAndView showPics(Mod ...

  10. HTML5 Canvas显示本地图片实例1、Canvas预览图片实例1

    1.前台代码: <input id="fileOne" type="file" /> <canvas id="canvasOne&q ...

随机推荐

  1. vue前端路由的两种模式,hash与history的区别

    1.直观区别: hash模式url带#号,history模式不带#号. 2.深层区别: hash模式url里面永远带着#号,我们在开发当中默认使用这个模式. 如果用户考虑url的规范那么就需要使用hi ...

  2. 【机器学习与深度学习理论要点】07.A/B测试的概念及用法

    1)什么是A/B测试? A/B测试就是两种模型同时运行,并在实际环境中验证其效果的方式.在互联网公司中,A/B测试是验证新模块.新功能.新产品是否有效,新算法.新模型的效果是否有提升,新设计是否收到用 ...

  3. STM32 + RTThread + UGUI

    一.概述 开发板:STM32F103C8T6 显示器:ST7735S RT-Thread:5.0.0 玩过 GUI 的小伙伴都知道,界面的显示是一个个像素点组合起来的,那么直接构建出来炫酷的 GUI ...

  4. CF1808E Minibuses on Venus 智商毁灭记

    都要考省选了大脑还在这里下线 场上看到这道题很快推出了 \(k\) 为奇数的搞法,发现可以直接做到 \(O(k\log n)\),一阵狂喜然后肝起了 E3,结果 E1 都没过. 事实上这道题可以直接做 ...

  5. Pillow模块——生成随机验证码

    urls.py path('get_code/',views.get_code), views.py中 from PIL import Image,ImageFont,ImageDraw " ...

  6. 2021-01-11:linux中,如何看内存的使用情况呢?

    福哥答案2021-01-11: 1.free:查看内存占用情况,会直接返回,常用参数 -M.-G 是以MB或GB为单位返回结果.2.sar:定时检测系统资源占用情况,-r 参数是内存资源,一般用法 s ...

  7. 2022-03-26:给定一个无向图, 从任何一个点x出发,比如有一条路径: x -> a -> b -> c -> y, 这条路径上有5个点并且5个点都不一样的话,我们说(x,a,b,c,y)是一条

    2022-03-26:给定一个无向图, 从任何一个点x出发,比如有一条路径: x -> a -> b -> c -> y, 这条路径上有5个点并且5个点都不一样的话,我们说(x ...

  8. 2021-08-24:合并石头的最低成本。有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的

    2021-08-24:合并石头的最低成本.有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头.每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的 ...

  9. 中文环境下使用 huggingface 模型替换 OpenAI的Embedding 接口

    OpenAI的文本嵌入衡量文本字符串的相关性.嵌入通常用于: 搜索(其中结果按与查询字符串的相关性排名) 聚类(其中文本字符串按相似性分组) 推荐(推荐具有相关文本字符串的项目) 异常检测(识别出相关 ...

  10. 记一次处理挖矿程序引发的postgres 连接超时

    近一段时间内发现自己的服务器总是警告被挖矿,然处理挖矿程序中也引发了许多其他的问题,也从中学到了其他的知识,趁今天未加班梳理一下便于巩固,记录日常 文章目录 一.查找进程 1.使用 ll /proc/ ...