DEV Community

Cover image for Effortlessly Manage Large File Uploads with Blazor File Manager
Zahra Sandra Nasaka for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Effortlessly Manage Large File Uploads with Blazor File Manager

TL;DR: Explore the Blazor File Manager’s chunk upload feature. Break large files into manageable pieces to improve upload efficiency and reliability. Key steps include installing dependencies, registering services, and configuring server-side accommodations.

The Syncfusion® Blazor File Manager is a graphical user interface component for managing the file system. It allows users to perform the most common file operations like accessing, editing, and sorting files or folders. This component provides easy navigation to select a file or folder from the file system. It effectively supports both file and folder uploads. However, managing large file uploads efficiently can be quite challenging.

To overcome this issue, we’ve introduced the chunk upload feature in the Blazor File Manager in the 2024 Volume 4 release.

Let’s examine how this feature helps handle large files, with an example to get you started.

Highlights of chunk upload

Efficient handling of large files

Chunk upload is designed to handle large files or folders by breaking them into smaller manageable chunks. This reduces the risk of upload failures due to network interruptions or server timeouts during uploads.

Fault tolerance

If a network error occurs during upload, only a specific chunk needs to be re-uploaded rather than the entire file, improving reliability and reducing potential upload times.

Parallel uploads

Multiple chunks can be uploaded simultaneously, making the upload process faster by better using the available bandwidth.

Pause and resume upload

The feature supports resuming uploads with pause and resume options in the upload dialog, so if an upload session is interrupted, it can continue from where it left off rather than starting from scratch.

Progress tracking

This feature provides detailed information on the upload progress so that users can see how much of the file has been successfully uploaded and how much is left.

How does chunk upload help with large file and folder uploads?

Dividing files into smaller chunks makes the upload process more manageable and efficient. Users benefit from a smoother experience without significant delays, even when uploading large or multiple files in a folder.

Render Blazor File Manager with chunk upload

Step 1: Installing dependencies

First, we need to install the following NuGetdependencies to build the File Manager efficiently.

Syncfusion.Blazor.FileManager

Syncfusion.Blazor.Themes
Enter fullscreen mode Exit fullscreen mode

After installing these dependencies, configure them in your app.

Step 2: Register the Blazor service

Then, open the ~/_Imports.razor file and import the necessary Syncfusion.Blazor namespace for the required components.

@using Syncfusion.Blazor

@using Syncfusion.Blazor.FileManager
Enter fullscreen mode Exit fullscreen mode

Then, register the Syncfusion Blazor Service in the ~/Program.cs file of your Blazor Web app. For an app with WebAssembly or Auto (Server and WebAssembly) interactive render mode, register the Syncfusion Blazor service in both ~/Program.cs files of your web app.

....
using Syncfusion.Blazor;
....
builder.Services.AddSyncfusionBlazor();
....
Enter fullscreen mode Exit fullscreen mode

Step 3: Add stylesheet and script resources

The theme stylesheet and script can be accessed from NuGet through Static Web Assets. Include the stylesheet reference in the section and the script reference at the end of the in the ~/Components/App.razor file.

Refer to the following code example.

<head>
    ....
    <link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
</head>
....
<body>
    ....
    <script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

Step 4: Implementing chuck upload in Blazor File Manager

Refer to the following code example to implement the chunk upload feature in the Blazor File Manager component.

@using Syncfusion.Blazor.FileManager

<SfFileManager TValue="FileManagerDirectoryContent">
    <FileManagerUploadSettings ChunkSize="5242880" MaxFileSize="73728000">
    </FileManagerUploadSettings>
    <FileManagerAjaxSettings 
        Url="https://ej2-aspcore-service.azurewebsites.net/api/FileManager/FileOperations"
        UploadUrl="https://ej2-aspcore-service.azurewebsites.net/api/FileManager/Upload"
        DownloadUrl="https://ej2-aspcore-service.azurewebsites.net/api/FileManager/Download"
        GetImageUrl="https://ej2-aspcore-service.azurewebsites.net/api/test/FileManager/GetImage">
    </FileManagerAjaxSettings>
