The Story of My Life

Diary of a noob programmer

Telerik UI for MVC Core – Image Browser keep on showing folders with name of “undefined”

Two days ago i was working on Telerik editor to see how i can get it work, in case if the company want to use it or not… So from it’s demo, i took the DLL i though i may need. add them to the project, and start the project… with editor alone everything was fine, … cool.

Next, I needed to check if the file uploader can be useful, so i add the code for it… and the issue came up… on creating folder or file, i got the right file, but as soon as I refreshed, or reopen it, I kept on getting undefined within the image browser.

Here’s the code I used in my razor page (*.cshtml)

@* Make sure tag helpers are not available for the Editor content *@
@removeTagHelper "*, Microsoft.AspNet.Mvc.Razor"
@removeTagHelper "*, Microsoft.AspNetCore.Mvc.Razor"
@using TestWysiwygEditor.Controllers
@model TestWysiwygEditor.Controllers.Model

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

@using (Html.BeginForm("Create", "Kendo", null, FormMethod.Post, true, new { @role = "form" }))
{
    @(Html.Kendo()
        .Editor()//.EditorFor(m => m.Content)
        .Name("Content")//.Name("editor")
        .Encoded(false)
        .HtmlAttributes(new { style = "width:100%;height:440px" })
        .Tools(tools => tools
            .Clear()
            .Bold().Italic().Underline().Strikethrough()
            .JustifyLeft().JustifyCenter().JustifyRight().JustifyFull()
            .InsertOrderedList().InsertUnorderedList()
            .Outdent().Indent()
            .CreateLink().Unlink()
            .InsertImage()
            .InsertFile()
            .SubScript()
            .SuperScript()
            .TableEditing()
            .ViewHtml()
            .Formatting()
            .CleanFormatting()
            .FormatPainter()
            .FontName()
            .FontSize()
            .ForeColor().BackColor()
            .Print()
        )
        .ImageBrowser(imageBrowser => imageBrowser
            .Image("~/shared/UserFiles/Images/{0}")
            .Read("Read", "ImageBrowser")
            .Create("Create", "ImageBrowser")
            .Destroy("Destroy", "ImageBrowser")
            .Upload("Upload", "ImageBrowser")
        )
        .FileBrowser(fileBrowser => fileBrowser
            .File("~/shared/UserFiles/Images/{0}")
            .Read("Read", "FileBrowser")
            .Create("Create", "FileBrowser")
            .Destroy("Destroy", "FileBrowser")
            .Upload("Upload", "FileBrowser")
        )
        .Value(@"<span style=""color:#999999"">Change this content in your desired way</span>")
    )

    <button type="submit">Save</button>
}
@Html.ActionLink("Kendo Home", "Index", "Kendo", new { @class = "btn btn-link" })

@section Styles
{
    @*CDN: https://kendo.cdn.telerik.com/2020.1.219/..*@
    <link href="/lib/kendo/css/web/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="/lib/kendo/css/web/kendo.default-v2.min.css" rel="stylesheet" type="text/css" />
    @*<link href="/lib/kendo/css/web/kendo.bootstrap-v4.min.css" rel="stylesheet" type="text/css" />*@
    <link href="/lib/kendo/css/web/kendo.rtl.min.css" rel="stylesheet" type="text/css" />
}

@section HeadScripts
{
    <script src="/lib/kendo/js/kendo.all.min.js"></script>
    <script src="/lib/kendo/js/kendo.aspnetmvc.min.js"></script>
    <script>
        window.kendoTheme = "default-v2";
        window.kendoCommonFile = "common-empty";
    </script>
}

Note i moved jquery on top of _layout page and introduce some new sections:

_layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - TestWysiwygEditor</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
    @RenderSection("Styles", required: false)

    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("HeadScripts", required: false)
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">TestWysiwygEditor</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Kendo" asp-action="Index">Kendo</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - TestWysiwygEditor - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
@RenderSection("Scripts", required: false)
</body>
</html>

And for image upload management, i just took a copy of telerik demo controller:

