File upload control in forms (Kentico 12 MVC)


With earlier versions of Kentico 12 MVC we got a case where we needed to add file upload capabilities to a form, but there was not out of the box feature to implement it, so we came out with our custom file upload control and we thought our experience might be useful to someone else, so we want to share it!


With earlier versions of Kentico 12 MVC we got a case where we needed to add file upload capabilities to a form, but there was not out of the box feature to implement it, so we came out with our custom file upload control and we thought our experience might be useful to someone else, so we want to share it!

INTRODUCTION

This step-by-step guide shows how to implement File upload form control Kentico MVC.

In our case, we needed to upload only PDF and MS Word documents, but I’m sure it is quite easy to extend it and allow other document formats.

 

Before diving into technical details, I’d like to provide you with high level view of this process:

  • A user chooses files to be uploaded;

  • Before actual uploading of selected file we will verify file extension and file size with JS;

  • If everything looks good JS converts file to base64 string, as this is the format we use to store file into the database;

  • JS stores base64 string into a hidden field on the form;

  • Send form data to the server upon form submission and store it into the database;

 



 

Step 1

Let’s create UploadFileComponent class in Models folder - we will need to register it in FormBuilder properties in which we will upload file.


using CMS.SiteProvider;
using Kentico.Forms.Web.Mvc;

[assembly: RegisterFormComponent(
    "UploadFile",
    typeof(UploadFileComponent),
    "UploadFile name",
    Description = "UploadFile description",
    IconClass = "icon-picture")]

    public class UploadFileComponent : FormComponent
    {
        [BindableProperty]
        public string FileBinary { get; set; } = string.Empty;//the value in which we will upload the //file

        public string SiteName => SiteContext.CurrentSiteName;

        public override string GetValue() => FileBinary;

        public override void SetValue(string value)
        {
            FileBinary = value;
        }
    }



Step 2

On this step let’s create UploadFileProperties class - it defines properties in database which will store file to.


using CMS.DataEngine;
using Kentico.Forms.Web.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;



    public class UploadFileProperties : FormComponentProperties//this class create in db property (where you can save file)
    {

        public UploadFileProperties() : base(FieldDataType.LongText)
        {
        }

        [DefaultValueEditingComponent(TextInputComponent.IDENTIFIER)]
        public override string DefaultValue { get; set; }

        [Required]
        public string File { get; set; }

    }

Step 3

Now let’s add FormComponent folder underneath Content folder and create UploadFile folder and create uploader-component.js file within the last one. It is JS code that is responsible for capturing a file on a front end and sending it to a server.


window.your_namespace = window.your_namespace || {};

(function (UploadFileComponent) {

    UploadFileComponent.renderFileDetails = function (target) {

        var mbSize = 5200000;
        var file = target.files[0];

        if (file) {
            var detailsElement =
                target.parentElement.parentElement.querySelector(".kn-upload-file-details");

            var checkFileType = file.name.split('.').pop();

            if (checkFileType === "docx" || checkFileType === "pdf") {
                var fileSize = 0;

                if (file.size > mbSize) {
                    detailsElement.querySelector(".kn-file-size").style.color = "red";
                    detailsElement.querySelector(".kn-file-size").innerHTML =
                        "File size should be less than " + "5" + "MB Your size : " + Math.round(file.size / (1024 * 1024)) + 'MB';      
                    document.querySelector(".form-control.file-upload").value = "";
                } else {

                    fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'kB';

                    detailsElement.querySelector(".kn-file-size").innerHTML =
                        "Size: "
                        + fileSize;

                    if (checkFileType === "docx") {
                        detailsElement.querySelector(".kn-file-type").innerHTML =
                            "Type: application/"
                            + checkFileType;

                    }
                    else {
                        detailsElement.querySelector(".kn-file-type").innerHTML =
                            "Type: "
                            + file.type;
                    }

                    document.querySelector(".form-control.myclass").value = file.name;

                    var pathArray = window.location.pathname.split('/');
                    var lastSegment = pathArray.pop() || pathArray.pop();
                    document.querySelector(".form-control.vacancy-name").value = lastSegment;

                    var reader = new FileReader();
                    reader.onload = (function (theFile) {
                        return function (e) {
                            var binaryData = e.target.result;
                            var base64String = window.btoa(binaryData);
                            document.querySelector(".form-control.file-upload").value = base64String;//  base64 file save into input value 
                        };
                    })(file);
                    reader.readAsBinaryString(file);
                }

            } else {
                detailsElement.querySelector(".kn-file-type").style.color = "red";
                detailsElement.querySelector(".kn-file-type").innerHTML =
                    "Invalid file type. Please upload a .docx or .pdf file.";
            }
            
        }
    };

}(window.your_namespace.UploadFileComponent = window.your_namespace.UploadFileComponent || {}));

Step 4

We need to create FormExtensions folder and create FormExtensions class where we create custom input.


using System.Collections.Generic;
using System.Web.Mvc;



    public static class FormExtensions
    {
       
        public static MvcHtmlString CustomInput(this HtmlHelper helper, string inputType, string name, object value, IDictionary htmlAttributes)
        {
            TagBuilder tagBuilder = new TagBuilder("input");
            tagBuilder.MergeAttribute("type", inputType);
            tagBuilder.MergeAttribute("name", name);
            tagBuilder.MergeAttribute("value", value.ToString());
            tagBuilder.MergeAttributes(htmlAttributes);

            return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.StartTag));
        }

        public static MvcHtmlString CustomInput(this HtmlHelper helper, string inputType, string name, object value, object htmlAttributes) =>
            CustomInput(helper, inputType, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));

   
    }

Step 5

We need to create FormComponent folder in Views/Shared folder, then create _UploadFile.cshtml view. In this view we access the javascript functions that uploads a file.


@using CMS.Helpers
@using CMS.MediaLibrary
@using Kentico.Forms.Web.Mvc
@using Models.FormComponent.FileUploader
@using your_namespace.Extension

@model UploadFileComponent
@{

    int GetCurrentPageId()
    {
        var pageBuilder = Context.Kentico().PageBuilder();
        int pageId = -1;

       pageId = feature.PageIdentifier;
    
        return pageId;
    }

    int currentPageId = GetCurrentPageId();

    if (currentPageId == 0)
    {
        Guid guid;
        guid = Guid.TryParse(Model.FileBinary.ToString(), out guid) ? guid : Guid.Empty;
        var mediaFileInfo = MediaFileInfoProvider.GetMediaFileInfo(guid, Model.SiteName);

    }
    else
    {

        string currentFilePathId = Html.IdFor(model => model.FileBinary).ToString();
        IDictionary fileInputAttributes = ViewData.GetEditorHtmlAttributes();
        fileInputAttributes["onchange"] = $"window.your_namespace.UploadFileComponent.renderFileDetails(this);";

        IDictionary hiddenInputAttributes = ViewData.GetEditorHtmlAttributes();
        IDictionary hiddenInputAttributesVacancyName = ViewData.GetEditorHtmlAttributes();

        fileInputAttributes["type"] = "button";

        if (hiddenInputAttributes.ContainsKey("class"))//add to hidden input class
        {
            hiddenInputAttributes["class"] += " file-upload";
          
        }
@Html.CustomInput("file", "UploadFile", string.Empty, fileInputAttributes) @Html.HiddenFor(model => model.FileBinary, hiddenInputAttributes)
 
 
} }

Summary

This brief article shows how to create File upload control in Kentico MVC forms, which saves particular file types with a minimum number of server requests. It does not cover all possible scenarios, but might be a good starting point for anyone that needs similar functionality.