在 上一篇文章中,我们看到了构建用户界面的基本实现。但在现实世界中,我们需要使用更加复杂的方法来开发出更复杂的用户界面。例如,用户不希望点击应用程序中的多个链接才浏览到他们想要的信息,他们希望能够很方便的在一个视图中便取得他们要求的所有信息。
在ASP.NET MVC中,我们仍然可以使用用户控件来创建一个应用程序中的可重用组件—它们被称为部分视图(Partial View)。自从ASP.NET MVC 1.0始就提供了这一支持。在本文中,我们还要使用这种方法,并且结合MVC 2.0中提供的一个新功能—Html.Action,联合起来使用。
一、创建用户界面
在Web表单中,当用户界面变得极其复杂时,开发人员往往通过构建一个巨大的ASPX页面或者把复杂的逻辑分解成单独的用户控件或自定义控件的途径来克服这一复杂性。通常情况下,只有当非常有必要进行重用时才把自定义控件派上用场,而用户控件则被经常使用,这是因为它们易于使用且设计简单的缘故。
在ASP.NET MVC中,上述这些类似功能可以借助于部分视图(相当于用户控件)或自定义HTML助理类(相当于自定义控件)来实现。
借助于MVC 2.0中新引入的一个功能—Html.Action方法,可以使一个行为方法把它的响应直接注入到一个父级视图中。而第二个行为方法返回待注入的一个视图,我们可以把这个方法实现为一个自我包含的实体或类型。局部视图可以建立一个表单以回寄到它的控制器,而主视图可以回寄到另一个不同的控制器。这种实现表单间互动的思路可谓清晰易懂,但在实现这种方法时仍然有一些问题值得关注。下面,让我们研究一个具体的示例。
清单1—使用Html.Action方法的表单示例
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<p>
Main Action
</p>
<% Html.BeginForm(); %>
<div>
Name: <%= Html.TextBox("IndexName")%>
</div>
<div>
Value: <%= Html.TextBox("IndexValue")%>
</div>
<input type="submit" value="save" />
<% Html.EndForm(); %>
<%= Html.Action("Custom", new { title = "Test Title" })%>
</asp:Content>
在上面代码中,我们有一个简单的表单。本表单包含了两个字段。接下来跟着的是调用一个名称为Custom的行为方法(位于相同的控制器内)。这个辅助行为方法以路由值形式调用控制器方法本身,并把控制器的名称(可选)和任何形式的数据传递到该控制器中。
清单2中给出的部分视图已经被注入并成为主视图的一部分。注意,这个视图定义了自己的表单(这是一种使部分视图自包含的方式)。任何到服务器端的回寄都被重定向到这个Custom行为方法中,而不是重定向到主行为方法。这一点特别重要。
清单2—注入部分视图的示例
<p>
Custom Action
</p>
<% Html.BeginForm("Custom", "RenderingActions"); %>
<div>
Name: <%= Html.TextBox("CustomName")%>
</div>
<div>
Value: <%= Html.TextBox("CustomValue")%>
</div>
<input type="submit" value="save" />
<% Html.EndForm(); %>
这个部分视图是自包含式的,除了可以使用与父视图所有相同的数据资源(例如HTTP上下文和路由数据)外,它并不知道父视图的其他内容。现在,我们创建了一个表单,它在浏览器中的样子如图1所示。
▲
图1—示例表单
其中,Main Action部分是通过父视图创建的用户界面,而Custom Action被创建为一个局部视图。处理表单回寄的控制器如清单3所示。
清单3—控制器代码
public class RenderingActionsController : Controller
{
[HttpGet, ChildActionOnly]
public ActionResult Custom()
{
return PartialView();
}
[HttpPost]
public ActionResult Custom(FormCollection form)
{
return PartialView();
}
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(FormCollection form)
{
return View();
}
}
你可能搞不清这究竟是如何工作的,毕竟我们有两个不同的过程在这里起作用:主表单的内容和子表单的内容。我们知道,在MVC中不存在任何ViewState的概念,回寄子视图不会自动保留父视图的数据,理解这一点是非常重要的。当点击相应于子控制器方法的提交按钮时,它回寄自身的内容却并不回寄父视图的内容。因此,针对这种情形应当预先规划一个很好的应对策略。
在我们进一步讨论这一点之前,让我们来看一看把内容寄送回行为方法的基本标记代码。下面是我们的示例中的两个表单对应的标记代码。请注意其中的表单行为方法和控制引用。
清单4—HTML标记代码
<form action="/RenderingActions" method="post">
<div>
Name: <input id="IndexName" name="IndexName" type="text" value="" />
</div>
<div>
Value: <input id="IndexValue" name="IndexValue" type="text" value="" />
</div>
<input type="submit" value="save" />
</form>
<p>
Custom Action
</p>
<form action="/RenderingActions/Custom" method="post">
<div>
Name: <input id="CustomName" name="CustomName" type="text" value="" />
</div>
<div>
Value: <input id="CustomValue" name="CustomValue" type="text" value="" />
</div>
<input type="submit" value="save" />
</form>
上述代码中,我们创建了两个不同的表单(你也可以建立更多的表单),它们将各自进行独立的回寄。另外,上述代码中包含了相对于Web表单框架的一个重要变更,因为以前只能是由一个Web表单包含所有的控件。
二、使用AJAX技术
前面的做法有可能导致为您的控制器添加大量额外的代码。被注入的局部视图执行一个到行为方法Custom的回寄操作,这反过来又导致第二阶段的再处理,并重定向回到原来的视图以显示新的数据。这其中会导致大量的工作。使用AJAX技术则能够节省一些处理时间。清单5取代了清单2中的部分视图,它能够以异步方式寄送表单数据。
清单5—使用AJAX表单的代码
<p>
<div id="customresponse"></div>
<% using (Ajax.BeginForm("AjaxCustom", new AjaxOptions
{ UpdateTargetId = "customresponse" }))
{%>
<div>
Name: <%= Html.TextBox("CustomName")%>
</div>
<div>
Value: <%= Html.TextBox("CustomValue")%>
</div>
<input type="submit" value="save" />
<%}%>
</p>
请注意上面使用的UpdateTargetID属性。此属性用于指定要把响应信息注入到一个特定的HTML元素目标。此更新的目标是在以AJAX模式回寄时调用方法的返回结果对应指定的目标。表单本身在表单的元素内向服务器发出回寄,并在清单6中进行处理。这一点特别值得注意。
清单6—返回一个响应
[HttpPost]
public ActionResult AjaxCustom(FormCollection post)
{
if (Request.IsAjaxRequest())
return Content("Success");
else
return PartialView();
}
当在一个视图中操作多个行为时,表单必须关注是以同步还是以异步方式发出的请求。如果请求是一个Ajax请求,结果的内容将被注入到更新的目标(一个DIV元素)。我们不希望返回部分视图,因为这会把用户界面复制到我们的自定义响应的DIV(更新目标)中。另外,我们并不需要使用更新目标来重新加载现有的用户界面,因为在AJAX请求模式下用户界面从未消失。
另一方面,当不使用AJAX方式时同步请求回寄到服务器端,需要你来处理整个请求。只返回成功的消息是不能按预期那样工作,因为那时用户界面会消失。当请求不使用Ajax回寄到服务器端时,你要自己处理整个的请求,而且为此您将拥有完全的控制权。这意味着,你必须针对每一个同步请求返回整个部分视图用户界面,并且要关注何时可能发生这种情况。处理上述两种情形可能要求您使用客户端JavaScript技术。
在这个例子中,正是发生了上述情况,因为主视图中的提交按钮并没有使用AJAX表单提交其内容,因而引发了到服务器端的一个完整的回寄。由于MVC让我们全面控制相应于每一个请求所呈现的内容,所以我们必须负责返回相同的用户界面信息。
三、使用JQuery
JQuery框架能够与ASP.NET MVC良好地协作。借助于JQuery,我们可以轻松地实现使用AJAX方式把数据以GET和POST寄送到服务器端。ASP.NET MVC框架能够以与ASP.NET MVC的客户端组件同样的方式检测一个AJAX请求。下面,让我们来看一下这样的例子。
清单7—结合JQuery使用HTML.Action的代码
<p>
JQuery Ajax Index
</p>
<% Html.BeginForm(); %>
<div>
Name: <%= Html.TextBox("IndexName")%>
</div>
<div>
Value: <%= Html.TextBox("IndexValue")%>
</div>
<input type="submit" value="save" />
<% Html.EndForm(); %>
<div id="JQueryCustomParent">
<%= Html.Action("JQueryCustomIndex")%>
</div>
请注意上面代码中并没有使用一个DIV元素来包装部分视图(从JQueryCustomIndex返回),这使得我们可以更容易确定要更新的目标区域。
在使用JQuery的时候应当注意几个问题。第一,要确保你替换掉更新区域中的全部内容,所以在我们的自定义DIV范围内的一切内容都必须清除并进行替换。第二,发生AJAX替代时部分视图中的JavaScript代码并不被执行;这意味着,脚本必须驻留在此视图中,并且视图必须要了解要更新的部分(这意味着视图和局部视图之间是紧耦合的,除非你做一些额外的工作来防止这一点)。清单8中提供了执行更新的脚本。
清单8—更新部分视图的代码
<script type='text/javascript'>
function attachToForm(){
$("#JQueryCustomParent").find("form:first").submit(function(e){
e.preventDefault();
$.post($(this).attr("action"),$(this).serialize(),
function(data){
$("#JQueryCustomParent").replaceWith($("<div/>")
.attr("id", "JQueryCustomParent").html(data));
attachToForm();
});
});
}
$(document).ready(function(){
attachToForm();
});
</script>
JQuery在加载时会被关联到我们的目标DIV中的第一个表单中。当提交时,默认的提交执行将被取消,而由jQuery执行回寄。此回寄将使用新的DIV及其内容替换掉我们的目标DIV。当更换内容时,我们失去了对提交处理程序的控制权。之后,这一过程需要重新连接(虽然我们也可能通过使用jQuery中的另一个事件处理方法来纠正这个重新连接问题)。
于是,局部视图提交数据,JQuery阻挡表单数据的提交并执行到服务器端的回寄,并使用从控制器行为方法返回的新内容替换原先的HTML内容,然而所有这些对用户来说都不会有任何异样的感觉。
四、结论
ASP.NET MVC 2中增加了一个Html.Action方法允许多个行为方法出现在同一个视图内,这是一个真正强有力的功能改进。但是,你必须小心注意在多个表单的情况下表单数据是如何回寄到服务器端的。这对开发人员提出了较高的要求。针对这个问题,你可以使用AJAX技术加以补救,你可以选择使用内置的MS AJAX,也可以是JQuery。
【相关文章】: