A CKEditor File Browser For ASP.NET Web Pages

CKEditor is one of the most popular rich text editors available. Out of the box, there is no file browser to help with selecting images, but you can purchase an add-on from them - CKFinder. Alternatively, you can build your own file browser component.

By default, if you click the Image button in CKEditor, you are presented with a dialogue box that requires you to enter a url for the image you want to insert. For web developers an other users with a technical bent, that shouldn't pose too many problems, but the average user won't have a clue as to what is required. What they need is a way to select an image and to have its url automatically populate the dialogue. CKEditor exposes an API that enables developers to plug in their own custom file browser to enable this.

At its simplest, a file browser can present a list of files and provide a means by which the user can indicate which one they would like to use. However, this approach would require that the user can identify which image they would like to select purely from its file name. Once any collection of image files grows, it is unlikely that the user will be able to associate images with file names unless some kind of supplementary indexing system was employed. A much more user-friendly solution would be to provide the user with a collection of thumbnails of images from which they can make their selection. The workflow is as follows:

  1. User clicks a button to browse images on the server
  2. User is presented with a list of directories that contain images from which they can select one
  3. The images in the selected directory are presented to the user in thumbnail form
  4. User can select one image by clicking it and the dialogue is populated with their selection

So you need to begin with some code to generate thumbnails of reasonable quality for display. The following code is added to a file called ThumbNails.cshtml which is placed in the App_Code folder:

@using System;
@using System.Drawing;
@using System.Drawing.Drawing2D;
@using System.IO;
@functions {
    public static byte[] GenerateTumbnail(string image, double thumbWidth) {
        try {
            using (var originalImage = Image.FromFile(image)) {
                var oWidth = originalImage.Width;
                var oHeight = originalImage.Height;
                var thumbHeight = oWidth > thumbWidth ? (thumbWidth / oWidth) * oHeight : oHeight;
                thumbWidth = oWidth > thumbWidth ? thumbWidth : oWidth;
                using (var bmp = new Bitmap((int)thumbWidth, (int)thumbHeight)) {
                    bmp.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
                    using (var graphic = Graphics.FromImage(bmp)) {
                        graphic.SmoothingMode = SmoothingMode.AntiAlias;
                        graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;

                        var rectangle = new Rectangle(0, 0, (int)thumbWidth, (int)thumbHeight);
                        graphic.DrawImage(originalImage, rectangle, 0, 0, oWidth, oHeight, GraphicsUnit.Pixel);
                        var ms = new MemoryStream();
                        bmp.Save(ms, originalImage.RawFormat);
                        return ms.GetBuffer();
        catch (Exception ex) {
            throw (ex);

This method picks up the the original image that is passed into it, and reduces it in size if it exceeds the width passed in as the second argument. It returns a byte array, so the new image is not stored on disk. Next, you need some code that passes in the image and thumbnail width and can do something meaningful with the byte array that is returned. This code forms Thumb.cshtml which resides in the root of the site:

    if (Request["image"].IsEmpty()) {
    var image = Path.Combine(Server.MapPath("~/Media/"), Server.UrlDecode(Request["image"]));
    var thumbWidth = 128D;
    var buffer = ThumbNails.GenerateTumbnail(image, thumbWidth);
    Response.ContentType = string.Format("image/{0}", Path.GetExtension(image).Trim(new[]{'.'}));
    Response.OutputStream.Write(buffer, 0, buffer.Length);

This file acts as a handler. When it is requested, it takes the image file name from the query string and uses the GenerateThumbnail function to create the thumb image and then write it to the browser. For a handler file like this to work, it needs to be set as the src of an image element. That happens in ImageViewer.cshtml:

    Layout = null;
    var directory = new DirectoryInfo(Path.Combine(Server.MapPath("~/Media"), Request["directory"]));
@foreach (var file in directory.GetFilesByExtensions(".png", ".gif", ".jpg")) {
    <div class="thumbnail">
        <img src="/Thumb/?image=@directory.Name\@file.Name" alt="thumb" title="@directory.Name/@file.Name" />

This file is responsible for locating the selected directory, represented by the value passed in the query string, and then obtaining all files that have the specified extensions and then displaying their thumbnail version. The url of the image is set as the value of the title attribute. This is visible to the user as a tooltip when they hover over an image, and is used later to populate the CKEditor image dialogue. GetFilesByExtensions is a custom extension method. It lives in a class file called FileExtensions.cs which is placed in the App_Code folder:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class FileExtensions
    public static IEnumerable<FileInfo> GetFilesByExtensions(this DirectoryInfo directoryInfo, params string[] extensions) {
        var allowedExtensions = new HashSet<string>(extensions, StringComparer.OrdinalIgnoreCase);
        return directoryInfo.EnumerateFiles().Where(f => allowedExtensions.Contains(f.Extension));

The main file is called FileBrowser.cshtml. It is the file that gets called when the user clicks the insert image icon in CKEditor.

    Layout = null;
    var media = new DirectoryInfo(Server.MapPath("~/Media"));
        <title>Media Browser</title>
        <link href="~/Content/StyleSheet.css" type="text/css" rel="stylesheet" />
        <script src="/Scripts/jquery.js" type="text/javascript"></script>
        <script src="/Scripts/ckeditor/ckeditor.js" type="text/javascript"></script>
        <script type="text/javascript">
            var funcNum = @(Request["CKEditorFuncNum"] + ";")
            $(function() {
                $('li').click(function() {
                    $('#fileExplorer').load('/ImageViewer?directory=' + encodeURIComponent($(this).text()));
                }).hover(function() {
                    $(this).css('cursor', 'pointer');
                $('#fileExplorer').on('click', 'img', function () {
                    var fileUrl = '/Media/' + $(this).attr('title');
                    window.opener.CKEDITOR.tools.callFunction(funcNum, fileUrl);
                }).hover(function() {
                    $(this).css('cursor', 'pointer');
        <div id="folderExplorer">
            @foreach (var dir in media.EnumerateDirectories().Where(d => d.GetFilesByExtensions(".png", ".gif", ".jpg").Any())) {
       <div id="fileExplorer"></div>

The main images folder is called Media, and images are stored in subfolders. These are displayed in a list. Some jQuery is used to set the cursor to a pointer (hand) when you hover over the list items, indicating that they are clickable. When they are clicked, the empty fileExplorer div is loaded with the result of a request to ImageViewer.cshtml - which is the file that calls Thumb.cshtml to display thumbnail versions of the contents of the selected directory.

The next block of jQuery applies a click event handler to any image that appears in the fileExplorer div. It extracts the url of the image being clicked from its title attribute. It passes that value along with another originally sent to the FileBrowser file by CKEditor back to the editor so that it knows you are selecting a file. The url of the file is applied to the appropriate dialogue text box and then the filebrowser window is closed.

All you need to do now is tell CKEditor that you have a file browser, and where it is. You can do that in the file that features the editor. Here's a minimal file that only displays the editor and configures the file browser:


<!DOCTYPE html>

<html lang="en">
        <meta charset="utf-8" />
        <title>CKEditor File Browser FOR ASP.NET Web Pages</title>
        <link href="~/Content/StyleSheet.css" type="text/css" rel="stylesheet" />
        <script type="text/javascript" src="~/Scripts/jquery.js"></script>
        <script type="text/javascript" src="~/Scripts/ckeditor/ckeditor.js"></script>
            $(function () {
                CKEDITOR.replace('Content', {
                    filebrowserBrowseUrl: '/FileBrowser',
                    filebrowserWindowWidth: 800,
                    filebrowserWindowHeight: 600
        <div id="form">
            <form method="post">
                    <input type="submit" />

The file browser configuration requires a url to be passed to the filebrowserBrowseUrl parameter, and then optionally a height and width for the window that will open to display the resource at the specified url. If you do not specify a height or width, the default values of 80% of the available screen width, and 70% of the available height will be used.

This example, which is available at GitHub assumes that the images are in sub folders of the Media folder. The download also includes a version that works with just one images folder. You can access that version by downloading and running the site and requesting Default2.cshtml.


Date Posted:
Last Updated:
Posted by:
Total Views to date: 24693


- Andrew Davis

Hi Mike,
you can replace the 'GenerateTumbnail' method with new 'WebImage' class found in 'System.Web.Helpers'. This will also enable you to do all the resizing and cropping in a fluent manner.

- Mike


I decided against using the WebImage helper as the quality of the resulting image is not very good.

- Handy


Is there an example to use this on asp.net web forms ?

- Mesut

I used your tutorial in my site, then I saw that you used TinyMCE in your CMS. Could you please prepare a tutorial for TinyMCE?

- Mike


I use CKEditor in the CMS. I've never really looked at TinyMCE.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and I end up deleting quite a lot. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Requests to fix your code (post a question to forums.asp.net instead, please)
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

kaleem 4/16/2015 9:14 AM
In response to ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS
hi this is very helpful ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS if to...

cranston mason 4/16/2015 8:49 AM
In response to Managing Checkboxes And Radios In ASP.NET Razor Web Pages
How do you include a checkbox at the View module and send its values to controller to be processed?...

vamsi 4/15/2015 5:40 AM
In response to Sessions in ASP.NET 5
good article...

James Chaney 4/14/2015 8:44 PM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Question on #5 - I don't see where this gets you out of a NullReferenceException if returns null is...

Grey 4/14/2015 6:09 PM
In response to Getting the identity of the most recently added record
Nice work....

Anh Huynh 4/14/2015 3:36 PM
In response to Create PDFs in ASP.NET - getting started with iTextSharp
I was successfully create and display PDF file from my localhost but when I move the aspx to the I...

Hisham Abdullah Bin Ateya 4/14/2015 7:00 AM
In response to Sessions in ASP.NET 5
Thanks Mike for sharing this article. George it will be nice if we let the web developers implement...

CTR 4/14/2015 6:39 AM
In response to Integrating Web API with ASP.NET Razor Web Pages
Thanks Mike, new to web pages. learning a lot from your posts, Making Ajax calls to Web API extend...

Bayu Angkasa 4/13/2015 11:27 AM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Four years after you wrote it, I still find out how lucky I am to read your posts ... Thank you Mike...

abedon 4/10/2015 9:17 PM
In response to ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?
I feel the title of this article is not that felicitous. ASP.NET 5 is built of top of the concept...