How to display local image as well as resources image in .Net MAUI Blazor
Asked Answered
A

5

7

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?

Aintab answered 6/6, 2022 at 4:32 Comment(0)
S
8

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" />
Sextuplicate answered 30/1, 2023 at 9:52 Comment(3)
I'm not sure what assembly or namespace is needed here. I haven't found the BlazorWebView except in Windows.Boorman
@Boorman I eddited the usings if that help. Wym "except in Windows" you are developping on Linux ?Sextuplicate
I test it and it works on windows and android. I am surprised that it can work and is so simple... ...Cush
W
5

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);
    }
}
Wishful answered 7/6, 2022 at 7:26 Comment(1)
Thanks for this answer, which works for Internal storage Files. However, I found that it has a couple of issues, mainly it's slow and forces you to control the loading process, which is messy in case you use Virtualization. Do you know of any alternative way to provide a direct link to a resource in internal storage? Been looking for a while.Example
T
3

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.

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?

Trapan answered 7/6, 2022 at 1:0 Comment(1)
Did you find any example for a non-Resource file? I'm struggling to find how to get a stream for a filesystem image. I'm using a workaround with a base64 conversion, but it is slow.Example
W
3

I solved in this way.

  1. Add the png image to Resources\Raw and set to MauiAsset compilation type
  2. 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
    }

}
Wentzel answered 30/1, 2023 at 17:11 Comment(0)
C
2

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.

Cush answered 1/12, 2023 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.