問題
你有一個網頁,列出重要的資訊明細,你要允許使用者迅速、輕鬆提交一個表單,無需重新載入整個頁面,失去自己在網站上的位置。
解決方案
使用AjaxHelper,建立一個新的Form,用新提交的內容自動更新現有的內容。
討論
下邊的例子把以前的秘方放在一起,向人們展示如何讓使用者提交一本書的評論,而不重新導向到另一個頁面去看那些評論以及自己提交的評論。
首先,我們需要建立一個新的model,用於儲存對書的評論。在model檔案夾,右鍵->添加->class,命名為:BookComment.cs。這個模型將用於儲存對書的評論。代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace MvcApplication.Models
{
public class BookComment
{
public int ID { get; set; }
[Required]
public string Comment { get; set; }
public DateTime Created { get; set; }
public int BookId { get; set; }
public virtual Book Book { get; set; }
}
}
下一步,必須更新先前建立的BookDBContext,去對這個表添加一個引用。先前的這個類是建立在原始的BookModel上。因此專門建立一個新的檔案來儲存這個類是明智的選擇。因為它將在您的項目中和未來的表一起繼續成長。右鍵點擊Models檔案夾。再次,選擇添加→類。這個類的名稱將BookDBContext:
(譯者:不要忘記刪除你在Book model裡的BookDBContext哦!)
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace MvcApplication.Models
{
public class BookDBContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<BookComment> BookComments { get; set; }
}
}
下面,重新build你的解決方案,在下邊的步驟裡你就可以找到我們新建立的model了。
我們要建立一個新的controller去實現 評論列表並且有能力去管理它們。選擇controller檔案夾,單擊“添加”→“控制器,命名為:BookCommentsController.cs。為了是手工工作最小化。我們將使用Entity Framework 腳手架建立controller。對於Data context class,選擇剛才建立的BookDBContext。(譯者:Modle class 選擇:BookComment (MvcApplication.Models))。點擊添加。當你再次運行應用程式時,你將接收到一個錯誤指示出:BookDBContext已經改變了。為瞭解決這個問題,你必須為DBContext建立一個initializer(初始化器)。因為這不是一個生產環境的應用程式,初始化器將刪除和重新建立資料庫。為了實現這個,右擊Models檔案夾。選擇添加->class ,命名為BookInitializer,代碼如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace MvcApplication.Models
{
public class BookInitializer : DropCreateDatabaseIfModelChanges<BookDBContext>
{
}
}
接下來Global.asax.cs要被更新,在Application_Start中去調用BookInitializer,更新Application_Start方法如下:
protected void Application_Start()
{
Database.SetInitializer<BookDBContext>(new BookInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
配置工作完成了,現在我們要做的就是允許使用者通過Ajax提交一個評論。我們將從Book/Details view 開始,因為展現評論的大多數邏輯都在這裡。(譯者:由於原書中引用代碼較多,我只在這指出我們更改的代碼)。代碼如下:
@model MvcApplication.Models.Book
@{
ViewBag.Title = "Details";
}
<h2>
Details</h2>
<fieldset>
<legend>Book</legend>
<div class="display-label">
Title</div>
<div class="display-field">
@Html.DisplayFor(model => model.Title)
</div>
<div class="display-label">
Isbn</div>
<div class="display-field">
@Html.DisplayFor(model => model.Isbn)
</div>
<div class="display-label">
Summary</div>
<div class="display-field">
@Html.DisplayFor(model => model.Summary)
</div>
<div class="display-label">
Author</div>
<div class="display-field">
@Html.DisplayFor(model => model.Author)
</div>
<div class="display-label">
Thumbnail</div>
<div class="display-field">
@Html.DisplayFor(model => model.Thumbnail)
</div>
<div class="display-label">
Price</div>
<div class="display-field">
@Html.DisplayFor(model => model.Price)
</div>
<div class="display-label">
Published</div>
<div class="display-field">
@Html.DisplayFor(model => model.Published)
</div>
</fieldset>
<fieldset>
<legend>Comments</legend>
<div id="Comments">
@{Html.RenderAction("Index", "BookComments",new { BookId = Model.ID });}
</div>
</fieldset>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
@Html.ActionLink("Back to List", "Index")
</p>
在上邊代碼中添加了一個新的<fieldset>,它裡邊又包含了一個<div>,div的id是” Comments”,在這個div中有一個Html.RenderAction(),它可以通過傳遞一個BookId參數到BookComment controller的Index action。
接下來,我們需要更新BookComments/Index view。在下邊的例子裡,Create New link被更新成,通過Ajax替代重新導向去展示Form。也會移除一些links,因為我們只需要添加的能力,不需要去管理這些評論。代碼如下:
@model IEnumerable<MvcApplication.Models.BookComment>
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
@Ajax.ActionLink("Create New", "Create", new
{
BookId = ViewBag.BookId
},
new AjaxOptions { UpdateTargetId = "AddComment" })
</p>
<div id="AddComment">
</div>
<table>
<tr>
<th>
Comment
</th>
<th>
Created
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Comment)
</td>
<td>
@Html.DisplayFor(modelItem => item.Created)
</td>
<td>
@Html.DisplayFor(modelItem => item.Book.Title)
</td>
</tr>
}
</table>
最後注意,需要改變的是自動產生的BookComments/Create view。將使用Ajax.BeginForm 去替換預設的Html.BeginForm。另外一件事是告訴當Ajax提交完成時,讓Form來調用一個JavaScript:函數ReloadComments()。此函數使用jQuery的Ajax請求來檢索更新的評論列表。也要建立一個帶BookID的hidden field去替換自動建立的下拉式清單。
代碼如下:
@model MvcApplication.Models.BookComment
@{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
<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>
<script type="text/javascript">
function ReloadComments() {
$("#Comments").load("@Url.Content("~/BookComments/Index?BookId=" + ViewBag.BookId)");
}
</script>
@using (Ajax.BeginForm(new AjaxOptions
{
OnComplete = "ReloadComments()"
}))
{
@Html.Hidden("BookId", (int)ViewBag.BookId);
@Html.ValidationSummary(true)
<fieldset>
<legend>BookComment</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Comment)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Comment)
@Html.ValidationMessageFor(model => model.Comment)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
為了完成這個例子,我們還需要在BookCommentsController更新一些代碼:
(譯者:作者為嘛總是說最後一步,都多少個最後一步了,別急,馬上就完成了 )
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication.Models;
namespace MvcApplication.Controllers
{
public class BookCommentsController : Controller
{
private BookDBContext db = new BookDBContext();
//
// GET: /BookComments/
public ActionResult Index(int BookId)
{
ViewBag.BookId = BookId;
var bookcomments = db.BookComments.Include(
b => b.Book).Where(b => b.BookId == BookId);
return PartialView(bookcomments.ToList());
}
//
// GET: /BookComments/Create
public ActionResult Create(int BookId)
{
ViewBag.BookId = BookId;
return PartialView();
}
//
// POST: /BookComments/Create
[HttpPost]
public ActionResult Create(BookComment bookcomment)
{
if (ModelState.IsValid)
{
bookcomment.Created = DateTime.Now;
db.BookComments.Add(bookcomment);
db.SaveChanges();
}
ViewBag.BookId = bookcomment.BookId;
return PartialView(bookcomment);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
在上面的例子中,Index Action已更新,接受整數的參數BookID。這被設定到ViewBag。另一個重要變化是,返回一個partial view 替代 返回完整的view(阻止完整的layout顯示)。如果你還記得在前面的例子,我們重用了相同的view執行Ajax請求,並在視圖中檢查,看它是否是一個Ajax請求去禁用layout。因為這個view是只通過Ajax顯示,更新controller去返回一個partial view是簡單的。
最後,Create action也被更新。基本的Create action就像Index action一樣 接收一個BookID,並返回一個partial view。第二,Create函action已被更新去設定評論的建立日期。如果有錯誤,返回一個partial view。Edit,details和delete action已經被移除了,因為沒用到他們。這些View也可以刪除,因為他們也沒有被使用。
現在,當使用者瀏覽一本書的細節,他們可以看到全部評論
發行的,如果他們想添加自己的意見,他們可以看到已經評論的列表。如果他們想添加自己的內容,他們可以點擊create new link,輸入他們的內容,提交,並且自動的看到他們剛提交的內容而不需要離開book詳細頁。