Click here to Skip to main content
15,867,835 members
Articles / Web Development / HTML

TinyMCE image insert plugin with ASP.NET WebApi2

Rate me:
Please Sign up or sign in to vote.
4.90/5 (7 votes)
24 Mar 2015CPOL1 min read 32.3K   1.1K   10   11
How to add image on TinyMCE with ASP.NET? This article shows plugin communicating with .NET WebApi2 to insert images.

Introduction

When you are using TinyMCE WYSYWIG editor, you want to add images in the editor. But to achieve this purpose, you have to buy file management module, or develop plugin by yourself.

There're two ways to make it work. First method is to upload a image in server in specific directory, and create a link for this in the editor. Another method is to convert image file to Base64 encoded string. Most of modern browsers support the second way, so I will show you how to convert image file to base64 encoded string with .NET WebApi.

Supported Browsers

  • IE 9 or higher version
  • Almost all Chrome version
  • Almost all Firefox version
  • Opera : Not tested.

Using the code

To convert image binary to base64 encoded string, I will user JQuery AJAX and .NET WebApi2.

So, MVC5 is requried for this project.

Let's start with WebApi Controller. There is a method called ConvertImageToBase64. This method convert the image binary to base64 encoded string requested by JQuery AJAX. Once the converting is finished, the string will be returned to the browser.

//
// EditController.cs in Project/Controllers
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.Web.Http;

namespace TinyMCE.Controllers
{
    public class EditorController : ApiController
    {
        [Route("api/image/convert-base64")]
        [HttpPost]
        public HttpResponseMessage ConvertImageToBase64()
        {
            string base64String = "";
            HttpResponseMessage response = new HttpResponseMessage();

            if (HttpContext.Current.Request.Files.AllKeys.Any())
            {
                // Get the uploaded image from the Files collection
                var httpPostedFile = HttpContext.Current.Request.Files["file"];

                if (httpPostedFile != null)
                {
                    // Validate the uploaded image(optional)

                    byte[] fileData = null;
                    using (var binaryReader = new BinaryReader(httpPostedFile.InputStream))
                    {
                        fileData = binaryReader.ReadBytes(httpPostedFile.ContentLength);
                        base64String = System.Convert.ToBase64String(fileData, 0, fileData.Length);
                    }
                }
            }

            response.Content = new StringContent(base64String);
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");

            return response;
        }
    }
}

 

This is TinyMCE popup window defintion file. To support same Look and Feel of current TinyMCE version, I added some style sheet in Contents/Site.css & used Bootstrap.

//
// dialog.html in Project/Scripts/tinymce
//

<!DOCTYPE html>
<html>
<head>
    <title>Image insert</title>

    <link href="/Content/bootstrap.css" rel="stylesheet" />
    <link href="/Content/Site.css" rel="stylesheet" />

    <script src="/Scripts/modernizr-2.6.2.js"></script>
    <script src="/Scripts/jquery-1.10.2.js"></script>

    <script src="/Scripts/bootstrap.js"></script>

    <script>
        $(document).on('change', '.btn-file :file', function () {
            var input = $(this),
            numFiles = input.get(0).files ? input.get(0).files.length : 1,
            label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
            input.trigger('fileselect', [numFiles, label]);
        });

        $(document).ready(function () {
            $('.btn-file :file').on('fileselect', function (event, numFiles, label) {

                var input = $(this).parents('.input-group').find(':text'),
                    log = numFiles > 1 ? numFiles + ' files selected' : label;

                if (input.length) {
                    input.val(log);
                } else {
                    if (log) alert(log);
                }

            });
        });
    </script>
</head>
<body>
    <div style="padding:20px;">
        <table style="width: 100%;">
            <tr>
                <td style="width: 141px; vertical-align: middle;"><label style="font-weight: normal;">Source</label></td>
                <td style="vertical-align: middle;">
                    <div class="input-group">
                        <span class="input-group-btn">
                            <span class="btn btn-default btn-file">
                                Browse&hellip; <input type="file" id="content" name="content">
                            </span>
                        </span>
                        <input type="text" class="form-control" readonly>
                    </div>
                </td>
            </tr>
            <tr>
                <td style="width: 141px; vertical-align: middle; padding-top: 20px;"><label style="font-weight: normal;">Image Description</label></td>
                <td style ="vertical-align: middle; padding-top: 20px;">
                    <input id="desc" name="desc" type="text" class="form-control">
                </td>
            </tr>
        </table>
    </div>
