不带 Web 窗体的 Web 应用程序文档格式.docx
《不带 Web 窗体的 Web 应用程序文档格式.docx》由会员分享,可在线阅读,更多相关《不带 Web 窗体的 Web 应用程序文档格式.docx(18页珍藏版)》请在冰豆网上搜索。
![不带 Web 窗体的 Web 应用程序文档格式.docx](https://file1.bdocx.com/fileroot1/2022-12/30/7e8012ae-02dc-47ba-a116-2144fe1ca28d/7e8012ae-02dc-47ba-a116-2144fe1ca28d1.gif)
但进一步的页面单元测试常常有很大困难。
您必须先启动所有ASP.NET,然后才能在“页面”对象的生命周期内运行该对象。
尽管可以通过发送HTTP请求到服务器或自动化浏览器来测试Web应用程序,但这类测试非常脆弱(更换一个控制ID测试就会中断)、难以设置(您必须以完全相同的方式在每位开发人员的计算机上设置该服务器)并且运行缓慢。
当我开始构建更复杂的Web应用程序时,Web窗体提供的抽象概念(如控件、视图状态和页面生命周期)就开始添乱而不是帮忙了。
我需要花越来越多的时间来配置数据绑定(并编写大量的事件处理程序对其进行正确配置)。
我不得不想办法缩减视图状态的大小以便更快加载我的页面。
Web窗体要求每个URL均存在物理文件,这对于动态站点(例如wiki)非常困难。
而成功编写一个自定义的WebControl是一个非常复杂的过程,需要全面了解页面生命周期和VisualStudio®
设计器。
自从在Microsoft工作开始,我就一直与其他人分享关于各种.NET难题的体验并希望可以解决一些难题。
最近,作为开发人员参加有关模式与实践的Web客户端软件工厂项目(时,我遇到了一个这样的机会。
特别是,模式与实践交付的内容之一就是自动单元测试。
在Web客户端软件工厂中,我们建议使用ModelViewPresenter(MVP)模式构建可测试的Web窗体。
简而言之,MVP并非将您的逻辑放入页面中,而是让您构建自己的页面,页面(View)只需调用单独的对象,即Presenter。
Presenter对象随即执行响应视图上活动必需的任何逻辑,通常通过使用其它对象(Model)访问数据库、执行业务逻辑等。
一旦这些步骤完成后,Presenter会更新视图。
这种方法提供了可测试性,因为表示器从ASP.NET管道中隔离出来;
它与视图通过界面进行通信并可脱离页面独立进行测试。
MVP的这种功能实现有点笨;
您需要单独的视图界面,并且您必须在源代码文件中编写许多事件转发函数。
但如果您想要在Web窗体应用程序中得到可测试的UI,这差不多是最佳途径。
任何改进均需要在基础平台中做出更改。
模型视图控制器模式
幸运的是,ASP.NET团队听取了象我这样的开发人员的意见,并且已经着手开发一种新的Web应用程序框架,该框架与您所熟知并喜爱的Web窗体处于同一层级,但采用一组完全不同的设计目标:
∙使用HTTP和HTML—不隐藏。
∙可测试性贯穿整个框架之内。
∙几乎在每个点均可扩展。
∙对输出进行总体控制。
由于此新框架基于模型视图控制器(MVC)模式,因此其名称为ASP.NETMVC。
MVC模式最初在70年代发明,是Smalltalk技术的一部分。
正如我将在本文中所展示的,它实际上非常适合Web的性质。
MVC将您的UI分为三种不同的对象:
用于接收和处理输入的控制器;
包含您域逻辑的模型;
以及用于生成输出的视图。
在Web环境中,输入为HTTP请求,而请求流程与图1类似。
Figure1
MVC模式请求流程
(单击该图像获得较大视图)
这实际上与Web窗体中的过程完全不同。
在Web窗体模型中,输入进入页面(视图),然后视图负责处理输入并生成输出。
而MVC中这些责任是分开的。
因此,您可能立即会产生以下一种想法:
“嘿,这太好了。
我应该如何使用它?
”或“为什么我要编写这些对象,以前只需要编写一个对象?
”这两个问题都问得很好,最好通过示例来进行解释。
因此,我将使用MVCFramework编写一个小型Web应用程序以说明其优点。
创建控制器
要继续进行,您将需要安装VisualStudio2008并获得MVCFramework的副本。
在撰写本文时,ASP.NET扩展的2007年12月社区技术预览(CTP)中已提供了这些内容(CTP和MVC工具包,其中包括一些非常有用的帮助程序对象。
一旦下载并安装CTP后,您将在“新建项目”对话框中获得名为“ASP.NETMVCWeb应用程序”的新项目类型。
选择“MVCWeb应用程序”项目后,会为您提供一个与常用网站或应用程序稍有不同的解决方案。
该解决方案模板会创建一个带有一些新目录的Web应用程序(如图2中所示)。
特别是Controllers目录包含各种控制器类,而Views目录(及其所有子目录)包含了各种视图。
Figure2
MVC项目结构
我将会编写一个非常简单的控制器,返回URL中传递的名称。
右键单击Controllers文件夹并选择“添加项目”以显示常用的“添加项目”对话框以及一些新增加的内容,包括MVC控制器类和几个MVC视图组件。
在此例中,我将添加一个非常富有想象力、名为HelloController的类:
usingSystem;
usingSystem.Web;
usingSystem.Web.Mvc;
namespaceHelloFromMVC.Controllers
{
publicclassHelloController:
Controller
{
[ControllerAction]
publicvoidIndex()
...
}
}
控制器类比页面简单得多。
实际上,唯一真正必需做的就是从System.Web.Mvc.Controller中衍生并将[ControllerAction]属性置于您的操作方法中。
操作是调用以响应特定URL请求的一种方法。
操作负责执行所需的一切处理,然后呈现一个视图。
我将通过编写一个将名称传递到视图的简单操作着手,如下所示:
[ControllerAction]
publicvoidHiThere(stringid)
ViewData["
Name"
]=id;
RenderView("
HiThere"
);
操作方法会通过ID参数从URL接收该名称(稍后会介绍方法),将其存储在ViewData集合中,然后呈现名为HiThere的视图。
在讨论如何调用此方法,或该视图的显示内容之前,我希望说一说可测试性。
还记得我之前关于测试Web窗体页面类有多难的评论吗?
控制器的测试简单得多。
实际上,控制器可以直接实例化,而调用操作方法无需任何附加的基础结构。
您不需要HTTP上下文,也不需要服务器,只要测试工具即可。
作为示例,我在图3中为此类包括了VisualStudioTeamSystem(VSTS)测试单元。
Figure
3
ControllerUnitTest
namespaceHelloFromMVC.Tests
[TestClass]
publicclassHelloControllerFixture
[TestMethod]
publicvoidHiThereShouldRenderCorrectView()
TestableHelloControllercontroller=new
TestableHelloController();
controller.HiThere("
Chris"
Assert.AreEqual("
controller.Name);
controller.ViewName);
classTestableHelloController:
HelloController
publicstringName;
publicstringViewName;
protectedoverridevoidRenderView(
stringviewName,stringmaster,objectdata)
this.ViewName=viewName;
this.Name=(string)ViewData["
];
下面将进行几项操作。
实际的测试相当简单:
实例化该控制器,使用预期的数据调用该方法,然后检查呈现的视图是否正确。
我通过创建测试专用的子类覆盖RenderView方法进行检查。
这可以缩短实际创建HTML的时间。
我只关心是否将正确的数据发送到视图以及是否呈现了正确的视图。
我不关心此测试视图本身的底层详细信息。
创建视图
当然,最终我必须生成一些HTML,因此,让我们创建该HiThere视图。
要进行此操作,首先,我将在解决方案中的Views文件夹下创建名为Hello的新文件夹。
默认情况下,控制器将在Views\<
控制器前缀>
文件夹(控制器前缀为控制器类的名称去掉"
Controller"
字样)中查找视图。
因此,对于HelloController呈现的视图,它会在Views\Hello中查找。
解决方案的查找结果如图4所示。
Figure4
将视图添加到项目中
视图的HTML如下所示:
<
html>
headrunat="
server"
>
<
title>
HiThere!
/title>
/head>
body>
div>
h1>
Hello,<
%=ViewData["
]%>
/h1>
/div>
/body>
/html>
应注意以下几件事。
没有runat="
标记。
没有form标记。
没有控件声明。
实际上,这看起来更象传统的ASP而不是ASP.NET。
请注意,MVC视图仅负责生成输出,因此其不需要任何Web窗体页面所需的事件处理或复杂控件。
MVCFramework借用了.aspx文件格式作为一种有用的文本模板语言。
如果需要,甚至可以使用源代码,但默认情况下,源代码文件如下所示:
namespaceHelloFromMVC.Views.Hello
publicpartialclassHiThere:
ViewPage
没有页面初始化或加载方法,没有事件处理程序,除了基类声明以外没有任何内容,基类声明为ViewPage而不是Page。
这就是MVC视图所需的一切。
运行该应用程序,导航至http:
//localhost:
端口>
/Hello/HiThere/Chris,您将看到如图5所示的内容。
Figure5
成功的MVC视图
如果您看到的并非如图5所示,而是难以理解的意外情况,请不要惊慌。
如果您将HiThere.aspx文件设置为VisualStudio中的活动文档,则当按F5后,VisualStudio将尝试直接访问.aspx文件。
由于MVC视图要求控制器在显示前运行,因此尝试直接导航至该页面将不起作用。
只需将该URL编辑为与图5中所示的内容相匹配,即可正常工作。
MVCFramework如何知道调用我的操作方法?
该URL甚至没有文件扩展名。
答案是URL路由。
如果您仔细查看global.asax.cs文件,则会看到如图6所示的代码段。
全局RouteTable会存储Route对象的集合。
每个Route说明一个URL窗体以及对其进行何种操作。
默认情况下,会向该表中添加两个路由。
第一个是该方法的内容。
它说明每个URL在服务器名后均由三部分组成,第一部分应为控制器名,第二部分为操作名称,而第三部分为ID参数。
6
RouteTable
publicclassGlobal:
System.Web.HttpApplication
protectedvoidApplication_Start(objectsender,EventArgse)
//ChangeUrl=toUrl="
[controller].mvc/[action]/[id]"
//toenableautomaticsupportonIIS6
RouteTable.Routes.Add(newRoute
Url="
[controller]/[action]/[id]"
Defaults=new{action="
Index"
id=(string)null},
RouteHandler=typeof(MvcRouteHandler)
});
Default.aspx"
Defaults=new{
controller="
Home"
action="
id=(string)null},
Url="
此默认路由是能让我的HiThere方法得以调用的路由。
请记住此URL:
http:
//localhost/Hello/HiThere/Chris?
此路由将Hello与控制器、HiThere与操作以及Chris与ID一一对应。
MVCFramework随即创建HelloController实例,调用HiThere方法,然后将Chris作为ID参数的值传递。
此默认路由为您提供了许多功能,但您也可以添加自己的路由。
例如,我想要一个真正友好的站点,好友们只需输入他们的姓名即可获得个性化的问候。
如果我在路由表的顶部添加以下路由
[id]"
Hello"
},
随后,我只需访问,我的操作仍处于调用状态,而我将会看到熟悉的友好问候。
系统如何知道调用哪个控制器和操作?
答案是Defaults参数。
它利用新的C#3.0匿名类型语法来创建一个伪词典。
Route上的Defaults对象可包含任意附加的信息,对于MVC,它还可以包含一些众所周知的条目:
即控制器和操作。
如果URL中没有指定控制器或操作,则其将使用Defaults中的名称。
这就是为什么即使我在URL中忽略它们,但仍可以将我的请求映射到正确的控制器和操作。
还有一件事需要注意:
还记得我说过“添加到表格的顶部”吗?
如果您将其置于底部,将会出现错误。
路由根据先到先得的原则进行工作。
当处理URL时,路由系统会自上至下浏览表格,并且使用第一个匹配的路由。
在本例中,默认路由"
匹配,因为它们是操作和ID的默认值。
这样,它会继续查找ChrisController,但我没有控制器,因此会出现错误。
稍大的示例
现在,我已经说明了MVCFramework的基础知识,将为您展示一个更大的示例,实现比仅显示字符串更多的功能。
wiki是一种可以在浏览器中进行编辑的网站。
可以轻松地添加或编辑页面。
我使用MVCFramework编写了一个小型的示例wiki。
“编辑此页面”屏幕如图7所示。
Figure7
编辑主页
您可以检查本文的代码下载以查看如何实现底层wiki逻辑。
现在我想重点说明MVCFramework如何使Web上的wiki获取变得简单。
让我们先设计URL结构。
我想要以下各项:
∙/[pagename]显示该名称的页面。
∙/[pagename]?
version=n显示页面的请求版本,其中0=当前版本,1=以前的版本,以此类推。
∙/Edit/[pagename]打开该页的编辑屏幕。
∙/CreateNewVersion/[pagename]是为提交编辑而传入的URL。
让我们从wiki页面的基本显示开始。
我为它创建了一个名为WikiPageController的新类。
接下来,我会添加一个名为ShowPage的操作。
启动的WikiPageController如图8所示。
ShowPage方法相当简单。
WikiSpace和WikiPage类分别表示一组wiki页面和特定的页面(及其修订)。
此操作只需加载模型并调用RenderView。
但此处的"
newWikiPageViewData"
行是什么意思?
8
WikiPageControllerImplementationofShowPage
publicclassWikiPageController:
Controller
ISpaceRepositoryrepository;
publicISpaceRepositoryRepository
get{
if(repository==null)
repository=newFileBasedSpaceRepository(
Request.MapPath("
~/WikiPages"
));
returnrepository;
set{repository=value;
publicvoidShowPage(stringpageName,int?
version)
WikiSpacespace=newWikiSpace(Repository);
WikiPagepage=space.GetPage(pageName);
showpage"
newWikiPageViewData
{
Name=pageName,
Page=page,
Version=version?
?
0
我前面的示例说明了一种将数据从控制器传递到视图的方法:
即ViewData词典。
词典非常方便,但也很危险。
它们几乎包含一切内容,您不能获取内容的任何IntelliSense®
,并且由于ViewData词典属于Dictionary<
string,object>
类型,它将消耗内容,您必须计算所有一切。
当您了解在视图中将需要什么数据后,就可以传递强类型化的ViewData对象。
在我的示例中,我创建了一个简单的对象(WikiPageViewData),如图9中所示。
此对象将wiki页面信息带到视图,同时还携带了一些实用工具方法,执行获取wiki标记的HTML版本这类任务。
9
WikiPageViewDataObject
publicclassWikiPageViewData{
publicstringName{get;
set;
publicWikiPagePage{get;
publicintVersion{get;
publicWikiPageViewData(){
Version=0;
publicstringNewVersionUrl{
returnstring.Format("
/CreateNewVersion/{0}"
Name);
publicstringBody{
get{returnPage.Versions[Version].Body;
publicstringHtmlBody{
get{returnPage.Versions[Version].BodyAsHtml();
publicstringCreator{
get{r