</SfFileManager>
Enter fullscreen mode Exit fullscreen mode

In the above code example, the ChunkSize is set to 5 MB (5,242,880 bytes), and the MaxFileSize is set to 70 MB (73,728,000 bytes) in the FileManagerUploadSettings. This means files up to 70 MB will be uploaded in 5 MB chunks.

Step 5: Server-side configuration

Next, we should manage the upload size within the server-side controller and model files. Refer to the following code example.

Controller.cs

[Route("Upload")]
[DisableRequestSizeLimit]
public IActionResult Upload(string path, long size, IList<IFormFile> uploadFiles, string action)
{
    try
    {
        FileManagerResponse uploadResponse;

        foreach (var file in uploadFiles)
        {
            var folders = (file.FileName).Split('/');

            // checking the folder upload
            if (folders.Length > 1)
            {
                for (var i = 0; i < folders.Length - 1; i++)
                {
                    string newDirectoryPath = Path.Combine(this.basePath + path, folders[i]);

                    if (Path.GetFullPath(newDirectoryPath) != (Path.GetDirectoryName(newDirectoryPath) + Path.DirectorySeparatorChar + folders[i]))
                    {
                        throw new UnauthorizedAccessException("Access denied for Directory-traversal");
                    }

                    if (!Directory.Exists(newDirectoryPath))
                    {
                        this.operation.ToCamelCase(this.operation.Create(path, folders[i]));
                    }

                    path += folders[i] + "/";
                }
            }
        }

        uploadResponse = operation.Upload(path, uploadFiles, action, size, null);

        if (uploadResponse.Error != null)
        {
            Response.Clear();
            Response.ContentType = "application/json; charset=utf-8";
            Response.StatusCode = Convert.ToInt32(uploadResponse.Error.Code);
            Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = uploadResponse.Error.Message;
        }
    }
    catch (Exception e)
    {
        ErrorDetails er = new ErrorDetails();
        er.Message = e.Message.ToString();
        er.Code = "417";
        er.Message = "Access denied for Directory-traversal";

        Response.Clear();
        Response.ContentType = "application/json; charset=utf-8";
        Response.StatusCode = Convert.ToInt32(er.Code);
        Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = er.Message;

        return Content("");
    }

    return Content("");
}
Enter fullscreen mode Exit fullscreen mode

Models/PhysicalFileProvider.cs

