在现代Web开发中,将表示层、数据和逻辑清晰分离是一种常见的做法。HTML5/CSS3和jQuery的出现,使得能够显著地将客户端实现的重心转移到客户端。新的库如JSRender和Knockout进一步将关注点分离到客户端代码中。
本文将展示如何实现MVC4、JSRender和Knockout之间的整合。代码主要设计用来展示客户端技术,以便将数据从视图中解耦。这里仅使用控制器来模拟从存储库中检索数据。
首先,需要准备解决方案:
在App_Start下的BundleConfig.cs中注册bootstrap文件:
bundles.Add(new StyleBundle("~/Content/bootstrap/css"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap/js").Include("~/Scripts/bootstrap.js"));
bundles.Add(new ScriptBundle("~/bundles/custom").Include("~/Scripts/knockout-2.1.0.js", "~/Scripts/jsRender.js"));
bundles.Add(new ScriptBundle("~/bundles/demo").Include("~/Scripts/demo.js"));
在_Layout.cshtml中注册这些bundle:
@Styles.Render("~/Content/themes/base/css", "~/Content/css", "~/Content/bootstrap/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/custom")
@Scripts.Render("~/bundles/bootstrap/js")
JsRender是jQuery模板的替代品。它比jQuery模板更快,并且提供了清晰的关注点分离。在本例中,使用外部文件模板以实现更好的分离。JSRender模板可以位于同一页面的<script id='myTemplate' type='text/x-jsrender'></script>标签中。
使用文件中的模板的优势在于,模板可以更容易地在不同页面上重用。问题在于JSRender如何找到要渲染的模板。在示例中,有一个demo.js文件,其中包含两个函数:.getPath和.renderTemplate,它们将查找Templates/HTML/文件夹。
getPath函数使用命名约定来解析文件名,renderTemplate函数加载文件“_” + name + “.tmpl.html”。jQuery的.get()函数将读取文件并将上下文加载到内存中,然后渲染模板并使用Knockout.js绑定数据。
Knockout.js用于提供MVVM模式并触发绑定和刷新模板。首先,将添加一个没有任何数据绑定的模板。
在web项目中添加新文件夹Templates/HTML,并添加新的html文件:_home.tmpl.html、_head.tmpl.html、_contacts.tmpl.html、_header.tmpl.html。
_header.tmpl.html的内容如下:
<p>
要学习更多关于ASP.NET MVC,请访问
<a href="http://asp.net/mvc" title="ASP.NET MVC网站">
Bla … bla…
</a>
</p>
_home.tmpl.html的内容如下:
<h3>
建议以下内容:
</h3>
<ol class="round">
<li class="one">
<h5>
入门
</h5>
Bla … bla…
</ol>
现在将绑定简单的jsrender模板从外部文件加载到Index.cshtml视图中。为了在Index.cshtml视图页面上显示模板,需要添加自定义脚本以与knockout库绑定。
将使用两个不同的DIV进行数据绑定:一个用于头部,一个用于主体:
@{
ViewBag.Title = "首页";
}
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>
@ViewBag.Title.
</h1>
<h2>
@ViewBag.Message
</h2>
</hgroup>
</div>
</section>
请注意在这种情况下使用名称空间——demo。创建自定义脚本,将使用之前创建的自定义脚本文件来指定想要加载的模板。这是一个通用函数,将使用异步过程在读取模板文件后渲染模板。
添加以下代码:
var demo = demo || {};
$(function() {
demo.utils = function() {
var getPath = function(name) {
return "../Templates/HTML/_" + name + ".tmpl.html";
};
var renderTemplate = function(item) {
var file = getPath(item.name);
$.when($.get(file))
.done(function(tmplData) {
$.templates({ tmpl: tmplData });
item.selector($.render.tmpl(item.data));
});
};
return {
getPath: getPath,
renderTemplate: renderTemplate
};
}();
});
使用了揭示模式,其中函数将调用自身(注意函数末尾的())。现在是时候声明首页UI视图模型了,这可以在单独的文件中完成,但为了简单起见,将重用demo.js文件。
现在可以将模型添加到demo.js文件中:
$(function() {
demo.head = function() {
var headHtml = ko.observable(),
loadTemplate = demo.utils.renderTemplate({
name: "head",
data: "",
selector: headHtml
});
return {
loadTemplate: loadTemplate,
headHtml: headHtml
};
}();
demo.body = function() {
var bodyHtml = ko.observable(),
loadTemplate = demo.utils.renderTemplate({
name: "home",
data: "",
selector: bodyHtml
});
return {
bodyHtml: bodyHtml,
loadTemplate: loadTemplate
};
}();
demo.contacts = function() {
var contactHtml = ko.observable(),
loadContacts = function() {
$.ajax({
url: '/Home/LoadContacts',
type: "POST",
dataType: 'json',
success: function(d) {
demo.utils.renderTemplate({
path: "../",
name: 'contacts',
data: d,
selector: contactHtml
});
}
});
};
return {
contactHtml: contactHtml,
loadContacts: loadContacts
};
}();
ko.applyBindings(demo.head);
ko.applyBindings(demo.body);
demo.contacts.loadContacts();
});
此时,如果运行应用程序,应该看不到原始MVC首页和自定义的首页之间的区别。现在想要添加JSRender视图,它将绑定真实数据,这些数据将来自服务器端。将通过AJAX调用检索数据。
打开Contacts.cshtml并进行修改。它应该看起来像这样:
@{
ViewBag.Title = "联系人";
}
<hgroup class="title">
<h1>
@ViewBag.Title.
</h1>
<h2>
@ViewBag.Message
</h2>
</hgroup>
@Scripts.Render("~/bundles/demo")
<div data-bind="html: demo.contacts.contactHtml" id="headHtml">
</div>
为了从控制器加载数据,将添加一个新的AJAX调用。
打开HomeController.cs并添加以下代码:
[HttpGet]
public JsonResult LoadContacts()
{
var contacts = new List<Contact> {
new Contact { fn = "Ben", ln = "Doe", title = "Developer", email = "myemail@code.com", phone = "888-555-6565" },
new Contact { fn = "John", ln = "Smith", title = "Boss", email = "boss@code.com", phone = "888-555-7777" }
};
return Json(contacts);
}
还需要创建一个简单的Contact类:
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Title { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}