2013年11月17日 星期日

ASP.NET MVC 使用 CKEditor.Mvc 與 HtmlEncodeOutput 補充說明

ASP.NET MVC 3 使用 CKEditor」這一篇文章是大約在兩年前所寫的,其實在使用上無論是 ASP.NET MVC 3, 4 或是 5 都是一樣的,但是這裡將會說明如何在 ASP.NET MVC 專案裡使用 CKEditor.Mvc 以及對於使用 config.htmlEncodeOutput 設定後的補充說明。

 


開發環境以及使用版本:Visual Studio 2013, ASP.NET MVC 5

 

Step.1

這裡已經在專案裡加入 LocalDB 並建立一個 Article 的表格,然後使用 EF5 建立 Model(Database First),如下:

image

image

 

Step.2

接著我使用添加基架裡的「具有檢視、使用 Entity Framework 的 MVC 5 控制器」來建立 ArticleController 以及檢視頁面,

image

SNAGHTML94c6d86

不過預設建立出來的 Controller 與 View 還是會做適度的修改,

public class ArticleController : Controller
{
    private DemoEntities db = new DemoEntities();
 
    public ActionResult Index()
    {
        return View(db.Articles.ToList());
    }
 
    public ActionResult Details(Guid? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Article article = db.Articles.Find(id);
        if (article == null)
        {
            return HttpNotFound();
        }
        return View(article);
    }
 
