Skip to content

Commit

Permalink
Merge kb-image-preview-2260 into production (#2264)
Browse files Browse the repository at this point in the history
* kb(fileselect|upload): Add KB for image preview

* Add description

* Update knowledge-base/upload-preview-image.md

Co-authored-by: Iva Stefanova Koevska-Atanasova <koevska@progress.com>

* Update knowledge-base/upload-preview-image.md

Co-authored-by: Iva Stefanova Koevska-Atanasova <koevska@progress.com>

* Update knowledge-base/upload-preview-image.md

Co-authored-by: Iva Stefanova Koevska-Atanasova <koevska@progress.com>

* polishing

---------

Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com>
Co-authored-by: Iva Stefanova Koevska-Atanasova <koevska@progress.com>
  • Loading branch information
3 people committed Jul 24, 2024
1 parent 78c2031 commit 75b226d
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 1 deletion.
2 changes: 1 addition & 1 deletion components/upload/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public async Task<IActionResult> Save(IFormFile files)

// OR
// default statuc code 200 and custom success message
// default status code 200 and custom success message
return new OkObjectResult("Custom success message.");

// OR
Expand Down
303 changes: 303 additions & 0 deletions knowledge-base/upload-preview-image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
---
title: Preview Selected or Uploaded Image
description: Learn how to display or preview the images that the user selected in the Telerik Blazor Upload or FileSelect component.
type: how-to
page_title: How to Preview Selected or Uploaded Image
slug: upload-kb-preview-image
position:
tags: telerik, blazor, fileselect, upload
ticketid: 1468224, 1476179, 1481266, 1567963, 1621741
res_type: kb
---

## Environment

<table>
<tbody>
<tr>
<td>Product</td>
<td>FileSelect for Blazor, <br /> Upload for Blazor</td>
</tr>
</tbody>
</table>

## Description

This KB article answers the following questions:

* How to immediately display the uploaded image files in the Blazor app?
* How to render an image preview when using the Blazor FileSelect?
* How to show the uploaded file as a preview when using the Upload control? I prefer to show a Base64 image without saving the file to the server.
* How to preview the selected image in the Upload `OnSelect` event?
* How to get the raw file content from the Upload component and add it to a memory stream?

## Solution

Users can preview images that are uploaded through the [Upload component]({%slug upload-overview%}) or selected through the [FileSelect component]({%slug fileselect-overview%}).

In Blazor, [previewing images is easier when using the FileSelect](#using-the-fileselect), but based on your requirements you might [choose the Upload over the FileSelect]({%slug upload-overview%}#upload-vs-fileselect) or vice versa.

### Using the Upload

1. Set the [`Accept`, `AllowedExtensions`, and `MaxFileSize`]({%slug upload-overview%}#upload-parameters) parameters of the Upload.
1. [Ensure the Blazor app can handle large uploaded files]({%slug upload-overview%}#large-file-uploads) with a size that matches or exceeds `MaxFileSize`.
1. [Implement a controller action method to receive the uploaded files]({%slug upload-overview%}#implement-controller-methods). Also see the [example on the Upload Events page]({%slug upload-events%}#example).
1. Read the uploaded image(s) in the controller and return an image URL or [Base64 data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) for the `<img>` tag(s) to render. You can choose to provide the original image to the UI or a smaller version for greater efficiency.
1. Subscribe to the [Upload `OnSuccess` event]({%slug upload-events%}#onsuccess) to detect completed file uploads and obtain the `<img>` tag's `src` value. You can also use `OnSuccess` to detect file removals in the Upload component and remove the respective `<img>` tags.

You cannot preview the image in the Upload `OnSelect` event, because this event handler has no access to the file contents.

>caption Preview uploaded images when using the Upload component
<div class="skip-repl"></div>

````Razor
@inject NavigationManager NavigationManager
<h2>Upload</h2>
<TelerikUpload Accept="@string.Join(",", ImageFileExtensions)"
AllowedExtensions="@ImageFileExtensions"
MaxFileSize="@MaxImageSize"
SaveUrl="@ToAbsoluteUrl("api/upload/saveimage")"
RemoveUrl="@ToAbsoluteUrl("api/upload/removeimage")"
OnSuccess="@OnUploadSuccess">
</TelerikUpload>
<div class="image-preview">
@foreach (var image in UploadImages)
{
<img src="@image.ImageSource" class="k-border-2 k-border-solid k-border-primary" />
}
</div>
<style>
.image-preview {
display: flex;
gap: 2em;
flex-wrap: wrap;
align-items: center;
}
.image-preview img {
max-width: 200px;
max-height: 200px;
}
</style>
@code {
private List<string> ImageFileExtensions { get; set; } = new List<string>() { ".jpg", ".jpeg", ".png", ".gif" };
private int? MaxImageSize { get; set; } = 8 * 1024 * 1024;
private List<ImageModel> UploadImages { get; set; } = new();
private void OnUploadSuccess(UploadSuccessEventArgs args)
{
var fileInfo = args.Files.First();
if (args.Operation == UploadOperationType.Upload)
{
UploadImages.Add(new ImageModel()
{
ImageId = fileInfo.Id,
ImageSource = args.Request.ResponseText
});
}
else
{
UploadImages.RemoveAll(x => x.ImageId == fileInfo.Id);
}
}
private string ToAbsoluteUrl(string url)
{
return $"{NavigationManager.BaseUri}{url}";
}
public class ImageModel
{
public string ImageId { get; set; } = string.Empty;
public string ImageSource { get; set; } = string.Empty;
}
}
````
````Controller
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace TelerikBlazorUpload.Controllers
{
[Route("api/[controller]/[action]")]
public class UploadController : ControllerBase
{
public IWebHostEnvironment HostingEnvironment { get; set; }
public UploadController(IWebHostEnvironment hostingEnvironment)
{
HostingEnvironment = hostingEnvironment;
}
[HttpPost]
public async Task<string> SaveImage(IFormFile files)
{
string response = "";
if (files != null)
{
try
{
// OPTION 1. Return a Base64 string and display the image source as a data URI.
// Not efficient for large files.
//var imageBytes = new byte[files.Length];
//await files.OpenReadStream().ReadAsync(imageBytes);
//response = $"data:image/{Path.GetExtension(files.FileName)};base64,{Convert.ToBase64String(imageBytes.ToArray())}";
// ===============
// OPTION 2. Save the image as a file and return the image URL.
// May not be efficient for large files. You can save another smaller version of the file.
var imageSaveLocation = Path.Combine(HostingEnvironment.WebRootPath, files.FileName);
using var imageStream = new FileStream(imageSaveLocation, FileMode.Create);
await files.CopyToAsync(imageStream);
response = files.FileName;
}
catch (Exception e)
{
Response.StatusCode = 500;
await Response.WriteAsync($"File processing failed: {e.Message}");
}
}
return response;
}
[HttpPost]
public async Task<IActionResult> RemoveImage([FromForm] string files)
{
if (files != null)
{
try
{
var imageLocation = Path.Combine(HostingEnvironment.WebRootPath, files);
if (System.IO.File.Exists(imageLocation))
{
System.IO.File.Delete(imageLocation);
}
}
catch
{
Response.StatusCode = 500;
await Response.WriteAsync("File deletion failed.");
}
}
return new EmptyResult();
}
}
}
````

### Using the FileSelect

1. Set the [`Accept`, `AllowedExtensions`, and `MaxFileSize`]({%slug fileselect-overview%}#fileselect-parameters) parameters of the FileSelect.
1. [Increase the SignalR message size]({%slug common-kb-increase-signalr-max-message-size%}) to match or exceed `MaxFileSize`.
1. Subscribe to the [FileSelect `OnSelect` event]({%slug fileselect-events%}#onselect).
1. Read the selected image(s) and create a [Base64 data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) for the `<img>` tag(s) to render.
1. (optional) Subscribe to the [FileSelect `OnRemove` event]({%slug fileselect-events%}#onremove) to remove the image preview.

>caption Preview selected images when using the FileSelect component
````CSHTML
<h2>FileSelect</h2>
<TelerikFileSelect Accept="@string.Join(",", ImageFileExtensions)"
AllowedExtensions="@ImageFileExtensions"
MaxFileSize="@MaxImageSize"
OnSelect="@OnFileSelectSelect"
OnRemove="@OnFileSelectRemove">
</TelerikFileSelect>
@if (FileSelectImages.Any())
{
<h2>Image Preview</h2>
<div class="image-preview">
@foreach (var image in FileSelectImages)
{
<img src="@image.ImageSource" class="k-border-2 k-border-solid k-border-primary" />
}
</div>
}
<style>
.image-preview {
display: flex;
gap: 2em;
flex-wrap: wrap;
align-items: center;
}
.image-preview img {
max-width: 200px;
max-height: 200px;
}
</style>
@code {
private List<string> ImageFileExtensions { get; set; } = new List<string>() { ".jpg", ".jpeg", ".png", ".gif" };
private int? MaxImageSize { get; set; } = 8 * 1024 * 1024;
private List<ImageModel> FileSelectImages { get; set; } = new();
private async Task OnFileSelectSelect(FileSelectEventArgs args)
{
foreach (var file in args.Files)
{
if (!file.InvalidExtension && !file.InvalidMaxFileSize)
{
var imageBytes = new byte[file.Size];
await using MemoryStream ms = new MemoryStream(imageBytes);
await file.Stream.CopyToAsync(ms);
FileSelectImages.Add(new ImageModel()
{
ImageId = file.Id,
ImageSource = $"data:image/{file.Extension};base64,{Convert.ToBase64String(ms.ToArray())}"
});
}
}
}
private void OnFileSelectRemove(FileSelectEventArgs args)
{
foreach (var file in args.Files)
{
FileSelectImages.RemoveAll(x => x.ImageId == file.Id);
}
}
public class ImageModel
{
public string ImageId { get; set; } = string.Empty;
public string ImageSource { get; set; } = string.Empty;
}
}
````

## See Also

* [Upload Overview]({%slug upload-overview%})
* [Handle Upload Events]({%slug upload-events%})
* [FileSelect Overview]({%slug fileselect-overview%})
* [Handle FileSelect Events]({%slug fileselect-events%})

0 comments on commit 75b226d

Please sign in to comment.