public virtual FileManagerResponse Upload(string path, IList<IFormFile> uploadFiles, string action, long size = 0, params FileManagerDirectoryContent[] data)
{
    FileManagerResponse uploadResponse = new FileManagerResponse();
    try
    {
        string validatePath;
        validatePath = Path.Combine(contentRootPath + path);

        if (Path.GetFullPath(validatePath) != GetFilePath(validatePath))
        {
            throw new UnauthorizedAccessException("Access denied for Directory-traversal");
        }

        AccessPermission PathPermission = GetPathPermission(path);

        if (PathPermission != null && (!PathPermission.Read || !PathPermission.Upload))
        {
            accessMessage = PathPermission.Message;
            throw new UnauthorizedAccessException("'" + this.getFileNameFromPath(this.rootName + path) + "' is not accessible. You need permission to perform the upload action.");
        }

        List<string> existFiles = new List<string>();

        foreach (IFormFile file in uploadFiles)
        {
            if (uploadFiles != null)
            {
                var name = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim().ToString();
                string[] folders = name.Split('/');
                string fileName = folders[folders.Length - 1];
                var fullName = Path.Combine((this.contentRootPath + path), fileName);

                if (Path.GetFullPath(fullName) != GetFilePath(fullName) + Path.GetFileName(fullName))
                {
                    throw new UnauthorizedAccessException("Access denied for Directory-traversal");
                }

                long fileLength = File.Exists(fullName) ? new FileInfo(fullName).Length : 0;

                if (action == "save")
                {
                    bool isValidChunkUpload = file.ContentType == "application/octet-stream" && (fileLength != size);

                    if (!System.IO.File.Exists(fullName) || isValidChunkUpload)
                    {
                        PerformUpload(file, fileLength, size, fullName);
                    }
                    else
                    {
                        existFiles.Add(fullName);
                    }
                }
                else if (action == "remove")
                {
                    if (System.IO.File.Exists(fullName))
                    {
                        System.IO.File.Delete(fullName);
                    }
                    else
                    {
                        ErrorDetails er = new ErrorDetails();
                        er.Code = "404";
                        er.Message = "File not found.";
                        uploadResponse.Error = er;
                    }
                }
                else if (action == "replace")
                {
                    long duplicateFileSize = new FileInfo(fullName).Length;

                    if (System.IO.File.Exists(fullName) && (duplicateFileSize == size || file.ContentType != "application/octet-stream"))
                    {
                        System.IO.File.Delete(fullName);
                    }
                    PerformUpload(file, fileLength, size, fullName);
                }
                else if (action == "keepboth")
                {
                    string newName = fullName;
                    int index = newName.LastIndexOf(".");

                    if (index >= 0)
                        newName = newName.Substring(0, index);

                    int fileCount = 0;

                    while (System.IO.File.Exists(newName + (fileCount > 0 ? "(" + fileCount.ToString() + ")" + Path.GetExtension(name) : Path.GetExtension(name))))
                    {
                        long duplicateSize = new FileInfo(newName + (fileCount > 0 ? "(" + fileCount.ToString() + ")" + Path.GetExtension(name) : Path.GetExtension(name))).Length;

                        if (duplicateSize == size || file.ContentType != "application/octet-stream")
                        {
                            fileCount++;
                        }
                        else
                        {
                            break;
                        }
                    }
                    newName = newName + (fileCount > 0 ? "(" + fileCount.ToString() + ")" : "") + Path.GetExtension(name);
                    long newFileLength = File.Exists(newName) ? new FileInfo(newName).Length : 0;
                    PerformUpload(file, newFileLength, size, newName);
                }
            }
        }

        if (existFiles.Count != 0)
        {
            ErrorDetails er = new ErrorDetails();
            er.Code = "400";
            er.Message = "File already exists.";
            er.FileExists = existFiles;
            uploadResponse.Error = er;
        }

        return uploadResponse;
    }
    catch (Exception e)
    {
        ErrorDetails er = new ErrorDetails();
        er.Message = e.Message.ToString();
        er.Code = er.Message.Contains("is not accessible. You need permission") ? "401" : "417";

        if ((er.Code == "401") && !string.IsNullOrEmpty(accessMessage)) { er.Message = accessMessage; }

        uploadResponse.Error = er;
        return uploadResponse;
    }
}

private void PerformUpload(IFormFile file, long fileLength, long size, string name)
{
    bool isValidChunkUpload = file.ContentType == "application/octet-stream" && (fileLength != size);

    if (file.ContentType == "application/octet-stream")
    {
        using (var fileStream = new FileStream(name, FileMode.Append))
        {
            file.CopyTo(fileStream);
        }
    }
    else
    {
        using (FileStream fs = System.IO.File.Create(name))
        {
            file.CopyTo(fs);
            fs.Flush();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Refer to the following image.

Chunk upload feature in Blazor File Manager
Chunk upload feature in Blazor File Manager

References

For more details, refer to the chunk upload in the Blazor File Manager component documentation.

Conclusion

Thanks for reading! We hope you enjoyed this quick guide to utilizing the chunk upload feature of Syncfusion® Blazor File Manager. This feature helps us greatly enhance the performance and reliability of file uploads in our web app. Breaking down large files into smaller, manageable pieces as chunks ensures that your apps remain responsive and efficient even under demanding load conditions.

It was rolled out in the Essential Studio® 2024 Volume 4 release. Try out this great feature and provide valuable feedback in the comments section. Check out our Release Notes and What’s New pages to see all the new updates in this release.

Our existing customers can download the latest Essential Studio® version from the License and Downloads page. If you are not a Syncfusion® customer, try our 30-day free trial to check out our newest features.

You can also contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!

Related Blogs

Top comments (0)