简介
这篇文章将向大家介绍一下如何在ASP.NET MVC 4.0下如何使用Knockout JS和如何使用一种可以帮助我们写出更高维护度的JS基本模式。这里我用到的实例,最适用于SAP 的开发(详细信息见:single-page application)。但是knockout JS的使用范围却不仅限于于此。你可以根据你的需求,把它使用到任何asp.net MVC 的程序开发中。
背景
如果我们从事基于asp.net MVC 架构的程序开发。那么,很明显,我们不得不分出相当一部分精力来处理JS(至少我敢说,我们可以在几乎所有的项目中都能看到jQuery)。根据我的个人经验,对一个传统的ASP.NET开发者来说,处理JS部分的工作(开发、调试等)是一件肯头疼的事情。诚然,在MVC应用中,不受服务端控制和也没有viewstate(视图状态),确实会让人惴惴不安。然而,在我刚刚开始从事MVC应用开发的时候,我仔细研究了一下JS和jQUery后,我发现js和jQuery是一种非常简单而且可靠的web开发框架。我最近开发的项目中大多数是基于SAP开发的程序。 尽管刚开始的时候,我尝试了很多种技术和方式(灰常纠结),但是我现在觉得使用该框架真心的舒服,而且非常喜爱这个方法。在这里,非常感谢Douglas Crockford,他写的那本《JavaScript Good Parts》,非常棒,对我更好的理解JavaScript有很大帮助。
代码
我将分基础和进阶两部分,一步一步的向大家介绍该技术。如果你不是ASP.NET MVC程序开发的新手,可以跳过基础部分,直接阅读进阶部分。
基础部分:
-
创建ASP.NET MVC 4应用程序:文件 -> 新建 -> 项目-> 模板 -> Visual C# -> Web -> ASP.NET MVC 4 Web Application -> 给项目起一个友好的名字,单击确定.
-
在选择模板菜单中选择Basic。选择Razor作为视图引擎,单击确定.
-
手动下载knockout mapping library并添加到项目中。或者运行NuGet命令Install-Package Knockout.Mapping 实现该类库的添加。
-
右键Controllers文件夹-> 添加 -> Controller。命名为PersonController,单击添加按钮.
-
右键项目->添加 -> 新建文件夹。重命名为:ViewModel.
-
右键ViewModel文件夹-> 添加 -> 类。把新添加的类命名为:PersonViewModel。并添加如下代码:
123456publicclass PersonViewModel{publicint Id {get;set; }publicstring Name {get;set; }publicDateTime DateOfBirth {get;set; }} -
回到PersonController,粘贴如下代码:
1234567891011121314151617publicActionResult Index(){var viewModel =newPersonViewModel(){Id = 1,Name ="Naveen",DateOfBirth =newDateTime(1990, 11, 21)};returnView(viewModel);}[HttpPost]publicJsonResult SavePersonDetails(PersonViewModel viewModel){// TODO: Save logic goes here.return Json(new { });}同时,确保在该文件中添加了对View-Model的引用。
-
把光标定位到index方法中。右键鼠标,单击Add View VS将弹出一个窗口。保留默认选项。单击添加按钮。这样会在Views文件夹下的Person 文件夹下创建Index.cshtml文件。
-
打开App_Start 文件夹下的RouteConfig .cs文件。在这里设置controller为Person。如果完成了以上操作,RoutConfig文件会自动更新为下列代码。
12345678910111213publicclass RouteConfig{publicstaticvoid RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute(name:"Default",url:"{controller}/{action}/{id}",defaults:new{ controller ="Person", action ="Index", id = UrlParameter.Optional });}} -
右键Content文件夹-> 添加 -> 新建项 -> 选择 样式表,并命名为Person.css.
-
在Scripts文件加下添加Application文件夹。右键该文件夹添加新项目,选择javascript文件。命名为Person.js。
进阶
以上操作只是做好了准备工作。现在,让我们总结一下到做了哪些操作:
首先,我们创建了一个以Person为实体的项目。这意味着我们创建了PersonViewModel, PersonController,和一个为表现层而创建的Index.cshtml文件。
其次,我们也添加了一个样式文件: Person.css 和Person.js。(框架需要)
三、为了使Person作为根,我们修改了RouteConfig
四、我们在项目中添加了knockout mapping library
到此,准备工作全部完毕,但是在继续学习使用knockout JS之前,先向大家简单介绍一下什么是knockout。
Knockout:
它是是一个用来帮助我们保持视图模型和UI元素同步的javascript类库。然而,这不是knockout的唯一功能。若想对其深入了解,请单机http://knockoutjs.com/了解更多内容。由于这篇文章旨在向大家展示如何入门,因此本文将把重点放在基础的UI绑定上。关于knockout的数据绑定方面,我们只需向html页面元素添加data-bind属性即可。例如:如果ViewModel 对象在Person.ViewModel并且我们想绑定name属性到一个textbox,那么我们需要有下列标记符。
|
1
|
<
inputdata-bind
=
"value: Person.ViewModel.Name"
type
=
"text"
>
|
同样,我们需要为所有域添加数据绑定属性。现在,如果你改变了那个对象的值,那么它的值会反射到UI和vice-versa。这是knockout的基本功能。随着我们的进一步探索,我们将会学到更多关于knockout的功能。下面我们继续一起讨论学习knockout:
-
既然我们需要为每一个UI元素添加数据绑定属性,我们最好为自己建立一个html helper方法。为了实现上述操作,请在项目中添加一个文件夹,并重命名为Helper。在Helper文件夹下添加一个类文件HtmlExtensions.cs并将下面的代码复制到该类。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116publicstaticclassHtmlExtensions{/// <summary>/// To create an observable HTML Control./// </summary>/// <typeparam name="TModel">The model object</typeparam>/// <typeparam name="TProperty">The property name</typeparam>/// <param name="htmlHelper">The <see cref="HtmlHelper<T>"/></param>/// <param name="expression">The property expression</param>/// <param name="controlType">The <see cref="ControlTypeConstants"/></param>/// <param name="htmlAttributes">The html attributes</param>/// <returns>Returns computed HTML string.</returns>publicstaticIHtmlString ObservableControlFor<TModel, TProperty>(thisHtmlHelper<TModel> htmlHelper, Expression<Func<TModel,TProperty>> expression,stringcontrolType =ControlTypeConstants.TextBox,objecthtmlAttributes =null){var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);stringjsObjectName =null;stringgeneralWidth =null;// This will be useful, if the same extension has// to share with multiple pages (i.e. each with different view models).switch(metaData.ContainerType.Name){case"PersonViewModel":// Where Person is the Javascript object name (namespace in theory).jsObjectName ="Person.ViewModel.";generalWidth ="width: 380px";break;default:thrownewException(string.Format("The container type {0} is not supported yet.",metaData.ContainerType.Name));}var propertyObject = jsObjectName + metaData.PropertyName;TagBuilder controlBuilder =null;// Various control type creation.switch(controlType){caseControlTypeConstants.TextBox:controlBuilder =newTagBuilder("input");controlBuilder.Attributes.Add("type","text");controlBuilder.Attributes.Add("style", generalWidth);break;caseControlTypeConstants.Html5NumberInput:controlBuilder =newTagBuilder("input");controlBuilder.Attributes.Add("type","number");controlBuilder.Attributes.Add("style", generalWidth);break;caseControlTypeConstants.Html5UrlInput:controlBuilder =newTagBuilder("input");controlBuilder.Attributes.Add("type","url");controlBuilder.Attributes.Add("style", generalWidth);break;caseControlTypeConstants.TextArea:controlBuilder =newTagBuilder("textarea");controlBuilder.Attributes.Add("rows","5");break;caseControlTypeConstants.DropDownList:controlBuilder =newTagBuilder("div");controlBuilder.Attributes.Add("class","dropDownList");break;caseControlTypeConstants.JqueryUIDateInput:controlBuilder =newTagBuilder("input");controlBuilder.Attributes.Add("type","text");controlBuilder.Attributes.Add("style", generalWidth);controlBuilder.Attributes.Add("class","dateInput");controlBuilder.Attributes.Add("data-bind","date: "+ propertyObject);// date is the customized knockout binding handler. Check PrepareKo method of Person.break;default:thrownewException(string.Format("The control type {0} is not supported yet.", controlType));}controlBuilder.Attributes.Add("id", metaData.PropertyName);controlBuilder.Attributes.Add("name", metaData.PropertyName);// Check data-bind already exists, add if not.if(!controlBuilder.Attributes.ContainsKey("data-bind")){controlBuilder.Attributes.Add("data-bind","value: "+ propertyObject);}// Merge provided custom html attributes. This overrides the previously defined attributes, if any.if(htmlAttributes !=null){controlBuilder.MergeAttributes(HtmlExtensions.AnonymousObjectToHtmlAttributes(htmlAttributes),true);}returnMvcHtmlString.Create(controlBuilder.ToString());}/// <summary>/// To convert '_' into '-'./// </summary>/// <param name="htmlAttributes">The html attributes.</param>/// <returns>Returns converted <see cref="RouteValueDictionary"/>.</returns>privatestaticRouteValueDictionary AnonymousObjectToHtmlAttributes(objecthtmlAttributes){RouteValueDictionary result =newRouteValueDictionary();if(htmlAttributes !=null){foreach(System.ComponentModel.PropertyDescriptor propertyinSystem.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes)){result.Add(property.Name.Replace('_','-'), property.GetValue(htmlAttributes));}}returnresult;}}同样添加一个类文件ViewModelConstants.cs并复制下列代码。
1234567891011publicstaticclassControlTypeConstants{publicconststring TextBox ="TextBox";publicconststring TextArea ="TextArea";publicconststring CheckBox ="CheckBox";publicconststring DropDownList ="DropDownList";publicconststring Html5NumberInput ="Html5NumberInput";publicconststring Html5UrlInput ="Html5UrlInput";publicconststring Html5DateInput ="Html5DateInput";publicconststring JqueryUIDateInput ="JqueryUIDateInput";}ObservableControlFor是一个实现页面元素和数据属性相关联的一个方法。通过默认机制,它创建了TextBox。但是我们可以自己添加在ControlTypeConstants中定义的各种类型。在添加的时候可以按自己的需求随意添加。在添加的时候只需在ControlTypeConstants中添加另一个constant.并扩展位于ObservableControlFor 中的switch语句。如果不太理解上述方法体内的代码,不用担心,随着下面的学习,慢慢就懂了。
-
打开Views/Person文件夹下的Index.cshtml文件,并粘贴如下代码:
1234567891011121314151617181920212223242526@model Mvc4withKnockoutJsWalkThrough.ViewModel.PersonViewModel@using Mvc4withKnockoutJsWalkThrough.Helper@section styles{@Styles.Render("~/Content/themes/base/css")<linkhref="~/Content/Person.css"rel="stylesheet"/>}@section scripts{@Scripts.Render("~/bundles/jqueryui")<scriptsrc="~/Scripts/knockout-2.1.0.js"></script><scriptsrc="~/Scripts/knockout.mapping-latest.js"></script><scriptsrc="~/Scripts/Application/Person.js"></script><scripttype="text/javascript">Person.SaveUrl = '@Url.Action("SavePersonDetails", "Person")';Person.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));</script>}<form><divclass="mainWrapper"><table><tr><td>Id :</td><td>@Html.ObservableControlFor(model => model.Id, ControlTypeConstants.Html5NumberInput)</td></tr><tr><td>Name :</td><td>@Html.ObservableControlFor(model => model.Name)</td></tr><tr><td>Date Of Birth :</td><td>@Html.ObservableControlFor(model => model.DateOfBirth,ControlTypeConstants.JqueryUIDateInput)</td></tr></table></div><br/><inputid="Save"type="submit"value="Save"/></form>部分人可能遇到如下错误:
1json doesnotexistinthe current context你可以按照**连接**中的步骤解决这个问题。或者你需要用你自己的命名空间替换掉Mvc4withKnockoutJsWalkThrough。正如你在代码中看到的,我们正在使用在第12步中创建的html helper。同时你能注意到在script 模块(section)编写的脚本
12Person.SaveUrl ='@Url.Action("SavePersonDetails", "Person")';Person.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));这里的Person是一个js对象(我们即将在Person.js文件中创建)。理论上来说,我们可以称之为命名空间(至少在这里非常适用)。在razor引擎中创建的工程中存在不能像扩展JS文件中使用语法。因此我们指定razor估计Person 对象的属性值。下面我将详细解ko.mapping.fromJS(@Html.Raw(Json.Encode(Model))); 的作用。
-
你可能注意到我们在Index.cshtml 页中使用了样式。 因此,必须在相关的页中定义以上内容。在本实例中,是_Layout.cshtml。因此打开views文件夹下Shared folder文件夹中的_Layout.cshtml文件。在头文件结束前,添加下面的代码:
1@RenderSection("styles", required: false)最后,图层展示页面会是这个样子。
12345678910<!DOCTYPEhtml><html><head><metacharset="utf-8"/><metaname="viewport"content="width=device-width"/><title>@ViewBag.Title</title>@Styles.Render("~/Content/css")@Scripts.Render("~/bundles/modernizr")@RenderSection("styles", required: false)</head><body>@RenderBody()@Scripts.Render("~/bundles/jquery")@RenderSection("scripts", required: false)</body></html> -
现在,开始编写等待已久的javascript代码。打开Person.js文件(文件在Scripts文件夹下Application文件夹中)并粘贴下面代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879varPerson = {PrepareKo:function() {ko.bindingHandlers.date = {init:function(element, valueAccessor, allBindingsAccessor, viewModel) {element.onchange =function() {varobservable = valueAccessor();observable(newDate(element.value));}},update:function(element, valueAccessor, allBindingsAccessor, viewModel) {varobservable = valueAccessor();varvalueUnwrapped = ko.utils.unwrapObservable(observable);if((typeofvalueUnwrapped =='string'|| valueUnwrappedinstanceofString) &&valueUnwrapped.indexOf('/Date') === 0) {varparsedDate = Person.ParseJsonDate(valueUnwrapped);element.value = parsedDate.getMonth() + 1 +"/"+parsedDate.getDate() +"/"+ parsedDate.getFullYear();observable(parsedDate);}}};},ParseJsonDate:function(jsonDate) {returnnewDate(parseInt(jsonDate.substr(6)));},BindUIwithViewModel:function(viewModel) {ko.applyBindings(viewModel);},EvaluateJqueryUI:function() {$('.dateInput').datepicker();},RegisterUIEventHandlers:function() {$('#Save').click(function(e) {// Check whether the form is valid. Note: Remove this check, if you are not using HTML5if(document.forms[0].checkValidity()) {e.preventDefault();$.ajax({type:"POST",url: Person.SaveUrl,data: ko.toJSON(Person.ViewModel),contentType:'application/json',async:true,beforeSend:function() {// Display loading image},success:function(result) {// Handle the response here.},complete:function() {// Hide loading image.},error:function(jqXHR, textStatus, errorThrown) {// Handle error.}});}});},};$(document).ready(function() {Person.PrepareKo();Person.BindUIwithViewModel(Person.ViewModel);Person.EvaluateJqueryUI();Person.RegisterUIEventHandlers();});这里的Person是命名空间,或者你可你称它为描述person相关操作的核心对象。为了更好的理解上述代码,在我解释上面方法之前。我想简单介绍一下knockout的一些内容。
关于knockout:
到目前为止,我解释了如何绑定viewmodel和UI元素。但是我没介绍如何创建viewmodel。大体上,你可以像下面这样创建一个viewmodel:
|
1
2
3
4
5
|
var
myViewModel = {
Name: ko.observable(
'Bob'
),
Age: ko.observable(123),
Report: ko.observableArray([1,5,6,7,8])
};
|
可以这样激活knockout:
|
1
|
ko.applyBindings(myViewModel);
|
通过阅读上述代码,貌似我们需要在每个属性上调用ko.observable。但是别担心,我们还有其它的方式可以选择。Knockout在knockout.mapping-* 库中提供了引入的功能。 我们在第三步的时候已经加入了该类库。我们也在第十三步中使用了一次:
|
1
|
Person.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));
|
ko.mapping.fromJS通过服务器服务器提供的javascript对象为我们创建了想要的视图模型(viewmodel)。通过这种方式我们在Person.ViewModel 中实现了视图模型(viewmodel)。此后,所有的Person.ViewModel 属性都是可见的,而且我们可以通过方法调用它的属性。例如:我们可以像Person.ViewModel.Name()这样获得person的name属性。也可以像Person.ViewModel.Name('New Name') 设置person的name属性。正如你所注意到的:Person.ViweModel 已经不再适合存储。这意味着,如果你直接向服务器传递Person.ViewModel ,它将不能映射为.NET可识别的对象。因此,我们需要从knockout获得无格式的JS对象。想要实现上述需求,就要调用ko.toJSON 方法。
|
1
|
ko.toJSON(Person.ViewModel)
|
下面我们开始探讨Person 对象中定义的方法:
PrepareKo: 这个方法是设置knockout,或者扩展它默认的方法。在上面粘贴的代码中。由于.NET对JSON不兼容,我创建了我自己的绑定句柄(handler)来捕获数据。由于关于.NET和JSON的内容,已经超出本文的范围,在此不再赘述。(如果你有兴趣,请回帖,我将向您详细介绍)下面是绑定数据的样例:
|
1
|
<
inputdata-bind
=
"date: AnyDate"
type
=
"text"
>
|
我们可以在第十二步中的代码中看到使用它的代码:
|
1
|
controlBuilder.Attributes.Add(
"data-bind"
,
"date: "
+ propertyObject);
|
-
ParseJsonDate: 这是一个处理JSON格式转换为JS格式的公共方法。 -
BindUIwithViewModel: 这个方法实现向UI元素绑定viewModel。 -
EvaluateJqueryUI: 这个方法处理jQuery UI相关的操作。这里已经将datepicker处理完毕。 -
RegisterUIEventHandlers: 这个方法实现注册UI元素的事件句柄。目前,元素的单击事件已经被注册为save。Save 方法首先校验页面,阻止默认功能并且触发一个URL指向Person.SaveUrl的AJAX请求。由于这个URL是从服务器端用Url.Action 生成的,因此我们不用考虑域名和虚拟目录。
以上就是关于Person内的所有对象。所有材料都准备完毕,可以进行开发了。也就是说,一旦文件建好了,我们可以用合适的顺序一个一个调用相关方法。下面可以运行一下程序看看效果。
兴趣点
我们建立了一个包含上百个基础UI元素和7个处理大量json数据的富元素页面。这个页面运行的非常流畅,而且没有发现任何展示方面的问题。至于代码,那个页面比我在这里介绍的页面复杂得多。但是,使用的JS模式方面,采用了相似的方式。在我的那个项目中,js脚本被分割到了一系列的文件中。最后,传统ASP.NET开发者对zero处理页面元素从而生成服务器端代码的目的会比较好奇。对我来说,我感觉是为了更好地实现客户端的数据分离操作。让我们专注于处理数据。
转自:http://www.codeproject.com/Articles/657981/ASP-NET-MVC-4-with-Knockout-Js
3979

被折叠的 条评论
为什么被折叠?



