使用 ASP.NET WebApi2 的 TinyMCE 图片插入插件






4.90/5 (7投票s)
如何在 ASP.NET 中在 TinyMCE 中添加图片?本文档展示了插件与 .NET WebApi2 通信以插入图片。
引言
在使用 TinyMCE 所见即所得编辑器时,您可能希望在编辑器中添加图片。但要实现此目的,您需要购买文件管理模块,或者自行开发插件。
有两种方法可以实现。第一种方法是将图片上传到服务器的特定目录,并在编辑器中创建指向该图片的链接。另一种方法是将图片文件转换为 Base64 编码字符串。大多数现代浏览器都支持第二种方法,因此我将向您展示如何使用 .NET WebApi 将图片文件转换为 base64 编码字符串。
支持的浏览器
- IE 9 或更高版本
- 几乎所有 Chrome 版本
- 几乎所有 Firefox 版本
- Opera:未测试。
使用代码
为了将图片二进制数据转换为 base64 编码字符串,我将使用 JQuery AJAX 和 .NET WebApi2。
因此,此项目需要 MVC5。
让我们从 WebApi 控制器开始。其中有一个名为 ConvertImageToBase64 的方法。此方法将图片二进制数据转换为 JQuery AJAX 请求的 base64 编码字符串。转换完成后,该字符串将返回到浏览器。
// // 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; } } }
这是 TinyMCE 弹出窗口定义文件。为了支持当前 TinyMCE 版本的相同外观和感觉,我在 Contents/Site.css 中添加了一些样式表并使用了 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… <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>
这是一个 JavaScript 插件,用于将图片二进制数据发送到服务器。
引用如果您更改了此文件中的任何内容,则必须将其缩小到 plugin.min.js 文件。否则,该功能将无法按预期工作,因为 TinyMCE 仅读取缩小后的文件。
// // 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" }] }) } }) });
最后,只需更改 Index.cshtml 以包含 TinyMCE 编辑器模块即可。
// // 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> }
屏幕截图
此屏幕截图只是展示了此插件和 WebApi 模块的工作方式。
历史
2015-03-24:初始文章上传