ASP.NET MVC - HTML 編輯器 | (3) CKEditor + CKFinder

CKEditor + CKFinder

最後一個 HTML 編輯器,我要介紹王牌 CKEditor

可能是因為版權的關係,我不清楚。目前沒有人把 CKEditor 整合到 NuGet 上。我們必須做一些事,才能在 ASP.NET MVC 上整合 CKEditor。

先下載 CKEditor 3.6.2

這裡請注意,請下載第一個 CKEditor Source 版本即可,不要下載 for xxx 版本,例如,CKEditor for ASP.NET 版本。

解壓縮後,把 ckeditor 目錄整個複製到專案的根目錄之下。

CKEditor 目錄位置
圖一:CKEditor 目錄位置
然後先一樣準備我們的 Model、Controller / Action 、View。

Model:BlogPostCKEditor.vb

''' <summary>
''' Blog 發表文章類別
''' </summary>
Public Class BlogPostCKEditor
    ' 標題
    Public Property Title() As String 
    ' 發佈日期
    Public Property PostedOn() As DateTime 
    ' 標籤
    Public Property Tags() As String 
    ' 內容
    
    Public Property Content() As String 
End Class

Controller / Action :BlogPostCKEditorController.vb

Namespace Mvc3HTMLEditorAndDatePicker
    Public Class BlogPostCKEditorController
        Inherits System.Web.Mvc.Controller

        '
        ' GET: /BlogPostCKEditor/Create

        Function Create() As ActionResult 
            Return View()
        End Function

        <HttpPost(), ActionName("Create")>
        Function Create_Post(model As BlogPostCKEditor) As ActionResult
            ' 在充許 HTML 的欄位,切記必須使用 AntiXSS 4.0 過濾
            ViewBag.HtmlContent = Microsoft.Security.Application.Sanitizer.GetSafeHtmlFragment(model.Content)
            Return View(model)
        End Function

    End Class
End Namespace

先「建置」,用「強型別」產生 View。

View:Create.aspx

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of Mvc3HTMLEditorAndDatePicker.BlogPostCKEditor)" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    KKBruce : CKEditor HTML編輯器測試
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>CKEditor HTML編輯器測試</h2>

<%-- The following line works around an ASP.NET compiler warning --%>
<%: "" %>

<script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script>

<% Using Html.BeginForm() %>
    <%: Html.ValidationSummary(True) %>
    <fieldset>
        <legend>CKEditor HTML編輯器測試</legend>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Title) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.Title) %>
            <%: Html.ValidationMessageFor(Function(model) model.Title) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.PostedOn) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.PostedOn) %>
            <%: Html.ValidationMessageFor(Function(model) model.PostedOn) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Tags) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.Tags) %>
            <%: Html.ValidationMessageFor(Function(model) model.Tags) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Content) %>
        </div>
        <div class="editor-field">
            <%: Html.TextAreaFor(Function(model) model.Content) %>
            <%: Html.ValidationMessageFor(Function(model) model.Content) %>
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
<% End Using %>

<p>
    發表內容(顯示HTML標籤):<%: ViewBag.HtmlContent %>
</p>
<p>
    發表內容(顯示HTML效果):<%= Html.Raw(ViewBag.HtmlContent) %>
</p>
<div>
    <%: Html.ActionLink("回首頁", "Index","Home") %>
</div>

</asp:Content>

注意,要把 Content 欄位的 EditorFor() 修改為 TextAreaFor(),接下來我們就來 ASP.NET MVC 裡使用 CKEditor 與 CKFinder 吧!


在 ASP.NET MVC 使用 CKEditor


我們先在 Create.aspx 的 View 中,引用 ckeditor.js 這支 Javascript。

<script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script>
<!-- CKEditor -->
<script src="<%: Url.Content("~/ckeditor/ckeditor.js") %>" type="text/javascript"></script>

然後在要使用的欄位下方撰寫啟用的 Javascript。以我們的範例,是要寫在 Content 欄位下方。

<div class="editor-label">
    <%: Html.LabelFor(Function(model) model.Content) %>
</div>
<div class="editor-field">
    <%: Html.TextAreaFor(Function(model) model.Content) %>
    <%: Html.ValidationMessageFor(Function(model) model.Content) %>
</div>
<script type="text/javascript">
    CKEDITOR.replace('<%= ckdata.ClientID %>', {skin : 'kama'}); 
</script>

以上程式碼是抄的,抄我「ASP.NET + CKEditor + CKFinder 整合教學」,結果呢?CKEditor當然不會運作,問題出在,ASP.NET 與 ASP.NET MVC 本身運作不同,ASP.NET MVC 可沒有 <%= ckdata.ClientID %>,ClientID ???我還不知道 ASP.NET MVC 有 ClientID 這種東西,有知道的高手、前輩、長輩,可以指教一下我。

那怎麼辦?

也很簡單,把網頁「Ctrl + F5」Run一下,看一下Source Code。你會發現,ASP.NET MVC 會直接使用欄位名稱當HTML 屬性 id 及 name 名稱的值,而不是使用 ASP.NET 控制項的方式。我們只要直接寫欄位名稱即可。

<script type="text/javascript">
    CKEDITOR.replace('Content', {skin : 'kama'}); 
</script>

建置、Ctrl + F5,把網站Run起來,輸入「http://localhost:6944//BlogPostCKEditor/Create」(記得,Port 要換成你的),

CKEditor 運作圖
圖二:CKEditor 運作圖
輸入一些資料,去測試一下效果。

CKEditor 中加入 CKFinder


Step 1. 要在 CKEditor 中加入 CKFinder 功能,先下載 CKFinder for ASP.NET (2.1),然後解壓縮後,把 ckfinder 目錄整個放到 ckeditor 目錄之下。
CKFinder 目錄位置
圖三:CKFinder 目錄位置

Step 2. CKFinder預設使用"/ckfinder/userfiles/"目錄,來讓使用者當成上傳與下載的目錄,我們另外在根目錄新增一個 Files 目錄,當來上傳與下載的目錄。新增之後,我們必須檢查上傳目錄權限(Files),必須有寫入的權限。(測試環境,最簡單是給此目錄 Everyone 寫入權限,不過切記,上線後的 Server 就不能這樣給,不然…應該會死的很慘)。一般是給 IUSR_<computername> 這個帳號權限(請參考:IIS Authentication Methods)。

Step 3. 將「~\ckeditor\ckfinder\bin\Release\CKFinder.dll」加入參考。

加入 CKFinder.dll 參考
圖四:加入 CKFinder.dll 參考
Step 4. 引用 ckfinder.js Javascript 檔案

<!-- CKEditor 與 CKFinder -->
<script src="<%: Url.Content("~/ckeditor/ckeditor.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/ckeditor/ckfinder/ckfinder.js") %>" type="text/javascript"></script>

Step 5. 修改 ckfinder\config.ascx 設定檔

需要修改的有三個地方:

  1. 將 CheckAuthentication() 方法內的 return false; 改為 return true;
    作者苦口婆心請大家不要設為「true;」,請大家注意,測試時還沒關係,正式上線一定一定要設為false。
  2. BaseUrl 修改為上傳路徑(URL)
  3. BaseDir 修改為上傳路徑(實體路徑)
以我而言:

  1. BaseUrl = "http://localhost:6944/Files/";
  2. BaseDir = "D:/StudyTest/Mvc3HTMLEditorAndDatePicker/Mvc3HTMLEditorAndDatePicker/Files/";

注意,第二個路徑,必須使用「/」而不是「\」。另,最後會有一個專案檔讓大家下載,記得要修改以上兩項資訊,範例才能正常運作。

Step 6. 改寫 CKEditor 的 Script

$(function () {
    CKEDITOR.replace('Content', {
        skin: 'kama',
        filebrowserBrowseUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html") %>',
        filebrowserImageBrowseUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html?type=Images") %>',
        filebrowserFlashBrowseUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html?type=Flash") %>',
        filebrowserUploadUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Files") %>',
        filebrowserImageUploadUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Images") %>',
        filebrowserFlashUploadUrl:
            '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Flash") %>'
    });
});

注意,路徑是否正確。

Step 7. 建置,reload 網頁。

Link, Image, Flash 會與 CKFinder 整合
圖五:Link, Image, Flash 會與 CKFinder 整合
以 Image 為例,

Image 影像管理
圖六:Image 影像管理
Image 伺服器端管理
圖七:Image 伺服器端管理(點擊看大圖)
這裡所管理的目錄就是我們所設定的 Files,CKFinder 會自己去建立對應的目錄,例如,圖片就會產生 Images 目錄。當然最重要的 Upload 功能。另外,在圖六中,有一個"上傳",在ASP.NET 可以正常運作,不過整合到 ASP.NET MVC 之後,反而不能正常運作,不過這不影響我們使用 CKFinder,因為一樣的功能,我們在Link, Image, Flash的第一頁面的「瀏覽伺服器端」中的 Upload 功能是正常的。

另注意,版權說明。

瀏覽伺服器中,Upadload 是使用一支 Uploader.swf 來上傳,所以沒有任何問題。但在"上傳"之中,是透過 connector.aspx 方式來上傳,我所抓到的網址為「POST http://localhost:6944/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&amp;type=Images&CKEditor=Content&CKEditorFuncNum=2&langCode=zh」得到 500 Internal Server Error。

訊息為:

<html>
    <head>
        <title>已發生類型 'CKFinder.Connector.ConnectorException' 的例外狀況。</title>
        <style>
         body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;} 
         p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
         b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
         H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
         H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
         pre {font-family:"Lucida Console";font-size: .9em}
         .marker {font-weight: bold; color: black;text-decoration: none;}
         .version {color: gray;}
         .error {margin-bottom: 10px;}
         .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }
        </style>
    </head>

    <body bgcolor="white">

            <span><H1>'/' 應用程式中發生伺服器錯誤。<hr width=100% size=1 color=silver></H1>

            <h2> <i>已發生類型 'CKFinder.Connector.ConnectorException' 的例外狀況。</i> </h2></span>

            <font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">

            <b> 描述: </b>在執行目前 Web 要求的過程中發生未處理的例外情形。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

            <br><br>

            <b> 例外詳細資訊: </b>CKFinder.Connector.ConnectorException: 已發生類型 'CKFinder.Connector.ConnectorException' 的例外狀況。<br><br>

            <b>原始程式錯誤:</b> <br><br>

            <table width=100% bgcolor="#ffffcc">
               <tr>
                  <td>
                      <code>

在執行目前 Web 要求期間,產生未處理的例外狀況。如需有關例外狀況來源與位置的資訊,可以使用下列的例外狀況堆疊追蹤取得。</code>

                  </td>
               </tr>
            </table>

            <br>

            <b>堆疊追蹤:</b> <br><br>

            <table width=100% bgcolor="#ffffcc">
               <tr>
                  <td>
                      <code><pre>

[ConnectorException: 已發生類型 'CKFinder.Connector.ConnectorException' 的例外狀況。]
   CKFinder.Connector.Connector.OnLoad(EventArgs e) in D:\CKFinder.Net\Connector\Connector.cs:222
   System.Web.UI.Control.LoadRecursive() +54
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3488
</pre></code>

                  </td>
               </tr>
            </table>

            <br>

            <hr width=100% size=1 color=silver>

            <b>版本資訊:</b> Microsoft .NET Framework 版本:4.0.30319; ASP.NET 版本:4.0.30319.17020

            </font>

    </body>
</html>
<!-- 
[ConnectorException]: 已發生類型 'CKFinder.Connector.ConnectorException' 的例外狀況。
   於 CKFinder.Connector.Connector.OnLoad(EventArgs e) 於 D:\CKFinder.Net\Connector\Connector.cs: 行 222
   於 System.Web.UI.Control.LoadRecursive()
   於 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
[HttpUnhandledException]: 已發生類型 'System.Web.HttpUnhandledException' 的例外狀況。
   於 System.Web.UI.Page.HandleError(Exception e)
   於 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   於 System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   於 System.Web.UI.Page.ProcessRequest()
   於 System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
   於 System.Web.UI.Page.ProcessRequest(HttpContext context)
   於 ASP.ckeditor_ckfinder_core_connector_aspx_connector_aspx.ProcessRequest(HttpContext context) 於 g:\Temp\VS\root\946b3876\15d73a2a\App_Web_uyf1d151.0.cs: 行 0
   於 System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   於 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
-->

看起來是因為我們是 ASP.NET MVC 的關係,那支上傳程式是為 ASP.NET 所寫,所以無法直接在 ASP.NET MVC 裡運作,CKFinder 細部問題,我就不往下追了。希望未來能有 CKEditor + CKFinder for ASP.NET MVC 的版本出現。

或是有人知道原因和解決辦法,請通知我一下。

CKEditor 中簡易設定 CKFinder


上面的 CKFinder 的 Script 很長,也很容易出錯,我們可以有更簡易的方式來設定 CKFinder。

<script type="text/javascript">
    //CKEDITOR.replace('Content', { skin: 'kama' });

    /*
    $(function () {
        CKEDITOR.replace('Content', {
            skin: 'kama',
            filebrowserBrowseUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html") %>',
            filebrowserImageBrowseUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html?type=Images") %>',
            filebrowserFlashBrowseUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/ckfinder.html?type=Flash") %>',
            filebrowserUploadUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Files") %>',
            filebrowserImageUploadUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Images") %>',
            filebrowserFlashUploadUrl:
                '<%: Url.Content("~/ckeditor/ckfinder/core/connector/aspx/connector.aspx?command=QuickUpload&type=Flash") %>'
        });
    });
    */

    var editor = CKEDITOR.replace('Content');
    CKFinder.SetupCKEditor(editor, '<%: Url.Content("~/ckeditor/ckfinder/") %>');

最後面簡單兩行,就可以把 CKFinder 設定進去 CKEditor。

CKEditor + CKFinder 使用心得


CKEditor + CKFinder 是一個很棒的組合,而且使用的流暢度也非常不錯,只不過未能像前面的 TinyMCE 或 CLEditor 一樣整合進 NuGet 裡面,所以設定上複雜很多,這是優點也是缺點,優點在於不管是 ASP.NET MVC 1/2/3/4 ... ,也不管是 Visual Studio 2008/2010/11,你都能把 CKEditor + CKFinder 整合進去。使用 NuGet 有一定的版本限制在,這是缺點。

參考資料

5 則留言:

  1. http://nuget.org/packages/CKEditor, 已經有人整合進NuGet,但CKFinder還沒有。

    回覆刪除
  2. 你好,想請教用Microsoft.Security.Application.Sanitizer.GetSafeHtmlFragment 之後,用戶輸入內容的格式可能會變,但如要保持用戶的格式,應如何做呢?

    回覆刪除
  3. Url.Content("~/ckfinder/core/connector/aspx/connector.aspx")?command=QuickUpload&type=Files

    後面的參數不要放在Url.Content裡面,不然&會變成 & 會造成上傳錯誤,我也因為這個錯誤而研究好久,終於找到原因

    事實證明,MVC是可以用ASPX上傳的

    回覆刪除
  4. 您好, 請教一個問題, 我們的網站使用CKEditor 4.2, 一直在上傳圖檔時,出現綠框說圖片上傳成功了, 但卻出現紅色框說在上傳時發生Http錯誤(404:檔案找不到)的錯誤訊息,旦找很久沒有很合適的解決,不知大師能否解惑?謝謝!

    回覆刪除
    回覆
    1. 這篇文件已經是十年前的產物。CKEditor後續版本我也沒有再追,建議可以到官方Repos去詢問看看:https://github.com/ckeditor/ckeditor4

      刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。