Stephen Ellis
Stephen Ellis

Reputation: 2711

Blazor Hybrid WPF - Serving up Dynamic Images

I am writing a WPF application using Blazor Hybrid. I'm turning a Web App written in asp.net core into a local-only app.

My web app had some images which it would generate on the fly in a Controller. You could show them in the html with a local http call, e.g. <img src="/dynamic-imgs/387854.png" />.

In asp.net core, this is easy, but in Blazor Hybrid, I don't know how to serve up data to a url. In my WPF version, how would I go about serving up dynamic data to the WebView based on a url?

Attempted Solution

The closest I've come to solving this is to intercept calls made by the WebView2 component as per the demo below. (For the purposes of the demo, I'm not dynamically generating images, but pulling them from the file system).

This solution allows me to intercept images if they're called from a domain other than 0.0.0.0, e.g. https://somedomain/img/someimage.png. If I try to do the same with 0.0.0.0 (/img/someimage.png) a get a Not Found error. I'm presuming in this latter case, the Blazor routing system is getting in the way of the response after I've handled WebResourceRequested.

 var rootPath = "https://0.0.0.0/imgs/"; //this doesn't work.
 var rootPath = "https://demo/imgs/"; //this works
 webView.CoreWebView2.AddWebResourceRequestedFilter($"{rootPath}*", CoreWebView2WebResourceContext.All);
 webView.CoreWebView2.WebResourceRequested += delegate (object? sender,
                                      CoreWebView2WebResourceRequestedEventArgs args)
 {
     if (!args.Request.Uri.StartsWith($"{rootPath}")) return;

     string assetsFilePath = "E:\IMAGES" +
                             args.Request.Uri.Substring($"{rootPath}*".Length - 1);
     try
     {
         var deferral = args.GetDeferral();
         if (File.Exists(assetsFilePath)){

         }
         FileStream fs = File.OpenRead(assetsFilePath);
         ManagedStream ms = new ManagedStream(fs);
         string headers = "";
         if (assetsFilePath.EndsWith(".png"))
         {
             headers = "Content-Type: image/png";
         }
        

         args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(ms, 200, "OK", headers);
         deferral.Complete();
     }
     catch (Exception e)
     {

         args.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(
                                                         null, 404, "Not found", "");
     }
 };

This code relies on the following ManagedStream class

public class ManagedStream : Stream
{
    public ManagedStream(Stream s)
    {
        s_ = s;
    }

    public override bool CanRead => s_.CanRead;

    public override bool CanSeek => s_.CanSeek;

    public override bool CanWrite => s_.CanWrite;

    public override long Length => s_.Length;

    public override long Position { get => s_.Position; set => s_.Position = value; }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return s_.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int read = 0;
        try
        {
            read = s_.Read(buffer, offset, count);
            if (read == 0)
            {
                s_.Dispose();
            }
        }
        catch
        {
            s_.Dispose();
            throw;
        }
        return read;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    private Stream s_;
}

Upvotes: 0

Views: 94

Answers (1)

Doctor Coetzee
Doctor Coetzee

Reputation: 1

If your images are located in a subfolder of your app you could do this: (Note the initial "/" is removed.)

<img src="dynamic-imgs/387854.png" />

If your images are located somewhere else you could do this:

<img src="e:/images/dynamic-imgs/387854.png" />

If you have the image in memory and don't want to save it to disk you can cast it to a Base64 string and serve it directly:

@page "/test"

<img src=@DynamicImageData(387854) />
 
@code {
    string DynamicImageData(int id)
    {
        //just getting image from disk as example
        var bytes = System.IO.File.ReadAllBytes(string.Format("dynamic-imgs/{0}.png", id));
        System.Drawing.Image image;
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes))
        {
            image = System.Drawing.Image.FromStream(ms);
        }

        return ImageToDataString(image);
    }

    string ImageToDataString(System.Drawing.Image image)
    {
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
        {
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            return "data:image/png;base64," + System.Convert.ToBase64String(ms.ToArray());
        }
    }
}

You can also base your result on a dynamic url like this:

@page "/test/{ID}"

<img src=@DynamicImageData() />

@code {
    [Parameter]
    public int ID { get; set; }

    string DynamicImageData()
    {
        //just getting image from disk as example
        var bytes = System.IO.File.ReadAllBytes(string.Format("dynamic-imgs/{0}.png", ID));
        System.Drawing.Image image;
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes))
        {
            image = System.Drawing.Image.FromStream(ms);
        }

        return ImageToDataString(image);
    }

    string ImageToDataString(System.Drawing.Image image)
    {
        using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
        {
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            return "data:image/png;base64," + System.Convert.ToBase64String(ms.ToArray());
        }
    }
}

If your data needs to be displayed in a component you only using the parameter and not serve it as a page.

<Mycomponent ID=387854/>

Upvotes: -1

Related Questions