    public ActionResult Create()
    {
        return View();
    }
 
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include="ID,Subject,ContentText,IsPublish")] Article article)
    {
        if (ModelState.IsValid)
        {
            article.ID = Guid.NewGuid();
            article.CreateDate = DateTime.Now;
            article.UpdateDate = DateTime.Now;
 
            db.Articles.Add(article);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
 
        return View(article);
    }
 
    // GET: /Article/Edit/5
    public ActionResult Edit(Guid? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Article article = db.Articles.Find(id);
        if (article == null)
        {
            return HttpNotFound();
        }
        return View(article);
    }
 
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include="ID,Subject,ContentText,IsPublish")] Article article)
    {
        if (ModelState.IsValid)
        {
            var original = db.Articles.FirstOrDefault(x => x.ID == article.ID);
 
            if (original == null)
            {
                return RedirectToAction("Index");
            }
 
            original.Subject = article.Subject;
            original.ContentText = article.ContentText;
            original.IsPublish = article.IsPublish;
            original.UpdateDate = DateTime.Now;
 
            db.Entry(article).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(article);
    }
 
    public ActionResult Delete(Guid? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Article article = db.Articles.Find(id);
        if (article == null)
        {
            return HttpNotFound();
        }
        return View(article);
    }
 
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(Guid id)
    {
        Article article = db.Articles.Find(id);
        db.Articles.Remove(article);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

View

image

image

 

Step.3

使用 NuGet 加入 CKEditor.Mvc 套件,

image

這個 CKEditor.Mvc 雖然不是由 CKEditor 官方所製作的,但是與官方的版本是沒有差太多(寫這篇文的時候,CKEditor.Mvc 所使用的 CKEditor 為 3.6.5,而 CKEditor 套件的版本為 3.6.4),不過 CKEditor.Mvc 除了在 Scripts 目錄裡有加入 ckeditor 目錄與檔案外,在 ~/Views/Shared 目錄下會增加 EditorTemplates 的 Html.cshtml,讓我們可以在 View 裡以比較快速方便的方式加入 CKEditor。

image

怎麼使用 CKEditor.Mvc 所提供的 EditorTemplates,在最後會做說明。

 

Step.4

按照「ASP.NET MVC 3 使用 CKEditor」裡所說明的,我們將 Create.cshtml 裡的 ContentText 修改為使用 CKEditor,

修改前:

image

修改後:

image

image

執行結果:

image

 

Step.5

在「ASP.NET MVC 3 使用 CKEditor」這篇文章裡對於要將輸入在 CKEditor 裡文字傳送到後端有做了說明,如果只有做 Step.4 的修改步驟的話,那麼資料傳送到後端就會出現以下的錯誤訊息,

image

我在文章裡有說過可以透過兩種修改方式做修改,一種是在 Controller 接收前端 POST 資料的 Action 方法加上 [ValidateInput(false)] 的 attribute,

image

而另外一種則是在 ~/Scripts/ckeditor/config.js 裡添加 config.htmlEncodeOutput = true;  的設定,

http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html#.htmlEncodeOutput

image

在 config.js 裡添加 config.htmlEncodeOutput = true; 的設定,在 Controller 的 Action 方法上是可以不用加上 ValidateInput(false) 的 attribute,因為添加 config.htmlEncodeOutput = true; 的設定是將 CKEditor 內的輸入內容字串轉換為 HTML 編碼的字串,所以儲存到資料庫的資料會是編碼過的內容,

image

如果沒有將這個資料再經過處理的話,那麼在編輯畫面裡就會變成以下的內容,

image

如果點選 CKEditor 的「原始碼」查看的話,可以看到原本處存在資料庫的文字又再被包一層 <p></p>,

image

前面有提到,這邊是使用在 config.js 裡添加 config.htmlEncodeOutput = true; 的方式,會把 CKEditror 裡的字串經過 HTML 編碼處理,所以我們要進行編輯的時候,在 Controller 裡從資料庫取出資料後要將經過 HTML 編碼的字串給轉換為解碼的字串,如下:

image

重新建置方案後再執行,在 CKEditor 內的就是經過解碼後的字串,

image

image

 

Step.6

前面有說過我們透過 NuGet 所加入的是 CKEditor.Mvc 而不是 CKEditor,使用 CKEditor.Mvc 的差別在於多了 ~/Views/Shared/EditorTemplates/Html.cshtml,

image

要使用這個 Templates 的方式有兩種,一種是在 Metadata 裡使用 UIHint 做指定,另外一種是在 View 裡做指定,先說明在 View 指定使用 Templates 的方式,

原本我們已經把 View 上面的 ContentText 做了修改,

image

修改為使用 Html.EditorFor(), 然後指定TemplateName 使用 Html,

image

而在 View 下面的 Javascript 部份就可以移除,但是使用 ckeditor.js 的部份還是要保留,

image

執行結果:

image

 

Step.7

另外可以直接在 Metadata 裡指定某個屬性的 UIHint 為 Html,這樣可以不必在 View 裡面再去做修改。

建立 Articla 的 Partial Class,然後在 ContentText 屬性加上 UIHint attribute,並指定使用 Html 這個 Template,

[MetadataType(typeof(ArticleViewModel))]
public partial class Article
{
    public class ArticleViewModel
    {
        [DisplayName("ID")]
        [Required(ErrorMessage = "Id 為必填")]
        public Guid ID { get; set; }
 
        [DisplayName("標題")]
        [Required(ErrorMessage = "名稱 為必填")]
        public string Subject { get; set; }
 
        [DisplayName("內容")]
        [Required(ErrorMessage = "內容 為必填")]
        [UIHint("Html")]
        public string ContentText { get; set; }
 
        [DisplayName("是否發佈")]
        [Required(ErrorMessage = "是否發佈 為必填")]
        public bool IsPublish { get; set; }
 
        [DisplayName("建立時間")]
        [Required(ErrorMessage = "CreateDate 為必填")]
        public DateTime CreateDate { get; set; }
 
        [DisplayName("修改時間")]
        [Required(ErrorMessage = "UpdateDate 為必填")]
        public DateTime UpdateDate { get; set; }
    }
}

原本 View 裡面的 ContentText 是需要指定 Html,因為已經在 Metadata 裡使用 UIHint 做指定,所以就可以移除,

image

執行結果

image

 


當然也可以由自己來設定 Template 的內容,Html.cshtml 由自己來做,在 NuGet 裡不安裝 CKEditor.Mvc 而是安裝 CKEditor,然後自己建立 CKEditor 的 EditorTemplates,這部份可以參考下面連結的文章說明:

Simple CKEditor MVC EditorTemplate | Troubles of the brain

其實我會比較建議大家直接由 NuGet 安裝 CKEditor,而不是 CKEditor.Mvc,這邊會介紹只是讓大家知道有這麼一個 Package 可以始,以及這個 Package 所建立的 EditorTemplate「Html.cshtml」要怎麼使用,再進而讓大家知道這個 Html.cshtml 應該怎麼建立。

另外就是如果不在 Action 方法使用 ValidateInput attribute 的話,可以經由 ckeditor 的 config.js 去設定 config.htmlEncodeOutput,並且補充說明之前沒有說過的內容,要怎麼使用 Html Decode 將經過 Html 編碼的字串做解碼的處理最後輸出到 View 上面的 CKEditor,而不會出現錯誤的內容。

 

以上

5 則留言:

  1. 天啊!!!感謝板主,寫得好詳細,照著做真的做得出來
    常常看板主的實用文,有沒有考慮在SOS Reader上寫,這樣可以養粉絲外,讓他們直接用金錢訂購您的服務
    這樣做起來會不會更有動力些?!

    回覆刪除
    回覆
    1. 謝謝你的回應與提議
      但文章還是自由自在的寫就好了,不想因為要有所謂的「動力」來附加在寫文章的初衷與動機
      有想法有靈感有時間才會讓我把文章給寫出來,不為別的,就只為了想寫而寫

      刪除
  2. 受益良多,特登入帳號感謝版主

    回覆刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。