Search code examples
c#asp.net-corekendo-uikendo-ui-mvc

Database driven Imagebrowser for Kendo UI for Asp.Net Core


This is a question I'll answer myself. I've spent a couple of hours trying to make it work, based on the example provied here: https://github.com/telerik/ui-for-aspnet-mvc-examples/tree/master/editor/database-image-browser/DatabaseImageBrowser Since I don't maintain a blog, this is my way of documenting this, in case others face the same use case and this might save them some time

The problem is: How do I implement an Imagebrowser, that does not work with a local folder, but with a database. The sample provided by Telerik is working with virtual folders, stored in one table and images linked in a seperate one, that are linked with a Folder Id. Since I did not want to use folders, I needed to find a way to work around this. Also: The IImageBrowserController only offers a synchronous interface, which made it unsuitable for async operations:

public interface IImageBrowserController : IFileBrowserController
{
    IActionResult Thumbnail(string path);
}

public interface IFileBrowserController
{
    ActionResult Create(string path, FileBrowserEntry entry);
    ActionResult Destroy(string path, FileBrowserEntry entry);
    JsonResult Read(string path);
    ActionResult Upload(string path, IFormFile file);
}

The second problem is: How do you convert the Image read path from a virtual path .Image("~/Content/UserFiles/Images/{0}") to a mvc route

And lastly, How do you implement a custom controller or Razor page, so you don't need to use virtual folders on Asp.Net Core.


Solution

  • First of all, create an interface that is suitable for async operations:

    public interface IImageBrowserControllerAsync
    {
        Task<IActionResult> Create(string name, FileBrowserEntry entry);
        Task<IActionResult> Destroy(string name, FileBrowserEntry entry);
        Task<IActionResult> Image(string path);
        Task<JsonResult> Read(string path);
        Task<IActionResult> Thumbnail(string path);
        Task<IActionResult> Upload(string name, IFormFile file);
    }
    

    Next up, create the controller implementation. I'll omit a few of the methods, so I don't waste precious reading time. The implementation is similar to the provided methods:

    public class ImageBrowserController : ControllerBase, IImageBrowserControllerAsync
    {
        private IImageRepository _repo;
        private const int ThumbnailHeight = 80,
            ThumbnailWidth = 80;
    
        public ImageBrowserController(IImageRepository repo)
        {
            _repo = repo;
        }
    
        [Route("Image")]
        public async Task<IActionResult> Image(string path)
        {
            var image = await _repo.GetByName(path);
            if (image != null)
            {
                return File(image.Data, image.ContentType);
            }
    
            return NotFound("Errormessage");
        }
    
        //read all images, when the widget loads
        [Route("Read")]
        public async Task<JsonResult> Read(string path)
        {
            var images = await _repo.Get(); // do not return the image data. it is not 
            //needed and will clog up your resources
            var fbe = images.Select(x => new FileBrowserEntry
            {
                Name = x.Name,
                EntryType = FileBrowserEntryType.File
            });
            return new JsonResult(fbe);
        }
    
        //Create thumbnail using SixLabors.Imagesharp library
        [Route("Thumbnail")]
        public async Task<IActionResult> Thumbnail(string path)
        {
            var image = await _repo.GetByName(path);
            if (image != null)
            {
                var i = SixLabors.ImageSharp.Image
                    .Load(image.Data);
                i.Mutate(ctx => ctx.Resize(ThumbnailWidth, ThumbnailHeight));
    
                using (var ms = new MemoryStream())
                {
                    i.SaveAsJpeg(ms);
                    return File(ms.ToArray(), image.ContentType);
                }
            }
            return NotFound();
        }
    
        [Route("Upload")]
        public async Task<IActionResult> Upload(string name, IFormFile file)
        {
            if (file == null || file.Length == 0) return BadRequest();
            using (var ms = new MemoryStream())
            {
                file.CopyTo(ms);
                var img = new Entities.Image
                {
                    Name = file.FileName,
                    ContentType = file.ContentType,
                    Data = ms.ToArray()
    
                };
                await _repo.CreateImage(img);
                return Ok();
            }
        }
    }
    

    And here is the Imagebrowser / Editor config:

    @(Html.Kendo().Editor()
            .Name("editor")
    
            .HtmlAttributes(new { style = "width: 100%;height:440px
            .Tools(tools => tools
                .Clear()
                /*omitted config*/
            )
          .ImageBrowser(ib => ib
              //use actionmethod, controller, route values format
              .Image("Image", "ImageBrowser", new {path = "{0}"}) 
              .Read("Read", "ImageBrowser") // path can be null if you don't use folders
              .Destroy("Destroy", "ImageBrowser")
              .Upload("Upload", "ImageBrowser")
              .Thumbnail("Thumbnail", "ImageBrowser"))
          )
    

    Whoever reads this: I hope this example will help you save some time in implementing this on Asp.Net Core.

    Important: When reading all images on load, do not return the byte[]. Kendo only wants a FileBrowserEntry, with a name and a type property.

    I strongly advise to implement caching here. Creating thumbnails for dozens or hundreds of images on each page load, will put a huge strain on your infrastructure.