0

Right now i have the following lines of code:

private async Task SetImageUsingStreamingAsync()
{
    var imageStream = await GetImageStreamAsync();
    var dotnetImageStream = new DotNetStreamReference(imageStream);
    await JSRuntime.InvokeVoidAsync("setImageUsingStreaming", 
        "image1", dotnetImageStream);
}

Above snippet gets a Stream, and repackages that stream into DotNetStreamReferance, and then passes that DotNetStreamReferance to a JS function:

async function setImageUsingStreaming(imageElementId, imageStream) {
  const arrayBuffer = await imageStream.arrayBuffer();
  const blob = new Blob([arrayBuffer]);
  const url = URL.createObjectURL(blob);
  document.getElementById(imageElementId).src = url;
} 

which populates an html element by ID. This is all fine, and works, but if i want to have a loadingscreen while i get the streams from the DB things get messy:

if(isloading)
{
  show loadingscreen
}
else
{
   foreach(string in listofstrings)
   {
     <img id="somename"></img>
   }
}

the img element does not "show up", and causes an error in the js snippet if the bool isloading is not changed to false BEFORE populating the images on the site. Which kind of defeats the purpose with a loadingscreen.

Is there a smarter way of doing this? Preferably without using javascript, since i dont really know that language and cannot modify it to my needs.

The above code is a direct reference from .net documentation on blazor(not the loading part, but the stream to image part): https://learn.microsoft.com/en-us/aspnet/core/blazor/images?view=aspnetcore-6.0

Cryptojanne
  • 17
  • 1
  • 4
  • The problem might be that the code isn't rendering the image tags while loading, so setImageUsingStreaming fails because there is no element with the ID. Loading screens are usually done by hiding the section with a css class that applies display:none and then removing the class when loaded. This way all the elements are there in the DOM, but hidden from the user until ready. Do you see any error message in the browser dev console? – Yogi May 04 '22 at 14:41
  • Yes yogi, that is exactly the problem. The element has not been "deployed" when calling setImageUsingStreaming. using dispay:none could work, but seems like a nasty workaround. Is that how it is normally done? I would prefer if i was able to make the html element "in memory" before it being rendered, but that might not be do able. – Cryptojanne May 04 '22 at 14:53
  • 1
    Building a hidden section of the DOM and then unhiding it is common. Yet, in your case it seems like you could do this without resorting to JSRuntime. You could convert the images to dataURLs in C# render them directly to the page with Razor. Look at this SO question for ideas: [how to convert Image to Data URI for Html with C#?](https://stackoverflow.com/a/6826577/943435) – Yogi May 04 '22 at 15:07
  • 1
    Also, here is a [Blazor Fiddle](https://blazorfiddle.com/s/aew2uq7q) that I created for another question on SO about loading screens. This was for a data grid, but the same idea could be applied to a list of image dataURLs that you're loading. – Yogi May 04 '22 at 15:25
  • Yogi, thank you for your replies. I have researched what you said for hour and a half and it seems like it is doable. I just want some clearification that i am understanding correctly: Stream -> MemoryStream -> byte[] or maybe base64? What i fail to understand is how to reference that byte[] or base64 to a html element to show the image. – Cryptojanne May 04 '22 at 17:08
  • 1
    You seem to understand correctly. Create an asnyc method that queries the database, then each image is converted to a dataURL, then set isLoading false, and in the Razor markup loop through the list of dattaURLs and write . And Blazor will update that section and the images will appear. I'm sure you'll need to experiment to get it right, but it will be a much better solution that using JSRuntime. – Yogi May 04 '22 at 19:05

2 Answers2

0
if (isloading) 
{
  // show loadingscreen
} 
else 
{
    // Populate a list with image elements...
    List imageElements = new List();
    foreach(string in listofstrings) 
    {
        Element image = new Element("img");
        image.Id = "somename";
        imageElements.Add(image);
    }

    // ...and append the list to the DOM using RenderElementsAsync
    // to ensure that it is rendered only when the list is fully
    // populated.
    await JSRuntime.InvokeAsync("setImageUsingStreaming", imageElements, dotnetImageStream);
}

The idea is to populate an in-memory list of elements, and then append the list to the DOM in one go, by rendering it asynchronously using RenderElementsAsync. Note: In your original code, you should invoke the JavaScript function setImageUsingStreaming() asynchronously. That is, you should use InvokeAsync instead of InvokeVoidAsync. Let me know if this helps...

spaleet
  • 838
  • 2
  • 10
  • 23
  • Can you explain why OP should use InvokeAsync when no value is being returned? Isn't InvokeVoidAsync also an async call? – Yogi May 04 '22 at 14:29
  • This would not work, the list would populate the page without the images for a brief second, before the javascript has finished getting the images. – Cryptojanne May 04 '22 at 14:57
-2

I figured it based on Yogi's answer in the comment section of my post:

there is absolutely no need to use JS here, i dont know why the blazor docs prefer it that way.

a simple sample on how i did:

private async Task PopulateImageFromStream(Stream stream)
    {
            MemoryStream ms = new MemoryStream();
            stream.CopyTo(ms);
            byte[] byteArray = ms.ToArray();
            var b64String = Convert.ToBase64String(byteArray);
            string imageURL = "data:image/png;base64," + b64String;
        }
    }

add the imageURL to an array, or simply declare it globaly to get it in HTML:

<img src="@imageURL">
Cryptojanne
  • 17
  • 1
  • 4
  • 1
    For other people reading this: While base64 strings work, they're a very bad idea: the base64 string is larger than the original image, it doesn't work well with SEO, and most importantly browsers can't cache a base64 string. This is a very, very bad idea. Google will give you plenty of articles with reasons why you shouldn't do this. – yesman Aug 30 '22 at 11:44