以往在開發 ASP.NET WebForm 專案時,如果專案需求是有編輯 HTML 圖文資料時,大多都會選擇使用 FCKEditor,FCKEditor 是個相當不錯的 HTML 編輯器,能夠符合許多情境的需求,然而 FCKEditor 的開發已經停止,轉而開發 CKEditor,CKEditor 是將原本的 FCKEditor 給全部重新寫過,雖然大致上的設定還是與原本的FCKEditor 相去不遠,但卻有著很大的不同,我在 ASP.NET MVC 3 專案上還沒有對 CKEditor 做過整合,所以此次就練習在 ASP.NET MVC 3 專案上整合 CKEditor 的功能,使用 NuGet 來取得 CKEditor,並且試著用不同的方式在前端與後端的傳值上做些不同的變化。
CKEditor
目前版本:3.6.2
Download:http://ckeditor.com/download
Documention:http://docs.cksource.com/
接下來這個連結是我認為比較重要的,很多的設定與CKEditor的重要資訊都可以在裡面找到,
CKEditor 3 JavaScript API Documentation
http://docs.cksource.com/ckeditor_api/index.html
在Download的最下方還是有提供FCKEditor最後版本的下載,不過還特別註明:retired (退休啦~)
一開始我們就說過,我們要在Visual Studio中使用NuGet來安裝CkEditor,所以就先別急著在官網上面去把最新的版本給下載下來,以下是CKEditor在NuGet Gallery的相關資訊。
NuGet Gallery - CKEditor 3.6.2
http://nuget.org/packages/CKEditor
PM> Install-Package CKEditor
於新的ASP.NET MVC 3網站中加入CKEditor
直接再VS2010中新增一個空白的ASP.NET MVC3網站專案
選擇Razor的ViewEngine(如果不熟悉的話也可以切換為ASPX的ViewEngine)
按下確定之後就會建立好一個「空空」的網站內容,真的是空的…
這時候還不用急著去建立新的Controller、View等,要先做的就是開啟NuGet Manage對專案中的套件、組件、函式庫做升級的動作。
上述的升級動作都做完之後,接下來就是切換到Manage NuGet Package視窗的「Online」並且在搜尋列輸入「CKEditor」
在上面的所搜尋出來的CKEditor中按下「Install」就可以將CKEditor完整安裝到網站中,安裝的過程需要一段時間,因為會把所有CKEditor會用到的JS檔案、圖檔等都一一下載到網站專案裡。
安裝完成後,會在Scripts目錄中看到完成安裝的CKEditor
使用 CKEditor
安裝好 CKEditor 之後就是要來使用它啦…至於中間過程的建立 Controller 與 ViewPage 的步驟就跳過,直接進入到使用的步驟來說明。
首先比較重要的是,先在 Views/Shared 目錄下的 _Layout.cshtml 去加入「Scripts/ckeditor/ckeditor.js」,像我的話,我都會把 Include Javascript 的部份給抽出來另外建立為一個部分檢視,然後也會在 _Layoout.cshtml 去加入一個RenderSection,用來放置每個ViewPage所要建立的Javascript程式,
如下:
Views/Shared/_Layout.cshtml
<!DOCTYPE html><html><head><title>@ViewBag.Title</title><link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /></head><body>@RenderBody()@Html.Partial("IncludeJavascript")@RenderSection("JavascriptContent", required: true)</body></html>
Views/Shared/IncludeJavascript.cshtml
<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script><script src="@Url.Content("~/Scripts/ckeditor/ckeditor.js")" type="text/javascript"></script>
接著下來,建立一個Craeate.cshtml,這個ViewPage是呈現一個簡單的文章編輯頁面,而文章內容就是要使用CKEditor
@model Test.Web.ODP.Models.Article@{ViewBag.Title = "Create";Layout = "~/Views/Shared/_Layout.cshtml";}<h2>Create</h2>@using (Html.BeginForm("Create", "Home", FormMethod.Post, new { id = "FormCreate" })){<fieldset><legend>Article</legend><div class="editor-label">@Html.LabelFor(model => model.SUBJECT)</div><div class="editor-field">@Html.TextBox("subject", null, new { id = "subject", style = "width: 400px", MaxLength = "100" })</div><div class="editor-label">@Html.LabelFor(model => model.CONTENT)</div><div class="editor-field">@Html.TextArea("content", new { id = "content", @name = "content" })</div><p><input type="button" value="Create" id="ButtonCreate" /></p></fieldset>}<div>@Html.ActionLink("Back to List", "Index")</div>
其中「@Html.TextArea("content", new { id = "content", @name = "content" })」建立一個 TextArea,而這個 TextArea 就是 CKEditor 要取代的 Html Element,
再來就是要在Javascript的區域中去做宣告,讓這個TexrArea置換為CKEditor
<script type="text/javascript" language="javascript">var editor = CKEDITOR.editor.replace('content', { skin: 'kama', width: '800px' });
</script>
CKEDITOR.editor.replace()方法:
第一個參數就是要指定取代的TextArea Name,
第二個參數則是設定這個CKEditor的外觀樣式,如外觀樣式、高度、寬度等,
有關 skin 的外觀樣式,可以開啟 Scripts/ckeditor/skins 去找到外觀樣式的名稱,安裝後預設有 Kama, office2003, v2
設定好之後,就可以來看看這個頁面的執行結果,
把 CKEditor 裡面的內容傳送到 Server 端
在 CKEditor 所編輯好的內容當然還是要送回到Server端去做儲存的處理,其中最簡單的方式就是直接用Form Post傳送,要用Form Post 傳送到Server端,當然就是要先在ViewPage中去usgin Hrml.BeginForm,如下:
而這個Form所要使用的Action網址就是Controller中的Create Action方法,在ASP.NET WebForm開發,當遇到前端要傳送含有HTML Tag的內容時,如果沒有在該頁面去設定 ValidateInput = false,那一定會出現警告訊息,同理,在ASP.NET MVC也是一樣,但是ValidateInput的設定就不是在ViewPage上面去做設定,而且可以去設定的地方有兩個地方。
先來看看如果不去設定ValidateInput時會看到的畫面:
設定的地方一:
Controller 中所對應的Action方法上,使用Attribute「ValudateInput」,除了使用HttpPost之外,要設定 ValidateInput(false)
而在這邊一定要特別的強調,因為我們將ValidateInput給設定為false,無疑就是開了一道安全大門給有心破壞的人來擅闖,所以為了避免網站遭受到XSS攻擊或是輸入的內容去帶有不友善或是惡意的指令碼,這個時候我們一定要再加上一道安全的鎖,
Microsoft Web Protection Library – AntiXSS Library V4.0
Codeplex:http://wpl.codeplex.com/
Download:ww.microsoft.com/download/en/details.aspx?id=5242
NuGet:https://nuget.org/packages/AntiXSS
於VS2010就可以透過NuGet幫網站專案安裝上 AntiXSS
相關介紹與使用方法,請各位參閱網路上的文件與資料。
程式中使用AntiXSS的 Sanitizer.GetSageHtmlFragement() 方法,取得安全的HTML區段內容。
在Controller的Create Action方法加上 [Validate(false)] Attribute 後就可以前端傳送帶有HTML Tag內容的資料道Server端,請記得一定要讓網站加上AntiXSS並且使用,以加強防護。
設定的地方二:
除了在Action方法加上 [Validate(false)] Attribute 外,另外也還有個地方可以設定,在 Scripts/ckeditor 目錄下找個檔案「config.js」
檔案內容:
config.js 可以對CKEditor做很多的設定,不過這些並不在此次的說明範圍中,在這個config.js檔案中可以加上「 config.htmlEncodeOutput = true; 」,修改如下:
修改好 config.js 後,為了驗證這裡的修改,我們把原本加在 HomeController Create Action方法前的Attribute拿掉,
接著就執行程式來看看執行的狀況:
不會在出現黃頁的警告訊息,而且有可以順利執行 Action方法
所以可以確定在 ckeditor/config.js 加上config.htmlEncodeOutput = true 的狀況下,Controller的Action方法是可以不用加上 [Validate(false)] Attribute ,不過咧~ 最好的作法還是不論前後端,都各自加上設定,也加上 AntiXSS的防護。
回到傳送資料到後端的部份,前面說到最直接的方式就是用Form Post的方式,但有時會為了不要讓整頁做POST的動作而想要用AJAX的方法將鎖需要傳送到後端的值給POST出去,這時候就必須要在前端先把FCKEditor的內容給取出來,再使用jQuery的AJAX方法然後與其他資料一併送到Server端。
前端使用jQuery AJAX方法傳送資料到Server端 - 1
前端的Javascript的程式中要去取得CKEditor的內容,不能直接以下的方法來取得:
var content = $('#content').val();
這樣是取不到內容的,因為TextArea content已經在之前用以下的程式給置換為CKEditor:
var editor = CKEDITOR.editor.replace('content', { skin: 'kama', width: '800px' });
所以應該是需要用下列的方式來取得CKEditor的內容:
var content = editor.getData();
那麼前端的完整程式如下:
function CreateArticle()
{var subject = $.trim($('#subject').val());
var content = editor.getData();
if (subject.length == 0){alert('請輸入標題');
return false;}if (content.length == 0){alert('請輸入內容');
return false;}$.ajax({url: '@Url.Action("Create", "Home")',type: 'post',data: { subject: subject, content: content },cache: false,
async: false,
dataType: 'json',success: function (data)
{if (data.Msg)
{alert(data.Msg);
return false;}else
{if (data.Result == 'Success')
{alert('Success');
location.href = '@Url.Action("Index", "Home")';}else
{alert(data.ResultMessage);
return false;}}}});}
透過上面的程式處理就可以把指定的資料給傳送到Server端,不過這樣的處理方式會比較適合網頁表單的欄位比較少的情況下,哪萬一欄位變得很多,用一個一個取值然後放值的動作就會顯得很費力也相當不聰明,不想透過Form-post的方式又要處理很多個表單欄位資料時,jQuery還有另外兩個方法,一個是 serialize() 另一個是 serializeArray().
前端使用jQuery AJAX方法傳送資料到Server端 - 2 使用 serialize()
先來看看使用使用 serialize() 來取得 FormCreate表單下的欄位資料內容,
var data = $('#FormCreate').serialize();
console.log(data);
取得的資料:
可以看到content是取不到資料的,此時可以用以下的取值方式取得資料後再把資料給加入到serialize()的資料中,
var content = editor.getData();
不過 $(‘#FormCreate’).serialize(); 所取得的資料是字串,要再將CKEditor的內容給串接到一個字串中,並不是很理想。
前端使用jQuery AJAX方法傳送資料到Server端 - 3 使用 serializeArray()
以serializeArray()所取得表單資料:
使用serializeArray()所取得的資料會以Name-Value的方式處理為物件陣列,如此一來會比直接用字串的串接處理更好,因為是陣列的資料,我們可以使用我之前所介紹的Linq to Javascript (JSLINQ)來處理這個陣列,並且把 editor.getData()的資料給放到陣列中 name = “content” 的物件value中,如下:
var values = $("#FormCreate").serializeArray();new JSLINQ(values).Where(function (item){if (item["name"] == "content" && item["value"].length == 0){item["value"] = editor.getData();
}});
最後在jQuery.AJAX方法中,data還是要以字串的方式傳送出去,這個時候可以使用jQuery.Param()方法來處理陣列的資料,
jQuery.param(values),
以下是完整的前端處理程式:
function CreateArticle()
{var subject = $.trim($('#subject').val());
var content = editor.getData();
if (subject.length == 0){alert('請輸入標題');
return false;}if (content.length == 0){alert('請輸入內容');
return false;}var values = $("#FormCreate").serializeArray();new JSLINQ(values).Where(function (item){if (item["name"] == "content" && item["value"].length == 0){item["value"] = editor.getData();
}});$.ajax({url: '@Url.Action("Create", "Home")',type: 'post',data: jQuery.param(values),cache: false,
async: false,
dataType: 'json',success: function (data)
{if (data.Msg)
{alert(data.Msg);
return false;}else
{if (data.Result == 'Success')
{alert('Success');
location.href = '@Url.Action("Index", "Home")';}else
{alert(data.ResultMessage);
return false;}}}});}
另外也提供Server端的 HomeController Create() Action程式內容:
[HttpPost][ValidateInput(false)]
public ActionResult Create(string subject, string content){Dictionary<string, string> jo = new Dictionary<string, string>();if (string.IsNullOrWhiteSpace(subject)){jo.Add("Msg", "沒有輸入標題");return Content(JsonConvert.SerializeObject(jo), "application/json");}if (string.IsNullOrWhiteSpace(content)){jo.Add("Msg", "沒有輸入內容");return Content(JsonConvert.SerializeObject(jo), "application/json");}try
{Article article = new Article();
article.SUBJECT = Sanitizer.GetSafeHtmlFragment(subject);article.CONTENT = Sanitizer.GetSafeHtmlFragment(content);articleService.Create(article);jo.Add("Result", "Success");}catch (Exception ex)
{jo.Add("Result", "Failure");jo.Add("ResultMessage", ex.Message);
}return Content(JsonConvert.SerializeObject(jo), "application/json");}public ActionResult Details(string id){if (string.IsNullOrWhiteSpace(id)){return RedirectToAction("Index", "Home");}Article article = articleService.FindOne(id);return View(article);
}public ActionResult Edit(string id){return View();
}}
參考連結:
demoshop - ASP.NET MVC validateRequest 取消驗證失效?
KKBruce - ASP.NET MVC - HTML 編輯器 | (3) CKEditor + CKFinder
CKEditor 3 JavaScript API Documentation
Class CKEDITOR.editor - getData()
Namespace CKEDITOR.config - CKEDITOR.config.htmlEncodeOutput
jQuery
.serialize()
.serializeArray()
jQuery.param()
以上
請問我ckediter建立好以後,假如編輯欄位新增123存入資料庫,在打開就會出現//p 123 //p所以一直編輯一直就會增加
回覆刪除編輯欄位就會出現//p <p> 123</p> //p,一直編輯就會越來越多,是我哪裡設定不對嗎?
Hello,
刪除如果只是在 config.js 加上 config.htmlEncodeOutput = true 的話
這是把輸入的內容以 Html Encode 做轉換然後傳送當後端,
所以需要再做編輯的時候,需要將存到資料庫的內容作轉換(使用 HttpUtility.HtmlDecode 方法) 後再輸出到 View.
抱歉,因為這是兩年前的文章了, 所以當初文章裡就沒有再對這個部分做詳細說明.
感謝你非常用心的解決問題,還特定開了新的一篇文章,我學到了~
刪除