使用 ASP.NET MVC 和 Ext JS 构建以数据为中心的 Web 应用程序
下载代码示例
丰富 Internet 应用程序 (RIA) 将桌面应用程序的可用性与基于 Web 的部署和修订的灵活性结合到了一起。构建 RIA 有两种主要方法。第一种是使用承载执行环境的浏览器插件,如 Flash 插件、Java 插件和 Silverlight 插件。第二种是使用基于 JavaScript 的扩展库,如 Dojo、Ext JS、jQuery、MooTools、Prototype 和 YUI。这两种方法各有利弊。
构建 RIA 通常选择的是 JavaScript 库,因为所有主要浏览器都支持 JavaScript,而无需安装插件或运行时环境。我已经尝试过使用上面提到的另一种库 — Ext JS,而且我发现,对于实现 Web 应用程序,它是一种有趣的选择。它易于实现,文档完善,并且在测试方面与 Selenium 兼容。Ext JS 还提供预定义控件,以简化 Web 应用程序 UI 的创建过程。
遗憾的是,Ext JS 的大部分示例都需要通过服务器端的 PHP、Python 和 Ruby on Rails 代码才能说明。但这并不意味着使用 Microsoft 技术的开发人员不可以利用 Ext JS。虽然将 Ext JS 与 Web 窗体开发集成起来比较困难(这是由于抽象层导致的;为了提供有状态的基于控件的模型,抽象层将封装 Web 的请求-响应特性),但您可以使用 ASP.NET MVC 框架,该框架使您能够在同一应用程序中同时利用 Microsoft .NET Framework 和 Ext JS。
在本文中,我将提供之前没有找到的教程,逐步介绍如何使用可从后端数据库读取并可向后端数据库写入的 ASP.NET MVC 和 Ext JS 来开发实际的 Web 解决方案。
Ext JS 窗体基础知识
要使用 Ext JS,您首先需要从 sencha.com 下载它。(我使用的是 3.2.1 版本,但是您应该获取最新版本。)请注意,免费的开源版 Ext JS 可供开源项目、非营利组织和学习使用。对于其他用途,您需要购买许可证。请参阅 sencha.com/products/license.php 以了解详细信息。
将下载的文件解压缩到您的文件系统的一个目录中。它包含使用 Ext JS 开发 Web 解决方案时所需的所有内容,特别是主文件 ext-all.js。(还有一个调试版本可以帮助您更加轻松地找到错误。)依赖项、文档和示例代码都包含在下载的文件中。
\adapters 和 \resources 是项目必需的文件夹。adapters 文件夹允许将其他库与 Ext JS 一起使用。resources 文件夹包含依赖项,如 CSS 和图像。
要正确使用 Ext JS,您还需要在您的页面中添加三个关键文件引用:
- ext-3.2.1/adapter/ext/ext-base.js
- ext-3.2.1/ext-all.js
- ext-3.2.1/resources/css/ext-all.css
-
ext-base.js 文件包含 Ext JS 的核心功能。ext-all.js 包含小组件定义,ext-all.css 包含小组件的样式表。
让我们先通过在静态 HTML 页面中使用 Ext JS 来介绍一下基础知识。页面的开始部分包含以下几行代码,这几行代码链接着成功开发 Ext JS 解决方案所需的文件(我还在 JavaScript 模块中包含了 Ext JS 下载文件中的一些示例小组件):
- <link rel="stylesheet" type="text/css"
- href="ext-3.2.1/resources/css/ext-all.css" />
- <script type="text/javascript" language="javascript"
- src="ext-3.2.1/adapter/ext/ext-base.js"></script>
- <script type="text/javascript" language="javascript"
- src="ext-3.2.1/ext-all.js"></script>
- <script type="text/javascript" language="javascript"
- src="extjs-example.js"></script>
-
我在文件正文中插入了一个 div 元素,以呈现主 Ext JS 窗体:
<div id="frame">
</div>
extjs-example.js 文件对 Ext JS 应用程序的构建方式进行了深入分析。任何 Ext JS 应用程序的模板均使用 Ext.ns、Ext.BLANK_IMAGE_URL 和 Ext.onReady 语句:
- Ext.
- ns('formextjs.tutorial');
- Ext.BLANK_IMAGE_URL = 'ext-3.2.1/resources/images/default/s.gif';
- formextjs.tutorial.FormTutorial = {
- ...
- }
- Ext.onReady(formextjs.tutorial.FormTutorial.init,
- formextjs.tutorial.FormTutorial);
-
Ext.ns 语句使您能够按照逻辑组织命名空间中的代码,以避免命名冲突和作用域问题。
Ext.BLANK_IMAGE_URL 语句对于呈现小组件很重要。它称为空白区域图片(1x1 像素的透明图像),主要用于生成空白区域和放置图标及分隔符。
Ext.onReady 语句是使用 Ext JS 代码进行定义的第一个方法。完全加载 DOM 后会自动调用此方法,以保证脚本运行时您可能引用的所有 HTML 元素都可用。至于 extjs-example.js,下面就是该脚本:
- formextjs.tutorial.FormTutorial = {
- init: function () {
- this.form = new Ext.FormPanel({
- title: 'Getting started form',
- renderTo: 'frame',
- width: 400,
- url: 'remoteurl',
- defaults: { xtype: 'textfield' },
- bodyStyle: 'padding: 10px',
- html: 'This form is empty!'
- });
- }
- }
-
创建了 Ext.FormPanel 类的实例作为字段的容器。renderTo 属性指向将在其上呈现窗体的 div 元素。defaults 属性指定窗体上组件的默认类型。url 属性指定用于发送窗体请求的 URI。最后,html 属性指定作为默认输出的文本(带有任何 HTML 格式)。
要添加字段,您需要使用 items 属性替换 html 属性:
- items: [ nameTextField, ageNumberField ]
-
首先要添加的两个项是文本字段和数值字段:
var nameTextField = new Ext.form.TextField({
fieldLabel: 'Name',
emptyText: 'Please, enter a name',
name: 'name'
});
var ageNumberField = new Ext.form.NumberField({
fieldLabel: 'Age',
value: '25',
name: 'age'
});
所需的属性是:fieldLabel 属性(用于设置窗体组件附带的说明性消息)和 name 属性(用于设置请求参数的名称)。emptyText 属性定义当字段为空时字段将包含的水印文本。value 属性是控件的默认值。
声明控件的另外一种方法是在进行中声明:
- items: [
- { fieldLabel: 'Name', emptyText: 'Please, enter a name', name: 'name' },
- { xtype: 'numberfield', fieldLabel: 'Age', value: '25', name: 'age' }
- ]
-
如您所见,对于 Name 字段,您不必指定类型,因为它是从窗体的默认属性获得的。
我将在窗体中添加一些附加元素,结果如图 1 所示。
图 1 完成的窗体
到目前为止,您已经使用 Ext JS 创建了一个窗体来从用户那里获取数据。现在,我们来将这些数据发送到服务器。您需要添加一个按钮,以处理提交过程并向用户显示结果,如图 2 所示。
图 2 窗体按钮
- buttons: [{
- text: 'Save',
- handler: function () {
- form.getForm().submit({
- success: function (form, action) {
- Ext.Msg.alert('Success', 'ok');
- },
- failure: function (form, action) {
- Ext.Msg.alert('Failure', action.result.error);
- }
- });
- }
- },
- {
- text: 'Reset',
- handler: function () {
- form.getForm().reset();
- }
- }]
-
buttons 属性使窗体能够管理所有可能执行的操作。每个按钮都有 name 和 handler 属性。handler 属性包含与对按钮执行的操作相关的逻辑。在此例中,有两个按钮,分别名为 Save 和 Reset。Save 按钮处理程序执行窗体上的提交操作,并显示一条指示提交成功或失败的消息。Reset 按钮处理程序重置窗体上的字段值。
在窗体创建过程中,最后一个步骤,同时也是很重要的步骤是验证。为了指定必填字段,我们需要将 allowBlank 属性设置为 false,将 blankText 属性设置为所需的验证失败时显示的错误消息。例如,下面是窗体的 Name 字段:
- { fieldLabel: 'Name', emptyText: 'Please, enter a name', name: 'name', allowBlank: false }
-
如果未在 Name 和 Age 字段中输入任何数据,当您运行应用程序并单击 Save 按钮时,您会收到一条错误消息,并且必填字段下会出现红色下划线。
要自定义针对这些字段的错误消息,在 Ext.onReady 函数下添加下面这行代码:
- Ext.QuickTips.init();
-
现在,当用户将鼠标指针移动到字段上方时,就会显示一个提示框,其中包含用于说明错误的消息。
我设置了几条字段验证规则,如指定允许的最小和最大长度,将字段验证延迟至提交窗体后进行,为 URL、电子邮件地址和其他类型的数据创建验证函数。您可以在代码下载中查看此验证的详细信息。
构建 Web 应用程序
现在,让我们使用 Ext JS 和 ASP.NET MVC 开发一个 Web 解决方案。我使用的是 ASP.NET MVC 2,但是此解决方案应该也适用于 ASP.NET MVC 3。我接下来要解决的情况是在人力资源管理系统中添加一个员工。
Add Employee 用例说明如下:屏幕提示用户为新员工输入有效信息,如员工 ID、全名、地址、年龄、工资和部门。department 字段是一个用于从中选择部门的部门列表。
如您刚才所见,主要实施策略是在客户端创建一个 Ext JS 窗体,然后使用 ASP.NET MVC 处理数据。持久性层将使用 LINQ 来代表业务实体,并将数据永久保存到数据库系统中。后端数据库是 Microsoft SQL Server 2008。
首先打开 Visual Studio 2010,使用 ASP.NET MVC 2 Web 应用程序模板创建一个新项目。
接下来创建数据库架构。对于此示例,架构将包含两个实体:员工和部门。图 3 显示了我是如何创建 Human Resources 数据库和基础表及约束的。
图 3 创建 Human Resources 数据库
- create table department(
- deptno varchar(20) primary key,
- deptname varchar(50) not null,
- location varchar(50)
- );
-
- create unique index undx_department_deptname on department(deptname);
-
- insert into department
- values('HQ-200','Headquarter-NY','New York');
- insert into department
- values('HR-200','Human Resources-NY','New York');
- insert into department
- values('OP-200','Operations-NY','New York');
- insert into department
- values('SL-200','Sales-NY','New York');
- insert into department
- values('HR-300','Human Resources-MD','Maryland');
- insert into department
- values('OP-300','Operations-MD','Maryland');
- insert into department
- values('SL-300','Sales-MD','Maryland');
-
- create table employee(
- empno varchar(20) primary key,
- fullname varchar(50) not null,
- address varchar(120),
- age int,
- salary numeric(8,2) not null,
- deptno varchar(20) not null,
- constraint fk_employee_department_belong_rltn foreign key(deptno)
- references department(deptno)
- );
- create unique index undx_employee_fullname on employee(fullname);
-
现在,让我们使用 LINQ to SQL 定义实体的结构和持久性机制。首先创建一个 EmployeeRepository 类来管理员工表的数据访问逻辑。在本例中,您仅需要实现创建操作:
- public class EmployeeRepository {
- private HumanResourcesDataContext _ctxHumanResources =
- new HumanResourcesDataContext();
-
- public void Create(employee employee) {
- this._ctxHumanResources.employees.InsertOnSubmit(employee);
- this._ctxHumanResources.SubmitChanges();
- }
- }
-
您还需要一个 DepartmentRepository 类来管理部门表的数据访问逻辑。同样,在这个简单的示例中,您仅需要实现读取操作以查找部门列表:
- public class DepartmentRepository {
- private HumanResourcesDataContext _ctxHumanResources =
- new HumanResourcesDataContext();
-
- public IQueryable<department> FindAll() {
- return from dept in this._ctxHumanResources.departments
- orderby dept.deptname
- select dept;
- }
- }
-
现在让我们定义体系结构的另一重要部分:控制器。要定义控制器,请在“解决方案资源管理器”窗口中右键单击 Controllers 文件夹,然后选择“添加”|“控制器”。我使用 HumanResourcesController 作为控制器名称。
Ext JS 表示层
现在让我们回到 Ext JS,使用框架构建应用程序的表示层。对于此解决方案,您仅需要导入 ext-all.js 和 \adapter 及 \resources 文件夹。
转到 Site.Master 页面,在 head 元素中添加对 Ext JS 文件的引用,并添加 <asp:ContentPlaceHolder> 标记元素作为各个页面的自定义 JavaScript 和 CSS 代码的容器,如图 4 所示。
图 4 Site.Master
- <head runat="server">
- <title><asp:ContentPlaceHolder ID="TitleContent"
- runat="server" /></title>
- <link href="http://www.cnblogs.com/Content/Site.css" rel="stylesheet"
- type="text/css" />
-
- <!-- Include the Ext JS framework -->
- <link href="<%= Url.Content("~/Scripts/ext-3.2.1/resources/css/ext-all.css") %>"
- rel="stylesheet" type="text/css" />
- <script type="text/javascript"
- src="<%= Url.Content("~/Scripts/ext-3.2.1/adapter/ext/ext-base.js") %>">
- </script>
- <script type="text/javascript"
- src="<%= Url.Content("~/Scripts/ext-3.2.1/ext-all.js") %>">
- </script>
- <!-- Placeholder for custom JS and CSS and JS files
- for each page -->
- <asp:ContentPlaceHolder ID="Scripts" runat="server" />
- </head>
-
现在让我们添加 MVC 体系结构的其他重要部分:视图。视图将显示窗体,以获取与某位员工相关的数据。转到 HumanResourcesController,右键单击 Index 操作方法,然后选择 Add View。单击 Add View 对话框中的 Add 按钮。
要实现之前在本文中创建的 Ext JS 窗体,您需要在 Scripts 目录中添加一个 JavaScript 文件,并在视图中添加一个对此 JavaScript 文件的引用。然后将该引用添加到 employee_form.js 文件中,并将 div 元素添加到 Index.aspx 视图中(请参见图 5)。
图 5 添加员工窗体
- <%@ Page Title="" Language="C#"
- MasterPageFile="~/Views/Shared/Site.Master"
- Inherits="System.Web.Mvc.ViewPage" %>
-
- <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
- runat="server">
- Index
- </asp:Content>
-
- <asp:Content ID="Content2" ContentPlaceHolderID="MainContent"
- runat="server">
- <h2>Add a New Employee</h2>
- <div id="employeeform"></div>
- </asp:Content>
-
- <asp:Content ID="Content3" ContentPlaceHolderID="Scripts"
- runat="server">
- <script type="text/javascript"
- src="<%= Url.Content("~/Scripts/employee_form.js") %>">
- </script>
- </asp:Content>
-
转到 employee_form.js 文件,添加一些代码来配置 ExtJS 窗体及其基础小组件。第一步是定义 Ext.data.JsonStore 类的一个实例,以获取部门列表:
- var departmentStore = new Ext.data.JsonStore({
- url: 'humanresources/departments',
- root: 'departments',
- fields: ['deptno', 'deptname']
- });
-
url 属性指向 HumanResourceController 控制器中的 departments 操作方法。此方法通过 HTTP POST 动词进行访问。root 属性是部门列表的根元素。fields 属性指定数据字段。现在定义窗体。这些属性均为自描述性属性:
- var form = new Ext.FormPanel({
- title: 'Add Employee Form',
- renderTo: 'employeeform',
- width: 400,
- url: 'humanresources/addemployee',
- defaults: { xtype: 'textfield' },
- bodyStyle: 'padding: 10px',
-
在本例中,url 属性指向 HumanResourceController 控制器中的 AddEmployee 操作方法。此方法也可通过 HTTP POST 动词进行访问。
items 属性提供代表窗体字段的小组件列表(图 6)。此处的默认小组件是文本字段(这在 defaults 属性中指定)。第一个字段是员工编号,该字段为必填字段(由 allowBlank 属性指定)。第二个字段是全名,它也是一个必填文本字段。Address 字段是一个可选的文本区域。Age 字段是一个可选的数值字段。Salary 字段是一个必填的数值字段。最后,部门编号字段是一个是从部门列表中选择的标识符字符串。
图 6 窗体字段小组件
- items: [
- { fieldLabel: 'Employee ID', name: 'empno', allowBlank: false },
- { fieldLabel: 'Fullname', name: 'fullname', allowBlank: false },
- { xtype: 'textarea', fieldLabel: 'Address', name: 'address',
- multiline: true },
- { xtype: 'numberfield', fieldLabel: 'Age', name: 'age' },
- { xtype: 'numberfield', fieldLabel: 'Salary', name: 'salary',
- allowBlank: false },
- { xtype: 'combo', fieldLabel: 'Department', name: 'deptno',
- store: departmentStore, hiddenName: 'deptno',
- displayField: 'deptname', valueField: 'deptno', typeAhead: true,
- mode: 'remote', forceSelection: true, triggerAction: 'all',
- emptyText: 'Please, select a department...', editable: false }
- ],
-
最后,将 buttons 属性定义为处理对窗体执行的操作。这里的配置和图 2 中的配置相似,但文本属性的值为“Add”。
现在 employee_form.js 文件已完成。(到目前为止,我已经介绍了此文件的大部分元素。有关此文件的完整源代码列表,请参见代码下载。)
现在让我们转到 HumanResourceController,并执行相应的操作方法,如图 7 所示。
图 7 HumanResourceController
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- using HumanResources_ExtJS_ASPNETMVC.Models;
-
- namespace HumanResources_ExtJSASPNETMVC.Models.BusinessObjects {
- public class HumanResourcesController : Controller {
- DepartmentRepository _repoDepartment = new DepartmentRepository();
- EmployeeRepository _repoEmployee = new EmployeeRepository();
-
- // GET: /HumanResources/
- public ActionResult Index() {
- return View();
- }
-
- // POST: /HumanResource/Departments
- [HttpPost]
- public ActionResult Departments() {
- var arrDepartment = this._repoDepartment.FindAll();
- var results = (new {
- departments = arrDepartment
- });
- return Json(results);
- }
-
- // POST: /HumanResource/AddEmployee
- [HttpPost]
- public ActionResult AddEmployee(employee employee) {
- string strResponse = String.Empty;
- try {
- this._repoEmployee.Create(employee);
- strResponse = "{success: true}";
- }
- catch {
- strResponse = "{success: false, error: \"An error occurred\"}";
- }
- return Content(strResponse);
- }
- }
- }
-
大功告成!
现在运行解决方案。您将看到如图 8 所示的 Web 页面。在窗体中输入一些数据,然后单击 Add。您会看到一个确认消息框。您还会看到在数据库的 dbo.employee 表中插入的行。
图 8 运行应用程序
创建简单的 RIA 的确就这么简单。根据您想利用的功能,可以使用任何其他常见的 JavaScript 框架并同时使用 ASP.NET MVC 来构建相似的应用程序。您可以轻松地将实体框架替换为数据层,并使用 Windows Azure 存储或 SQL Azure 作为后端数据存储。这些简单的构造块使构建以数据为中心的基本 RIA 变得快速而简单。