In .Net MAUI Blazor I can use an img tag to display an image from wwwroot folder. But how to display an image from the device's internal storage? And how to display images from application resources?
From my research :
You can actually get the path of the wwwroot folder in the razor application with : AppDomain.CurrentDomain.BaseDirectory
.
In windows you can add files in this folder that will be accessible from the Blazor HTML. However, in Android the wwwroot folder is embeded in the app and will not be accessible (AppDomain.CurrentDomain.BaseDirectory
return a empty folder).
After looking on the .NET MAUI github repo in the BlazorWebView
class I found :
public virtual IFileProvider CreateFileProvider(string contentRootDir)
{
// Call into the platform-specific code to get that platform's asset file provider
return ((BlazorWebViewHandler)(Handler!)).CreateFileProvider(contentRootDir);
}
Which can be used to pass files to Blazor. For exemple if you want to make accessible all the files from the AppDataDirectory :
using Microsoft.AspNetCore.Components.WebView.Maui;
using Microsoft.Extensions.FileProviders;
public class CustomFilesBlazorWebView : BlazorWebView
{
public override IFileProvider CreateFileProvider(string contentRootDir)
{
var lPhysicalFiles = new PhysicalFileProvider(FileSystem.Current.AppDataDirectory);
return new CompositeFileProvider(lPhysicalFiles, base.CreateFileProvider(contentRootDir));
}
}
Then in MainPage.xaml
:
<local:CustomFilesBlazorWebView HostPage="wwwroot/index.html" x:Name="WebView">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
</BlazorWebView.RootComponents>
</local:CustomFilesBlazorWebView>
For exemple if in AppDataDirectory you have a file images/user.png
in any Blazor component you can use:
<img src="images/user.png" />
From Internal storage
We can read it into bytes
and convert it to base64
string , then show on img
tag .
Giving that we've put an image called dog.png
in FileSystem.CacheDirectory
folder.
Sample code
@if (imageSource is not null)
{
<div>
<img src="@imageSource" width="200" height="200" />
</div>
}
@code {
private string? imageSource;
protected override void OnInitialized()
{
var newFile = Path.Combine(FileSystem.CacheDirectory, "dog.png");
var imageBytes = File.ReadAllBytes(newFile);
imageSource = Convert.ToBase64String(imageBytes);
imageSource = string.Format("data:image/png;base64,{0}", imageSource);
}
}
To display from resource, see … Blazor Hybrid static Files / .Net Maui:
- Add file to project, in a folder named Resources/Raw.
- Make sure file / Properties / Build Action = MauiAsset.
- Create a razor component that:
- Calls
Microsoft.Maui.Storage.FileSystem.OpenAppPackageFileAsync
to obtain a Stream for the resource. - Reads the Stream with a StreamReader.
- Calls StreamReader.ReadToEndAsync to read the file.
- Calls
Example razor code (from that link):
@page "/static-asset-example"
@using System.IO
@using Microsoft.Extensions.Logging
@using Microsoft.Maui.Storage
@inject ILogger<StaticAssetExample> Logger
<h1>Static Asset Example</h1>
<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";
protected override async Task OnInitializedAsync()
{
try
{
using var stream =
await FileSystem.OpenAppPackageFileAsync("Data.txt");
using var reader = new StreamReader(stream);
dataResourceText = await reader.ReadToEndAsync();
}
catch (FileNotFoundException ex)
{
dataResourceText = "Data file not found.";
Logger.LogError(ex, "'Resource/Raw/Data.txt' not found.");
}
}
}
To access local file (not an asset in resources) from razor code, you’ll need a service
that given the file name (or relative path), returns the file contents as a stream.
I’m not finding a doc saying how to do that for Maui, then inject that into razor code.
Such a service would use .Net File System Helpers to access the file. This would be similar to the MauiAsset example above, but using one of the path helpers, NOT calling OpenAppPackageFileAsync.
TBD - someone give reference link or example?
I solved in this way.
- Add the png image to Resources\Raw and set to MauiAsset compilation type
- Check the project file to avoid that the image is excluded via ItemGroup-> None Remove. In this case delete the ItemGroup related to the image.
After this:
In my razor component HTML
<img src="@imageSource">
In the code part:
private string? imageSource;
protected override async Task OnInitializedAsync()
{
try
{
using var stream =
await FileSystem.OpenAppPackageFileAsync("testimage.png");
using var reader = new StreamReader(stream);
byte[] result;
using (var streamReader = new MemoryStream())
{
stream.CopyTo(streamReader);
result = streamReader.ToArray();
}
imageSource = Convert.ToBase64String(result);
imageSource = string.Format("data:image/png;base64,{0}", imageSource);
}
catch (Exception ex)
{
//log error
}
}
I tried various methods. The Base64 approach has serious memory issues, as just a few high-resolution images can cause the app to crash. The other method using staticFiles works in Windows for writing images, but on Android, the wwwroot directory is within the bundle, and it's not possible to write images there. Eventually, I used the Object URL approach, which is better in terms of performance. However, this means that I have to either copy the images into the app (for instance, using SQLite) or store external addresses, and then read the stream every time to display the images. The code snippet for displaying the images is as follows:
...
<MudPaper Class="pa-3" Elevation="3">
@if (_objUrl is null)
{
<MudAlert Severity="Severity.Normal">Choose an Image</MudAlert>
}
else
{
<MudImage @ref="_image" Height="400" Alt="pic" Class="rounded-lg" Src="@_objUrl" @onload="Revoke" />
}
</MudPaper>
...
<script>
window.createObjectURL = async (imageStream) => {
const arrayBuffer = await imageStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
return URL.createObjectURL(blob);
}
window.revokeObjectURL = (url) => {
URL.revokeObjectURL(url);
}
</script>
@code
{
private MudImage _image;
private string? _objUrl;
private async Task Revoke(ProgressEventArgs args)
{
var imageSrc = _image.Src;
if (imageSrc is not null)
{
await JS.InvokeVoidAsync("revokeObjectURL", imageSrc);
}
}
private async Task UploadFiles(IBrowserFile file)
{
Debug.WriteLine($"size:{file.Size}");
await using var openReadStream = file.OpenReadStream(file.Size);
var dotnetImageStream = new DotNetStreamReference(openReadStream);
_objUrl = await JS.InvokeAsync<string>("createObjectURL", dotnetImageStream);
}
}
I couldn't find any better solutions. There seems to be a bit too little information on this topic.
© 2022 - 2024 — McMap. All rights reserved.