How to Upload File in Grid
Environment
Product |
Grid for Blazor, Upload for Blazor |
Description
How to upload and download files in a Blazor Grid?
How to display an icon and a download link in a Grid column?
How to add an upload button in every Grid row?
How to upload photos and files as an attachment in a Blazor Grid?
Solution
Here are the required steps to implement file uploading inside the Telerik Blazor Grid.
- The Upload occupies more space than a simple textbox. Consider Grid popup editing with a wider popup edit form.
-
Configure an Upload component inside a Grid column
<EditorTemplate>
. - Handle the
OnUpload
event of the Upload to send custom information to the Upload controller, for example, information about the Grid data item. - Implement the Upload controller methods, which receive and delete the uploaded files. File deletion is optional.
- Handle the
OnSuccess
event of the Upload to confirm successful uploads or file deletions, and update the Grid data item, which is the<EditorTemplate>
context
. - The name of the saved file on the server can depend on the Razor UI or on the controller. If it depends on the UI, send it to the controller via the
OnUpload
event arguments (args.RequestData
). If the file name depends on the controller, receive it in theOnSuccess
event arguments viaargs.Request.ResponseText
. - Handle the Grid
OnUpdate
,OnCreate
andOnDelete
events to commit changes to the Grid data source. Optionally, delete the respective saved files inOnDelete
. - Display the uploaded files as images or download links in a Grid column
<Template>
.
Example
Upload Files in a Blazor Grid
The tabs below show a possible implementation for the Razor UI, Save
and Remove
controller methods.
@inject HttpClient HttpClient
@inject NavigationManager NavigationManager
<TelerikGrid Data="@GridData"
TItem="@Product"
EditMode="@GridEditMode.Popup"
OnUpdate="@OnGridUpdate"
OnCreate="@OnGridCreate"
OnDelete="@OnGridDelete">
<GridSettings>
<GridPopupEditSettings Width="600px" />
</GridSettings>
<GridToolBarTemplate>
<GridCommandButton Command="Add" Icon="@SvgIcon.Plus">Add New</GridCommandButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(Product.ImageUrl)" Width="160px" Title="Product Image">
<Template>
@{
var dataItem = (Product)context;
if (!string.IsNullOrEmpty(dataItem.ImageUrl))
{
<img src="@dataItem.ImageUrl" alt="@dataItem.Name" class="product-image" />
<a class="download-link" href="@dataItem.ImageUrl">@dataItem.Name</a>
}
}
</Template>
<EditorTemplate>
@{
var editDataItem = (Product)context;
if (!string.IsNullOrEmpty(editDataItem.ImageUrl))
{
<p>
<img src="@editDataItem.ImageUrl" alt="@editDataItem.Name" class="product-image" />
<br />
<TelerikButton ButtonType="@ButtonType.Button"
Icon="@SvgIcon.Trash"
OnClick="@( () => OnRemoveButtonClick(editDataItem) )">
Delete Current Image
</TelerikButton>
</p>
}
}
<TelerikUpload AllowedExtensions="@( new List<string> { ".jpg", ".jpeg", ".png", ".gif" } )"
Accept=".jpg, .jpeg, .png, .gif"
MaxFileSize="@( 16 * 1024 * 1024 )"
Multiple="false"
SaveUrl="@UploadSaveUrl"
RemoveUrl="@UploadRemoveUrl"
OnUpload="@( (UploadEventArgs args) => OnUploadUpload(args, editDataItem.Id) )"
OnSuccess="@( (UploadSuccessEventArgs args) => OnUploadSuccess(args, editDataItem) )" />
</EditorTemplate>
</GridColumn>
<GridColumn Field="@nameof(Product.Name)" />
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="@SvgIcon.Pencil">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="@SvgIcon.Trash">Delete</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
<style>
.product-image {
display: block;
margin: 0 auto;
max-width: 100px;
max-height: 100px;
}
td a.download-link {
display: block;
text-align: center;
text-decoration: underline;
}
</style>
@code {
private List<Product> GridData { get; set; }
private string UploadSaveUrl => ToAbsoluteUrl("api/upload/save/");
private string UploadRemoveUrl => ToAbsoluteUrl("api/upload/remove/");
private void OnUploadUpload(UploadEventArgs args, int productId)
{
// Send the product ID to the controller to take part in the saved file name.
args.RequestData.Add("productId", productId);
}
private void OnUploadSuccess(UploadSuccessEventArgs args, Product product)
{
if (args.Operation == UploadOperationType.Upload)
{
// Set the image URL in the Grid data item, based on controller response.
product.ImageUrl = args.Request.ResponseText;
}
}
private async Task OnRemoveButtonClick(Product product)
{
var successfulDelete = await DeleteFileOnServer(product.ImageUrl);
if (successfulDelete)
{
// Remove the image URL from the Grid data item.
product.ImageUrl = string.Empty;
}
}
private async Task OnGridUpdate(GridCommandEventArgs args)
{
var updatedItem = (Product)args.Item;
var originalItemIndex = GridData.FindIndex(x => x.Id == updatedItem.Id);
if (originalItemIndex >= 0)
{
GridData[originalItemIndex] = updatedItem;
}
}
private async Task OnGridCreate(GridCommandEventArgs args)
{
var createdItem = (Product)args.Item;
GridData.Insert(0, createdItem);
}
private async Task OnGridDelete(GridCommandEventArgs args)
{
var itemToDelete = (Product)args.Item;
var originalItemIndex = GridData.FindIndex(x => x.Id == itemToDelete.Id);
if (originalItemIndex >= 0)
{
await DeleteFileOnServer(itemToDelete.ImageUrl);
GridData.Remove(itemToDelete);
}
}
private async Task<bool> DeleteFileOnServer(string imageUrl)
{
// Set the file name for delection to the form key, which matches the Upload RemoveField value.
var controllerData = new Dictionary<string, string>() {
{ "files", imageUrl }
};
// UploadRemoveUrl must be an absolute URL.
var result = await HttpClient.PostAsync(UploadRemoveUrl,
new FormUrlEncodedContent(controllerData));
return result.IsSuccessStatusCode;
}
protected override void OnInitialized()
{
GridData = new List<Product>();
for (int i = 1; i <= 3; i++)
{
GridData.Add(new Product()
{
Id = i,
Name = $"Product {i}"
});
}
}
private string ToAbsoluteUrl(string url)
{
return $"{NavigationManager.BaseUri}{url}";
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
}
}
[HttpPost]
public async Task<IActionResult> Save(IFormFile files) // "files" must match Upload SaveField
{
if (files != null)
{
try
{
var productId = Request.Form["productId"];
var savedFileName = $"product-{productId}{Path.GetExtension(files.FileName)}";
// Blazor Server (wwwroot)
var saveLocation = Path.Combine(HostingEnvironment.WebRootPath, savedFileName);
// Blazor WebAssembly or Blazor Server (project root)
//var saveLocation = Path.Combine(HostingEnvironment.ContentRootPath, savedFileName);
using (var fileStream = new FileStream(saveLocation, FileMode.Create))
{
await files.CopyToAsync(fileStream);
await Response.WriteAsync(savedFileName);
}
}
catch (Exception ex)
{
Response.StatusCode = 500;
await Response.WriteAsync($"Upload failed: {ex.Message}");
}
}
return new EmptyResult();
}
[HttpPost]
public ActionResult Remove(string files) // "files" must match Upload RemoveField
{
if (files != null)
{
try
{
// Blazor Server (wwwroot)
var fileLocation = Path.Combine(HostingEnvironment.WebRootPath, files);
// Blazor WebAssembly or Blazor Server (project root)
//var fileLocation = Path.Combine(HostingEnvironment.ContentRootPath, files);
if (System.IO.File.Exists(fileLocation))
{
System.IO.File.Delete(fileLocation);
}
}
catch
{
Response.StatusCode = 500;
Response.WriteAsync("File deletion failed.");
}
}
return new EmptyResult();
}
Notes
- If necessary, adjust the application settings to allow larger file uploads.
- The Upload component itself can't delete files, which have been uploaded in previous edit sessions. Use separate UI for that inside the column
<Template>
or the<EditorTemplate>
. In both cases, call the controller method directly viaHttpClient.PostAsync()
. The UploadOnSuccess
event will not fire in this case. - It is also possible to use the Upload component in a custom edit form outside the Grid.