</body>
</html>

 

This is a java script plugin to send image binary to send a server.

Quote:

Once you changed something in this file, you MUST minify this file to plugin.min.js file. If not, the function will not work as expected because TinyMCE only reads minified file.

//
// plugin.js in Project/Scripts/tinymce
//

tinymce.PluginManager.add("base64_image", function (a, b) {
    a.addButton("base64_image", {
        icon: "image",
        tooltip: "Insert image",
        onclick: function () {
            a.windowManager.open({
                title: "Insert image",
                url: b + "/dialog.html",
                width: 500,
                height: 150,
                buttons: [{
                    text: "Ok",
                    classes: 'widget btn primary first abs-layout-item',
                    onclick: function () {
                        var b = a.windowManager.getWindows()[0];

                        if (b.getContentWindow().document.getElementById('content').value == '') {
                            alert('Please select a file');
                            return false;
                        }

                        if (b.getContentWindow().document.getElementById('content').files[0].size > 1000 * 1024) {
                            alert('Max image file size is 1MB');
                            return false;
                        }

                        if (b.getContentWindow().document.getElementById('content').files[0].type != "image/jpeg" && b.getContentWindow().document.getElementById('content').files[0].type != "image/jpg" &&
                            b.getContentWindow().document.getElementById('content').files[0].type != "image/png" && b.getContentWindow().document.getElementById('content').files[0].type != "image/gif") {
                            alert('Only image file format can be uploaded');
                            return false;
                        }

                        var data;

                        data = new FormData();
                        data.append('file', b.getContentWindow().document.getElementById('content').files[0]);

                        $.ajax({
                            url: '/api/image/convert-base64',
                            data: data,
                            processData: false,
                            contentType: false,
                            async: false,
                            type: 'POST',
                        }).done(function (msg) {
                            var imageAlt = b.getContentWindow().document.getElementById('desc').value;
                            var imageSrc = "data:" + b.getContentWindow().document.getElementById('content').files[0].type + ";base64," + msg;

                            var imageTag = '<img src="' + imageSrc + '" alt="' + imageAlt + '" style="max-width: 100%;" />';

                            a.insertContent(imageTag), b.close()

                        }).fail(function (jqXHR, textStatus) {
                            alert("Request failed: " + jqXH.responseText + " --- " +RtextStatus);
                        });
                    }
                }, {
                    text: "Close",
                    onclick: "close"
                }]
            })
        }
    }),

    a.addMenuItem("base64_image", {
        icon: "image",
        text: "Insert image",
        context: "insert",
        prependToContext: !0,
        onclick: function () {
            a.windowManager.open({
                title: "Insert image",
                url: b + "/api/image/convert-base64",
                width: 500,
                height: 150,
                buttons: [{
                    text: "Ok",
                    classes: 'widget btn primary first abs-layout-item',
                    onclick: function () {
                        var b = a.windowManager.getWindows()[0];

                        if (b.getContentWindow().document.getElementById('content').value == '') {
                            alert('Please select a file');
                            return false;
                        }

                        if (b.getContentWindow().document.getElementById('content').files[0].size > 1000 * 1024) {
                            alert('Max image file size is 1MB');
                            return false;
                        }

                        if (b.getContentWindow().document.getElementById('content').files[0].type != "image/jpeg" && b.getContentWindow().document.getElementById('content').files[0].type != "image/jpg" &&
                            b.getContentWindow().document.getElementById('content').files[0].type != "image/png" && b.getContentWindow().document.getElementById('content').files[0].type != "image/gif") {
                            alert('Only image file format can be uploaded');
                            return false;
                        }

                        var data;

                        data = new FormData();
                        data.append('file', b.getContentWindow().document.getElementById('content').files[0]);

                        $.ajax({
                            url: '/api/image/convert-base64',
                            data: data,
                            processData: false,
                            contentType: false,
                            async: false,
                            type: 'POST',
                        }).done(function (msg) {
                            var imageAlt = b.getContentWindow().document.getElementById('desc').value;
                            var imageSrc = "data:" + b.getContentWindow().document.getElementById('content').files[0].type + ";base64," + msg;

                            var imageTag = '<img src="' + imageSrc + '" alt="' + imageAlt + '" style="max-width: 100%;" />';

                            a.insertContent(imageTag), b.close()

                        }).fail(function (jqXHR, textStatus) {
                            alert("Request failed: " + jqXH.responseText + " --- " + RtextStatus);
                        });
                    }
                }, {
                    text: "Close",
                    onclick: "close"
                }]
            })
        }
    })

});

 