using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using Kendo.Mvc.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace TestWysiwygEditor.Controllers
{
    public class ImageBrowserController : EditorImageBrowserController
    {
        private const string contentFolderRoot = "shared/";
        private const string folderName = "Images/";
        private static readonly string[] foldersToCopy = Array.Empty<string>(); //{"shared/images/employees" };
        
        public ImageBrowserController(IHostingEnvironment hostingEnvironment)
            : base(hostingEnvironment)
        {
        }

        /// <summary>
        /// Gets the base paths from which content will be served.
        /// </summary>
        public override string ContentPath
        {
            get
            {
                return CreateUserFolder();
            }
        }

        

        private string CreateUserFolder()
        {
            var virtualPath = Path.Combine(contentFolderRoot, "UserFiles", folderName);
            var path = HostingEnvironment.WebRootFileProvider.GetFileInfo(virtualPath).PhysicalPath;

            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
                foreach (var sourceFolder in foldersToCopy)
                {
                    CopyFolder(HostingEnvironment.WebRootFileProvider.GetFileInfo(sourceFolder).PhysicalPath, path);
                }
            }
            return virtualPath;
        }

        private void CopyFolder(string source, string destination)
        {
            if (!Directory.Exists(destination))
            {
                Directory.CreateDirectory(destination);
            }

            foreach (var file in Directory.EnumerateFiles(source))
            {
                var dest = Path.Combine(destination, Path.GetFileName(file));
                System.IO.File.Copy(file, dest);
            }

            foreach (var folder in Directory.EnumerateDirectories(source))
            {
                var dest = Path.Combine(destination, Path.GetFileName(folder));
                CopyFolder(folder, dest);
            }
        }
    }
}

After searching for a long time, after trying many things, after reviewing the kendo demo for many times, i come across this line, which I saw before, but I ignored it every time:

// Add MVC services to the services container.
services
    .AddMvc(options => options.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
    .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

And I thought to myself what if… What if telerik doesn’t serialize the content just as regular MVC configuration used to serialize?
So I opened up browser console, and watched the response from “Read” action very carefully, and i noticed that those data are subtly different.

This is data coming from Telerik UI demo “Read” action:

[{"Name":"1.png","Size":5998,"EntryType":0},{"Name":"2.png","Size":5405,"EntryType":0},{"Name":"3.png","Size":7428,"EntryType":0},{"Name":"4.png","Size":6280,"EntryType":0},{"Name":"5.png","Size":6878,"EntryType":0},{"Name":"6.png","Size":6176,"EntryType":0},{"Name":"7.png","Size":6375,"EntryType":0},{"Name":"8.png","Size":6967,"EntryType":0},{"Name":"9.png","Size":7075,"EntryType":0}]

And this is data coming from My Project “Read” action:

[{"name":"Capture.PNG","size":36285,"entryType":0},{"name":"New folder","size":0,"entryType":1},{"name":"New folder (2)","size":0,"entryType":1}]

Ignoring the file differences, do you see the structure differences?

Properties of demo, are all Pascal-Case, starting with upper-case letter, but the project I just start out, returns data in JavaScript standard, starting with lower-case letter.

So there was two way fixing it, one going to startup and change the JSON serializer with whatever you like, which I ignored, because I think then what happen if you install another plugin, and the other one says you have to do something else? or you have codes written before? Then you have to change them all? So I moved to second way.

The second way for me was to look for changing serialization method on a single controller or an action… I found some, using action attribute for controller and using Json() method for actions. I went with the second, and through the way I found that, this can’t be done simply just as the old good MVC.

The JsonSerializerSettings class changed to JsonSerializerOptions and although you can pass a serializerSetting, at runtime it will generate errors.

So I override the “Read” action,  placed the following code there:

public override JsonResult Read(string path)
{
    var data = base.Read(path);

    var options = new JsonSerializerOptions
    {
        PropertyNamingPolicy = null
    };

    var list = ((FileBrowserEntry[]) data.Value).Select(s => new
    {
        s.Name,
        s.Size,
        s.EntryType
    }).ToList();
    return Json(list, options);
}

As you can see I assinged null to the PropertyNamingPolicy and that was due to limitation of values exists to be assigned to that property.

The only option available was: JsonNamingPolicy.CamelCase, but hopefully, null did the trick.

Now we can have the working value:

[{"Name":"Capture.PNG","Size":36285,"EntryType":0},{"Name":"New folder","Size":0,"EntryType":1},{"Name":"New folder (2)","Size":0,"EntryType":1}]

And our working Image Browser:


Thank you for your time,
Hassan Faghihi.

Leave a Reply

Your email address will not be published. Required fields are marked *.

*
*
You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>