Last, just change the Index.cshtml to include TinyMCE editor module.

//
// Index.cshtml in Project/View/Home
//


<div class="form-group" style="padding-top: 80px;">
    @Html.TextArea("Content")
</div>

@section Scripts {
    <script type="text/javascript" src="@Url.Content("~/Scripts/tinymce/tinymce.min.js")"></script>

    <script type="text/javascript">
        tinymce.init({
            selector: "textarea",
            width: '100%',
            height: 600,
            statusbar: false,
            menubar: false,

            setup: function (ed) {
                ed.on('init', function () {
                    this.getDoc().body.style.fontSize = '14px';
                    this.getDoc().body.style.fontFamily = '"Helvetica Neue", Helvetica, Arial, sans-serif';
                });
            },

            paste_data_images: true,

            plugins: [
                "advlist autolink lists link base64_image charmap hr anchor pagebreak",
                "searchreplace wordcount visualblocks visualchars code",
                "insertdatetime media nonbreaking save table contextmenu directionality",
                "emoticons textcolor colorpicker textpattern "
            ],
            toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link base64_image media | forecolor backcolor"
        });

        // Prevent bootstrap dialog from blocking focusin
        $(document).on('focusin', function (e) {
            if ($(e.target).closest(".mce-window").length) {
                e.stopImmediatePropagation();
            }
        });
    </script>
}

 

Screen Shot

This screen shot just shows how this plugin & WebApi module works.

History

2015-03-24 : Initial article uploaded

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGreat solution, but there is a bug Pin
dbtomy16-Nov-16 7:18
dbtomy16-Nov-16 7:18 
PraiseGreat Solution Pin
Chandra_NVS18-Oct-16 22:53
Chandra_NVS18-Oct-16 22:53 
PraiseNice solution for 'embedding' image data :) Pin
Doku-so17-Aug-16 0:45
Doku-so17-Aug-16 0:45 
PraiseThank you Pin
Kaew Pj6-Nov-15 5:37
Kaew Pj6-Nov-15 5:37 
QuestionWhy use an iFrame? Pin
Member 962935729-Sep-15 5:58
Member 962935729-Sep-15 5:58 
QuestionExcellent! One small typo. Pin
Member 1146224721-Jun-15 4:16
Member 1146224721-Jun-15 4:16 
QuestionExcellent Article! Pin
Your Display Name Here27-Mar-15 1:55
Your Display Name Here27-Mar-15 1:55 
Brian... Thanks for posting this, the more little tools we can keep adding to our toolbox, like this one, the better.
QuestionGood article but.... Pin
terryrey26-Mar-15 1:46
terryrey26-Mar-15 1:46 
AnswerRe: Good article but.... Pin
Brian Kwak27-Mar-15 6:34
professionalBrian Kwak27-Mar-15 6:34 
Questionsource code file link broken Pin
Tridip Bhattacharjee24-Mar-15 21:25
professionalTridip Bhattacharjee24-Mar-15 21:25 
AnswerRe: source code file link broken Pin
Brian Kwak25-Mar-15 11:41
professionalBrian Kwak25-Mar-15 11:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.