• MVC中Controller控制器相关技术


    第6章Controller相关技术
    Controller(控制器)在ASP.NET MVC中负责控制所有客户端与服务器端的交互,并 且负责协调Model与View之间的数椐传递,是ASP.NET MVC整体运作的核心角色,非常重
    6.3	Controller的运行过程
    在ASP.NET MVC中并非所有动作方法都必须回传ActionResult类别或其衍生类别,也可以直接使用.NET内建的基本数据类型当作回传类型(如string 或int等),最后还是都会被ASP.NET MVC自动转换成ContentResult后输出.当然, 你的Action若要声明成void也是可以的,那就代表这个Action不会回传任何数据到客户端。
    MvcHandler从Controller得到ActionResult之后,就会开始运行ActionResult提供的 ExecuteResult方法,并将运行结果响应到客户端,这时Controller的任务就算完成。
    以上是Controller大致的运行过程,不过,Controller在运行时还有一层所谓的动作过滤器(Action Filters)机制,分成以下四种类型。
    •授权过滤器(Authorization Filters)
    •动作过滤器(Action Filters)
    •结果过滤器(Result Filters)
    •例外过滤器(Exception Filters)
    因此,Controller的运行过程还必须考虑到动作过滤器的运行顺序。
    6.3.1	找不到Action时的处理方式
    如ActionInvoker找到对应的 Action,默认会运行System.Web.Mvc.Controller 类別的 HandleUnknownAction 方法,在 System.Web.Mvc.Controller 类别里• Handle- UnknownAction方法默认会响应HTTP 404找不到资源的错误消息
    在ASP.NET MVC项目中,所有Controller默认都足继承于System.Web.Mvc.Controller 类別,由于在System.Web.Mvc.Controller类别中的HandleUnknownAction方法被标注为 “virtual",代表此方法可以被替换(Override),因此,可以在项目的Controller中替换 HandleUnknownAction方法,即可自定义MvcHandler找不到Action时的处理方式,演示 程序如下:
    public class HomeController : Controller
    {
    	public ActionResult Index()
    	{
    		ViewBag.Message = "修改此模板以快速启动你的ASP.NET MVC应用程序";
    		return View();
    	}
    	protected override void HandleUnknownAction(string actionName)
    	{
    		Response.Redirect ("url网址");
    	}
    }
    
    TIPS
    利用HandleUnknownAction可设计许多弹性的Action处理机制,不过,需要注意 一些陷阱,如果是直接参照MSDN上的演示程序的话、会有一些潜在的风险!以下 是MSDN上关于HandleUnknownAction的演示程序: 
    protected override void HandleUnknownAction(string actionName)
    {
    	try
    	{
    		this.View(actionName).ExecuteResult(this.ControllerContext);
    	) 	 
    	catch (InvalidOperationException ieox)
    	{
    		ViewData["error"] = "Unknown Action"	+
    		Server.HtmlEncode(actionName);
    		ViewData["exMessage"]=ieox.Message;
    		this.View ("Error").ExecuteResult(this.ControllerContext);
    	}
    }
     
    
    如果你的Controller中有如下的Action,并套用了 HttpPost属性(Attribute),且同 时有上述的HandleUnknownAction方法定义,那么就很有可能会出现一个细微的安全 漏洞:
    [HttpPost]
    public ActionResult DoWorkO
    {
    	return View();
    )
    由于DoWork()动作方法只有在允许HttpPost的时候才能调用,但是,Handle- UnknownAction又没有判断是否要求HttpPost,可能会在使用HTTP GET的情况下显 示DoWork()动作方法相对应的View。
    
    6.3.2动作名称选定器
    当通过Actionlnvoker选定Controller内的公开方法时,默认会以Reflection的方式取得 Controller中拥有与action路由参数同名的方法(不区分英文人小写),这是默认的行为。如下面的演示程序就很清楚,当RouteValue中的Action是Index的话,默认就会运行Inde()方法:
    public class HomeControHer : Controller
    {
    	/// <summary>
    	/// 要求网址 http://localhost/Home/Index
    	/// </sununary>
    	public ActionResult Index()
    	{
    		return View(); 
    	}
    }
    如果在Action加hActionName属性:(Attribute)并指名为Default,此时,路由参数 action的值就会变成必须是Default才会正确运行Index()这个动作方法,这就是动作名称选定器(Action Name Selector)的用途:
    public class HomeController : Controller
    {
    /// <summary>
    /// 要求网址 http://localhost/Home/Index
    /// </summary>
    [ActionName ("Default")]
    public ActionResult Index
    {
    	return View();
    }
    唯一需要特別注意的趙,如果你在Controller中使用默认的return View();回传 ActionResult,由于你已经在动作方法上套用了 ActionName("Default")属性,所以 ASP.NET MVC会去寻找/Views/Home/Default.cshtml检视页面来运行,而不足 /Views/Home/Index.cshtml
    6.4动作方法选定器
    当通过Actionlnvoker选定Controller内的公开方法时, ASP.NET MVC还有另外一个特 性称为“动作方法选定器(Action Method Selector)",同样可以套用在动作方法上,以 便Actionlnvoker “选定"适当的Action。
    6.4.1	NonAction 属性
    用NonAction属性在Controller里的Action方法上, 即使该Action 方法是“公开方法" •也会吿知Actionlnvoker不要选定这个Action来运行。这个厲性的主要用来保护Controller中的特定方法个要发布到Web上,或是功能尚未开发完成就要进行部署时,暂时不想将此方法删除就可以套用这个属性不要对外公幵。请参考以下演示程序: 
    [NonAction]
    public ActionResult Index()
    {
    	return View();
    }
    将Action方法的public更改成private,也可以达到完全相冋的H的:
    private ActionResult Index()
    {
    	return View();
    }
    6.4.2	HTTP动词限定属性
    HttpGet、HttpPost、HttpDelete、HttpPut、HttpHead、HttpOptions、HttpPatch 属性 (Attributes)都是动作方法选定器的一分了,我们以下列程序为例,若在动作方法 上套用了HttpGet属性,即代表只有当客户端浏览器发送HTTP GET要求时,Actionlnvoker 才会选定到这个Action:
    [HttpPost]
    public ActionResult Index()
    {
    	return View();
    }
    相反的,如果你的动作方法上面都没有套用这些动作限定属性的话,不管客户端浏览器发送任意HTTP动词都会自动选定到对应的Action.
    这些属性常用在需要接收窗体信息的时候,你可以创建两个同名的Action,—个套用HttpGet厲性,以显示窗体HTML,另一个套用HttpPost,以接收窗休输岀的值,演示程序如下:
    [HttpGet]
    public ActionResult Create()
    {
    	return View();
    )
    
    [HttpPost]
    public ActionResult Create(FormCollection c) 
    {
    	UpdateToDB(c);
    	return RedirectToAction ("Index");
    }
    
    
    NOTES
    由于HTML窗体无法输出DELETE这个HTTP动词,如果希望Action能够提供 如同REST协议的方式来处理删除动作,又同时能够利用同一个窗体来使用这个只能允许HttpDelete的激活的话,可以利用Html.HttpMethodOvemde这个HTML辅助方 法来仿真HTTP DELETE的行为,但实际上窗体还是以HTTP POST的方式输出的.
    
    6.5 ActionResult解说
    ActionResult是Action运行后的回传类别,但是当Action回传ActionResult的时候,其 实并不包含这个ActionResult(例如ViewResult)的运行结果,而是包含运行这个 ActionResult时所需的数据,当MvcHandler从Controller取得ActionResult之后才会去运行出ActionResult的结果。我们先来看看ActionResult抽象类的程序代码,在ActionResult 抽象类中仅定义了一个ExecuteResult()方法用来运行结果:
    namespace System.Web.Mvc
    {
    	public abstract class ActionResult
    	{	
    		public abstract void ExecuteResult(ControllerContext context);
    	}
    }
    ASP.NET MVC定义了以F几种ActionResult的衍生类别,如表6-1所示。
    表 6-1 ActionResult 的型别
    类别						Controller辅助方法			用途帮助
    =========================================================================
    ContentResult				Content						回传个用户D定义的文字域性
    EmplyResult					不响应任何信息到客户端
    FileResult												以二进制串流的方式回传一个文裆信息:
    •	FileContentResult		File						直接输入byte[]属性
    •	FilePathResult			File						指定文件路径输出文件属性
    •	FileStreamResult		File						指定Stream对象回传其属性
    HttpStatusCodeResult									冋传自定义的HTTP状态代码和消息 
    •	HttpNotFoundResult		HttpNotFound				回传HTTP404状态代码
    •	HttpUnauthorizedResult	HttpNotFound				回传HTTP 404状态代码
    JavaScriptRcsult			JavaScript					回传的是JavaScript脚本
    JsonResult					Json						将数据转换化成JSON格式回传
    RedirectResult				Redirect					重新导向到指定的URL
    							RedirectPermanent			重新导向到指定的URL带参数
    RedirectToRouteResult		RedirectToAction
    							RedirectToActionPermanent
    							RedirectToRoute				与RedirectResult类似
    							RedirectToRoutePermanent	但足它是重导到一个 Action或Route									
    ViewResultBase											回传一个View页面
    •	ViewResult				View						回传检视页面(View Page)		
    •	PartialViewResull		PartialView					回传部分检视页面(Partial View>				
    
    
    如表6-1所示的“Controller辅助方法"都是在ASP.NET MVC的Controller基类里的辅助方法,这些辅助方法的主要目的是为了方便让你在Controller中回传ActionResult相关类别之用。如下程序代码可用来转址到另一个页面:
    public ActionResult Index()
    {
    	return new RedirectToRouteResult(new RouteValueDictionary (new { action
    	="About" }));
    }
    如果改用Controller辅助方法来编写,就可以改成以下程序代码:
    public ActionResult Index()
    {
    	return RedirectToAction("About");
    }
    实际上来说,大多会使用Controller辅助方法来帮助从Controller中回传正确的 ActionResult, 不过了解这些ASP.NET MVC内述的ActionResult衍生类别也是很重要的,也许在适当的时候可能会用上,以下就来详细介绍这些ASP.NET MVC内建的 ActionResult 衍生类别。
    6.5.1 ViewResult
    ViewResult足在ASP.NET MVC中M常用的ActionResult,用于回传一个标准的检视(View)页面。通过Controller辅助方法能更方便地定义要如何输出检视页面。你吋以指定要输出的View名称、制定该View要套用哪个主版页面(Layout Page)、指定要传入View 的数据模型(Model)等等。以下是几个常用的程序代码演示。
    演示一:回传默认的检视页面。
    如下演示会运行 /Views/Home/About.cshtml检视页面,并将结果输出至客户端:
    public class HomeController : Controller
    {
    	public ActionResult About()
    	(
    		return View();
    	}
    )
    演示二:指定检视页面名称响应。
    如下演示会运行/Views/Home/About2.cshtml检视页面,并将结果输出至客户端:
    public class HomeController : Controller
    {
    	public ActionResult About()
    	{
    		return View ("About2");
    	}
    }
    演示三:指定的检视页面不存在。
    如果指派的检视页面不存在,则程序如下:
    public class HomeController : Controller
    {
    	public ActionResult About{)
    	{
    		return View ("AAA");
    	}
    }
    
    图6-2在Views的对应目录找不到时,所出现的消息
    /Views/Home/AAA.aspx
    /Views/Home/AAA .ascx
    /Views/Shared/AAA.aspx
    /Views/Shared/AAA.ascx
    /Views/Home/AAA.cshtml
    /Views/Home/AAA.vbhtml
    /Views/Shared/AAA.cshtml
    /Views/Shared/AAA.vbhtml
    在ASP.NET MVC中为了找出一个同名的View页面 .尝试搜索了两个不同的路径与四个不同的扩展名,实上这八个不同的路径分别由两种不同的检视引擎(ViewEngine) 所支持, 前四个由WebFormViewEngine负责查找关联视图页面,后四个由RazorViewEngine负责查找关联视图页面。WebFormViewEngine默认支持的检视页面类型为ASP.NET MVC 2以前常用的WebForm页面(*.aspx)与用户控件页而(*.ascx)。而 RazorViewEngine默认支持的检视页面类型为ASP.NET MVC 3以后常用的Razor页面,在Razor页面里可支支持C# 语法(*.cshtml)与 VB.NET语法(*.vbhtml)。
    由此可知,ASP.NET MVC在查找View页面时第一顺序将会以WebFormViewEngine 为主,第二顺序才是RazorViewEngine, 不过从ASP.NET MVC 3开始,全新的Razor语法已经广受ASP.NET MVC幵发人员喜爱,如果你想要调整这两个ViewEngine的搜索顺序,可以在Global.asax文件的Application_Start()方法中添加以下程序代码,即可变换ASP.NET MVC搜索检视页面的顺序,以缩短ASP.NET MVC在搜索View页面的时间。
    protected void Application_Start()
    {
    	AreaRegistration.RegisterAllAreas();
    	WebApiConfig*Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundloTable.Bundles); AuthConfig.RegisterAuth();
    	ViewEngines.Engines.Clear();
    	ViewEngines.Engines.Add(new RazorViewEngine());
    	ViewEngines.Engines.Add(new WebFormViowEngine());
    )
    在搜索目录部分,ASP.NET MVC会到网站目录下Views目录里先搜索第一层目录,默认将会先搜索与Controller冋名的目录,如果找不到相对应的View页页面,就会改为搜索Shared目录,在Views目录下的Shared 目录中通常会放放置共享于多个Controller之 
    间的View页面.
    指定检视页面名称与要套用的主版页面名称后,如下演示将会运行 /Views/Home/Index.cshtml 检视页面,并设置套用/View/Shared/Layout2.cshtml主版页面,将结果输出至客户端。请注意,在套用主版页面时只需要输入文档名即可。
    public ActionResult Index()
    {
    	return View("Index", "_Layout2");
    }
    如下演示,我们在HomeController的Read动作方法里,通过Model取得数据后将数据传至默认的检视页面中,这时ASP.NET MVC会尝试找出/Views/Home/Read.cshtml检 视页面,并传入model这个强型别对象,最后将结果输出至客户端。
    public ActionResult Read{int id)
    {
    	Models.MvcGuestbookContext db = new MvcGuestbookContext();
    	var model = from p in db.Messages where p.Id == id select p; 
    	return View(model); 
    }
    
    6.5.2	PartialViewResult
    
    Partial ViewResult与ViewResult非常类似,但无法为选中的view指派主版页面,如 果想在页面中设计出更好的关注点分离特性,可以将页页的其中一部分独立成另一个动 作(Action),这时就可以利用PartialViewResult取得页面中的部分属性。
    除此之外,当网页前端开发以Ajax为主的M页应用时,也经常会利用 PartialViewResult取得网页的部分厲性,此时搭配PartialViewResult来取得部分属性也非常适合。
    如下演示会运行/Views/Home/Index_Marquee.cshtml检视页面,并将结果输出至客 户端。如果在Views\_ViewStart.cshtml文档里定义了默认装入主版页面,由于通过 PartialViewResult回传的关系,所以ASP.NET MVC在回传页面时并不会套用主版页面。
    public ActionResult Index_Marquee
    {
    	return PartialView();
    }
    6.5.3	EmptyResult
    有作Action不需要回传任何数据,例如,我们想在N站实作联机人数的统计功能, 可以从网页中动态发出一个HTTP要求洽Controller的其屮一个Action,当Controller收到 要求后会在Action里运行加总或记录的动作,之后不回传任何数据,因为这个Action的 主要目的就是统计数据而己。
    遇到这种情况,使用EmptyResult就非常合适,其使用方法如下:
    public ActionResult OnlineUserHit()
    {
    	return new EmptyResult();
    }
    在ASP.NET MVC中也有另一种表达EmptyResult的方式,可以将上述l吾法写成如下 形式:
    public void OnlineUserHit()
    {
    	return;
    }
    6.5.4	ContentResult
    ContentResult可以让你响应任意“文字厲性"的结果,可以任意指定文字属性、属性类型(Content-Type)与文字编码(Encoding)
    如下演示将会响应一段XML文卞,设置响应的Content-Type为text/xml,并指定该
    文字的编码为:Encoding.UTF8
    public ActionResult GetXML() 
    {
    	return Content ("<ROOT><TEXT>123</TEXT></ROOT>","text/xml", System.Text.Encoding.UTF8);
    )
    如果只想单纯响应一串UTF-8编码的HTML字符串,使用第一个参数传入即吋:
    public ActionResult GetHTML()
    {
    	string strHTML = "TEXT"; //省略 HTML 的属性  
    	return Content(strHTML);
    )
    在ASP.NET MVC中也有另一种表达如上例简单的回传类型,那就是直接将回传类型设置成string即可,这是非常简便的撰写方式,ASP.NET MVC会自动判断从Action回传的型别,只要不是ActionResult的衍生型別,就会将回传的数据自动转换成
    ContentResult来输出:
    public string Content ()
    {
    	string strHTML = "......"; //省略 HTML 的属性 
    	return strHTML;
    }
    6.5.5	FileResult
    FileResult可以响应任意文档的属性,包括二进制格式的数据,例如,图片、PDF、 Excel文件或ZIP压缩缩文件等,可以传入byte[]、文档路径、Stream等不同的属性方式,让ASP.NE丁 MVC帮你你将属性回传给客户端。除此之外,还能指定回传时的属性类别 (Content-Type)或指定客户端下载时要显示的文件名等。
    事实上, FileResult 是一个抽象型别,在ASP.NET MVC实作FileResult的型别共有三 个,分別如下。
    •	FilePathResult:响应一个实体文档的属性。
    •	FileContentResult:回应一个 byte[] 属性。
    •	FileStreamResult: 回应一个 Stream 属性。
    通过System.Web.Mvc.Contorller类别中所提供的File辅助方法可以不用记忆这么 
    多,一个File辅助方法就能自动选定不同的FileResult响应。
    如采你想通过Action输出一个放在App Datay牡蛎选的PNG格式文件,可以以参考以下程 序代码:
    public ActionResult GetFile()
    {
    	return File (Server .MapPath ("~/App_Data/UserA/Avatar.png"),"image/png");
    }
    如果希望能够要求浏览器直接下载文件而不是直接在浏览器打开文件,也可以传入要求下载的文件名在第三个参数。例如PDF文档来自于数据库,并希望使用者下载, 可以先取得一个byte[]成Stream数据,并在File辅助方法的第二个参数指定正确的
    Content-Type,最后再指定要下载的文件名即可,演示程序如下:
    public ActionResult GetFile()
    {
    	byte[] fileContent = GetFileByteArrayFromDB();
    	return File (fileContent, "appLication/pdf ", "YourReport.pdf");
    }
    当使用者中A•这个Action的网址,就会得到如图6-4所示下载文件的提示。
    如果要指定的文档名是中文,可以直接输入中文字在第三个参数里,如下演示:
    public ActionResult GetFil()
    {
    	byte[] fileContent = GetFileByteArrayFromDB();
    	return File (fileContent, "appLication/pdf ", "你的报表.pdf");
    }
    不过,由于ASP.NET MVC是依据RFC2231的规范来设H中文的编码,而RFC2231 规范对许多旧版浏览器(IE6与旧版的Safari与Chrome浏览器)来说并不支持这种HTTP Header Value的编码格式,以下是ASP.NET MVC用来创建RFC2231兼容Header Value的 
    代码段:
    private static string CreateRfc2231HeaderValue(string filename)
    {
    	StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8"};
    	byte[] filenameBytes = Encoding.UTF8.GetBytes(filename); 
    	foreach (byte b in filenameBytes)
    	{
    		if (IsByteValidHeaderValueCharacter(b))
    		{
    			builder.Append((char)b);
    		)
    		else
    		{
    			AddByteToStringBuilder(b, builder);
    		}
    	}
    	return builder.ToString();
    }
    所以,为了使中文文件名的文档能够让旧版浏览器顺利下载,在文件名的部分则需要先利Server.UrlPathEncode编码后才能正确下载到中文文件名的文档,否则指定文件名下载的功能将会完全无效,如下程序演示:
     
    
    public ActionResult GetFileO
    {
    	byte[] fileContent = GetFileByteArrayFromDB();
    	return File (fileContent, "text/plain", Server.UrlPathEncode("你的报
    	表• txt"));
    }
    在ASP.NET MVC中要能让旧版IE支持中文文件名下载的方式就只有这种,不过这种方式只对IE浏览器有限,你和其他非IE的“旧版"浏览器(如Firefox、Google Chrome或Safari)在下载文件时一样会变成乱码,因此,这并非是个究美的解决方案.
     
    TIPS
    虽然这个技巧可以适用于IE浏览器,但是笔者在实务开发上还是遇到过问题,如 果使用者直接将文档保存是没有问题的,不过如果用户使用的是旧版的IE浏览器 (IE6、丨E7),那么在下我文件时直接单击“打开"按钮就会遇到一些棘手的问题,,不过这个问题算是旧版IE的问题,基本上是无解的. 所幸该问题到了IE8之后,已经完全修复了。
    不过,实际上来说,我们不太会去考虑非IE后向兼容性问题.如果只考虑IE8以下的版本特别处理过中文文档名的部分,那么应该就非常完美了。演示程序如下:
    public ActionResult GetFile()
    {
    	byte[] fileContent = GetFileByteArrayFromDB();
    	if (Request.Browser.Browser == "IE" &&
    	Convert.Tolnt32(Request.Browser.MajorVersion) < 9)	{
    		//旧版IE使用旧的兼容性作法
    		return File (fileContent, fftext/plainM, Server.UrlPathEncode ("你的报表.txt"});
    	} 
    	else 
    	{
    		//新版浏览器使用RFC2231规范的Header Value作法
    		return File (fileContent, "text/plain", "你的报表.txt");
    	}
    }
    6.5.6	JavaScriptResult
    JavaScriptResult的用途是响应JavaScript程序代码给浏览器,通过Ajax的程序开发, 你可以利用JavaScriptResult来响应适 的JavaScript程序代码让浏览器动态运行.因Ajax功能属View的一环,因此更多关于ASP.NET MVC的Ajax的功能将在“View数据呈现相关技术"进行介绍。
    其实JavaScriptResult的功能与ContentResult差不多,主要的差别在于默认的 Content-Type不一样而已,JavaScriptResult默认的Content-Type 为application/x-javascript。 如下演示即响应alert('ok')至客户端:
    public ActionResult JavaScript()
    {
    	return JavaScript("alert('ok')");
    }
    如果在View中,利用Ajax辅助方法编写以下程序,就会动态调用JavaScript这个Action,并直接在浏览器中运行结果,过程中并不会换页:
    @Ajax. ActionLink ("Run JavaScript","JavaScript",new AjaxOptions ())
    诸注总,如果你要在ASP.NET MVC 4默认网络项目模板中运行以下这段演示.记得要在主版页面(_Layout.cshtml)的<head>标签内装入正确JavaScript函数库才能正常运行@Ajax.ActionLink辅助方法,代码段如下:
    @Scripts.Render ("~/bundles/jquery")
    @Scripts.Render ("~/bundles/jqueryval")
    
    6.5.7	JsonResult
    Json是Web在实际Ajax应用程序时经常使用的一种传输数据格式,JsonResult吋自动将任意对象数椐串行化成JSON格式回传,JsonResult默认的ContentType为appication/json, 对某些JavaScript Frarework这是必需的,例如,jQuery
    JsonResult是使用JavaScriptSeriali成JSON串行化操作,但如果你的对象无法串行化,这个转换的过程将会发生意外。 
    在使用JsonResult时必须特別注总,从ASP.NFTMVC2.0开始,为广避免JSON Hijacking的攻击,ASP.NET MVC开发端对基于安全考虑,在默认的情况下,任何以 JsonResult回 传的要求都不允许HTTPGET取得任何JSON信息, 如下演示会响应一个JSON格式的数据:
    public ActionResult JSON()
    {
    	return Json(new { 
    		id = 1,
    		name = "Will",
    		CreatedOn = DateTime.Now
    		});
    }
    
    如果你是用HTTP POST方法取得该属性,将会得到以下结果:
    {"id":1,"name":"Will","CreatedOn":"/Date(1350116309992}/"}
    如果直接在浏览器输入网址(即以HTTP GET取得属性),将会出现错误信息
    
    我们经常使用jQuery动态取得JSON数椐,在jQuery中有一个常用的$.getJSON就是以GET方法动态取得JSON数据;,如果你的JsonResult没有特別设将会导致无法正常取得JSON信息。
    虽然在jQuery中没有内置$.postJSON方法,但却非常容易实现。以下是jQuery.post 官网网站提供的演示.通过这个即可新增$.postJSON方法,使用方法和$.getJSON 一模一样:
    $.postJS0N = function(url,data,callback) {
    	$.post(url, data, callback, "json"); 
    }
    
    养成安全的幵发习惯非常重要,建议尽量避免使用HTTP GET取得JSON数据,可是只使用HTTP POST取得JSON也存在一个问题,那就是从服务器端取回的数据无法被浏览器缓存,如果你的信息敏感度不高且想操作缓存的话,可能还是需要让JsonResult以 对HTTP GET要求进行响应,解决方法就迠矜JSON辅助方法再加上一个
    JsonRequestBehavior枚举参数,这样就可以通过GET方法取得JSON属性了:
    public ActionResult JSON()
    {
    	return Json(new
    	{
    		id = 1,
    		name = "Will",
    		CreatedOn = DateTime.Now
    	},
    	JsonRequestBehavior.AllowGet);
    }
    
    6.5.8	RedirectResult
    RedirectResult的主要用途是运行重新导向到其他网址。在RedirectResult的内部,基本还是以Response.Redirect力法响应HTTP 302暂时导向。
    以下演示就是使用RedirectResult将结果转至/Home/Index页面:
    public ActionResult Redirect()
    {
    	return Redirect("/Home/NewIndex");
    }
    在ASP.NET MVC 3的版木之后,System.Web.Mvc.Controller类别里还内建了一个RedirectPermanent辅助方法,可让Action响应HTTP 301永久导向。使用HTTP 301永久导向可以提高SEO效果,可保保留原来页面网址的网页排名(Ranking)记录,并自动迁移 到转向的下一页,这对于网站改版导致网站部分页面的网址发生变更时非常实用。如 下演示:
    public ActionResult Redirect()
    {
    	return RedirectPermanent {"/Home/NewIndex");
    }
    
    6.5.9	RedirectToRoute
    RedirectToRoute的行为与RedirectResult类似,不过,它会替运算所有现有的网址路由值(RouteValue),并比对网址路由表(RouteTable)中的每条规则,如“4.3 网址路由 如何在ASP.NET MVC中生成网址"一节所提到的方式一样,这将有助于生成ASP.NET MVC的网址。
    Controller类别中朽四个与RedirectToRoute有关的辅助方法。
    •	RcdirectToAction
    •	RedirectToActionPermanent
    •	RedirectToRoute
    •	RedirectToRoutePermanent
    RedirectToAction与RedirectToActionPermanent是一个比较简易的版本,直接传入 Action名称即可设置浏览器转向至该Action的网址,也可以传入新增的RouteValue值, 其演示如下。
    •	转址到同Controller的另一个Action:
    public ActionResult RedirectToActionSample()
    { 
    	return RedirectToAction("SamplePage");
    }
    •	转址到指定Controller的特定Action并采用HTTP 301永久转址:
    public ActionResult RedirectToActionSample{)
    {
    	return RedirectToActionPermanent("List","Member");
    }
    public ActionResult RedirectToActionSample()
    {
    	return RedirectToAction("List", "Member", new { page = 3 });
    }
    
    RedirectToRoute与RedirectToRoutePermanent 则是较为高级的版本,可利用在 Global.asax中定义的网址路由表(RouteTable)来指定不同的转向网址。以下时几个常见的使用演示。
    •	转址到冋Contro丨ler的V)—个Action: 
    public ActionResult RedirectToRouteSample()
    {
    	return RedirectToRoute(new { action = "SamplePage" });
    }
    •	转址到指纪Controller的特定Action:
    public ActionResult RedirectToRouteSample()
    {
    	return RedirectToRoute (new { controller = "Member",action =
    	"List" });
    }
    •	较址到 MemberComroUer 的 List Action,并且加上 page 这个 RouteValue public ActionResult RedirectToRouteSample()
    {
    	return RedirectToRoute (new { controller = "Member", action = "List", page = 3 });
    }
    如果转址到指定的网址路由表定义的网址格式,我们先假设App_StartRouteConfig.cs中的RegisterRoutes方法定义的网址路由表如下:
    public static void RegisterRoutes(RouteCollection routes)
    {
    	routes.IqnoreRoute("{resource}.axd/{*pathInfo}");
    	routes.MapRoute(
    		name:	"MessageHome",
    		url: "MsgHome",
    		defaults: new { controller = "Message", action = "Index" }
    	);
    	routes.MapRoute(
    		name: "Default"
    		url: "{controller}/{action}/{id}",
    		defaults: new { controller = "Home",action = "Index’,id = UrlParaineter .Optional )
    	);
    }
    如果需要设置转址到MessageHome这个路凼的话,可以在使用RedirectToRoute辅助方法时传入路由名称(Route Name),演示如下:
    public ActionResult RedirectToRouteSample() 
    {
    	return RedirectToRoute("MessageHome");
    }
    
    6.5.10	HttpStatusCodeResult
    HttpStatusCodeRcsult的主要用途让ASP.NET MVC回传特定的HTTP状态代码消息给客户端。对于一些特殊的HTTP响应,吋利用HttpStatusCodeResult帮助我们响应适当的状态代码。
    HTTP状态代码是从服务器端响应(HTTP Response)的状态并大致分为五种。
    •	1xx:参考信息(Infomiational)。
    •	2xx:成功(OK),一般最常见的HTTP状态代码如200代表OK,也就是网页正常响应的意思,201代表Created服务器端己经成功创建资源。
    •	3xx:重新导向(Redirection),刚刚看过的302代表Found,意即査找这个资源,但暂时移到另一个URL.而301则代表Moved Permanently,M即URL 已经发生永久改变,客户端必须转向到另一个URL, 且不用保留原来URL的记录。
    •	4xx:客户端错误(ClientError),这里最常见的就是404NotFound,代表找不到网页,还有401 Unauthorized,代表报绝访问,也都是常见的客户端错误。
    •	5xx:服务器错误(Server Error),当服务器发生错误时会响应5xx的状态代码, 而500 Internal Server Error属于内部服务器错误, 也是常见的HTTP状态代码。
    
    NOTES
    完整的状态代码定义可以参考 RFC 2616 Hypertext Transfer Protocol — HTTP/1.1 的10 Status Code Definitions素节,里面有完整且详尽的讲解,网址如下: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    如果想要响应201Created状态代码,可以参考以下演示:
    [HttpPost]
    public ActionResult Create(FormCollection form)
    {
    	// TODO:依据客户端窗体输入的数据在数据库中创建一条新记录 
    	return new HttpStatusCodeResult (201,"数据已经被成功创建");
    }
    以下演示与上面这个演示程序运行的结果相冋.但推荐使用以下这个演示来撰写自定义的HTTP状态代码响应,以免设置了一个非标准的HTTP状态代码,如下演示: 
    [HttpPost]
    public ActionResult Create(FormCollection form)
    (	
    	// TODO:依据客户端窗体输入的数据在数据库中创建一条新记录
    	return new HttpStatusCodeResult(System.Net.HttpStatusCode.Created, "数据已被成功创建");
    }
    
    6.5.11	HttpNotFoundResult
    HttpNotFoundResult专门用来响应HTTP 404找不到网页的错误,在
    System.Web.Mvc.Controller类别中内建了一个HttpNotFound辅助方法,可以方便回传
    HttpNotFoundResult类别的ActionResult结果,程序演示如下:
    public ActionResult Get(int id)
    {
    	var data = GetDataFromDB(id);
    	if (data == null) {
    		return HttpNotFound();
    	} else {
    		return View(data);
    	}
    }
    
    6.5.12	HttpUnauthorizedResult
    HttpUnauthorizedResult专门用来响应HTTP 401拒绝访问的错误,例如,你可以在Action里做出一些额外的权限检査,如果查出客户端用户并没有特定数据的访问权限.即可利用HttpUnauthorizedResult响应"拒绝访问"的HTTP状态代码,演示如下:
    public ActionResult Get(int id)
    {
    	if (CheckPermission(User.Identity.Name))
    	{
    		var data = GetDataFromDB(id);
    		if (data == null)
    		{
    			return HttpNotFound();
    		}
    		else
    		{
    			return View(data);
    		}
    	}
    	else
    	{
    		return new HttpUnauthorizedResult();
    	}
    )
    
    
    6.6 ViewData、ViewBag 与TempData 概述
    控制器(Controller)负责处理浏览器来的所有要求,并决定响应什么属性给浏览器, 除了这件事以外,Controller还负责协调Model和View之间的数椐传递,因此,Controller 必须在取得数椐以后将数据传洽View来取用,并由View决定如何呈现这些数据。。
    6.6.1 ViewData
    当你使用ViewResult来运行结果时,可以在Action里而利用ControllerBase类别中的 ViewData属性来保存数据,以便这些数据吋以传递给View使用.
    6.6.2	ViewData.Model
    在ASP.NET MVC的View相关技术里,可以将特定检视页面声明为某种型別,让整个检视页面参考着特定型别,并且让Controller传来的Model数椐直接自动转增为View所声明的型别,而这个从Controller传来的Model数据就是通过ViewData.Model传过来的。
    以下程序代码演示可以将数椐通过ViewData.Model传递给View的Model使川:
    pxiblic ActionResult Index ()
    {
    	var data = GetDataFromDB();
    	ViewData.Model = data; 
    	return View();
    }
    上述这段程序代码与以下这段程序代码的结果是完全一样的,都是将 ViewData.Model传给View页面使用:
    public ActionResult Index()
    {
    	var data = GetDataFromDB(); 
    	return View(data);
    }
    
    6.6.3	ViewBag
    ViewData["Message"] = "更改此模板即可幵始着手进行您的ASP.NET MVC应用程。";
    换成ViewBag的写法则如下: 
    ViewBag.Message = "更改此模板即可开始着手进行您的ASP.NET MVC应用程序。";
    整体来说,每写一次要传给View用的变量大约可以省下四个字符,也许可以让你输 入的时间少一点点吧。
    NOTES
    dynamic 型别是.NET Framework 4.0 的新功能。
    
    6.6.4	TempData
    TempDala的数据结构与 ViewData —样都都是字典类型,但TempData的是TempDataDictionary.
    不过,TempData还是有那么一点不同的地方,在于它的内部是使用Session来保存信息,更有趣的是它的名称与特性。“Temp"是暂存的意思,但是保存TempData中的信息会暂存多久呢?答案是:一次网页要求。
    接下米,我们用以下演示来解释“一次网页要求"的定义,在窗体数据送出到以下 Action保存,如果发生数据库新增失败的消息,我们会希望这次送出的数据可以保留至下一页,此时,就会将这个只希望出现一次的消息保存到TempData,并在下一页进行取用。
    如下程序在更新数据库时发生失败后,会先将这次收到的Message数据保存到
    TempData["PostedMessagen]变量里,然后回到Create这个Action:
    [HttpPost]
    public ActionRewult Create(Message msg)
    {
    	if (!UpdateMessageToDB(msg))
    	{
    		TempData["PostedMessage"] = msg;
    		return RedirectToAction("Create");
    	}
    	return RedirectToAction("Index"); 
    }
    此时,重新回到Create动作,数据从TempData["PostedMessage"]再次读取,并再次传递到Create检视页面,当这次ASP.NET MVC生命周期退山的前一刻,由于ASP.NET MVC会记录TempData["PostedMessage"]已经被读取过,因此,在这次HTTP耍求退出前就会将TempData["PostedMessage"]刪除:
    [HttpGet]
    public ActionResult Created)
    {
    	string data = TempData["PostedMessage"] as Message; 
    	return View(data);
    }
    
    TIPS
    一般来说,在Action用到TempData来保存数据时,通常都会使用RedirectResult 或 RedirectToRouteResult 来当成 Action 的回传型别(可使用Redirect,RedirectToAction 或RedirectToRoute辅助方法),如果你的Action不是回传RedirectResult或 RedirectToRouteResult型别的话,很可能就会导致TempData提前消失的情况!
    从 ASP.NET MVC 2.0 之后的版本、只有在使用 RedirectResult 或 RedirectTo- RouteResult当成ActionResult型别时,才会强制保留TempData不被清除,除此之外, 只要有取用TempData的健值,默认就会在当次网页要求就被清除。但是,如果你只单纯设置了TempData的值,并没有读取行为的话,TempData还是会被保留到下一次取用.
    
    6.7模型绑定
    在ASP.NET MVC中足通过模型绑定(Modd Binding}达到解析客户端传来的数据, 而解析工作全部由DefaultModelBinder类别处埋,若需要自定义ModeBinder的行为,则可自行撰写有操作IModelBinder界面的类別即可,但在大部分的情况下, DefaultModelBinder类別就可以处理掉95%以上的信息型态,除非你有特殊的用途。
    
    6.7.1简单模型绑定
    我们用一个简单的例子来描述"简单模型绑定"的过程,请先参考以下动作方法的程序代码,Action名称为TestForm.它会通过简单模型绑定取得从客户端窗体传来的Username参数,最后会将该参数传入ViewData.Mode让View使用: 
    public ActionResult TestForm(string Username)
    {
    	ViewData.Model = Username;
    	return View(); 
    }
    以下是这个Action相对应的View检视页面,这里只足一个非常简单的HTML窗体,并且在窗体内有一个Usemame字段,还有如果从从Action有传入ViewData.Model信总的话, 也会在这个View里通过@Model显示在界面上:
    <form method="post">
    	<p>
    	使用者名称
    		<input type="text",name="Username" />
    	</p>
    	<p>
    	您输入的使用者名称为:@Model
    	</p>
    	<input type="submit" />
    </fomm>
    
    6.7.2使用FormCollection取得窗体信息
    除了通过简单模型绑定取得窗体传来的信息外,还可以通过FormConection 一次取得整份窗休传来的信息。如下程序演示,只要设置一个FormCollection类别的参数, 就以取得所有从窗休传来的信息,这种用法如同使用以前的Request.Form—样,不过,在ASP.NET MVC 里还是尽量不要使用Request.Form来取得窗体信息。
    public ActionResult TestForm(FormCollection form)
    {
    	ViewData.Model = form["Username"]; 
    	return View();
    }
    
    6.7.3复杂模型绑定
    在ASP.NET MVC中,可以通过DefaultModelBinder将窗体信息映射到非常复杂 的.NET型別,称之为“复杂模型" ,或简称"模型",该模型可能是一个List<T>或一个含有多个属性的自定义类型.
    
    public class UserForm
    {
    	public string Username { get; set; } 
    	public string Password { get; set; } 
    	public string Name { get; set; }
    }
    
    public ActionResult TestForm(UserForm form)
    {
    	ViewData.Model = form.Username;
    	return View();
    }
    通过这种方式做模型绑定还有个好处,那就是一样可以利MJVisual Studio 2012的 imellisense快捷提示功能,保助我们快速完成厲性名称的输入,如1训-丨9所示。
    
    再举另个例子解释复杂模型绑定。假设窗体中有四个字段,分别为Type、Name、 Email 和 body(请参考input 卷标的 name 厲性):
    <form method="post">
    	Type
    	<input type="radio", name="Type" value="1" checked="checked" /> 
    	Typel
    	<input type="radio", name="Type" value="2" checked="checked" /> 
    	Type2
    	<br />
    	Name
    	<input id="Name",name="Name" typ="text" value="" />
    	<br />
    	Email
    	<input id="Email",name="Email" typ="text" value="" />
    	<br />
    	Body
    	<textarea cols="20" id="Body" name="Body" rows="2"></textarea> 
    	<br />
    	<input type="submit" />
    </form>
    而你的数据模型与Action定义如下:
    public class GuestbookForm
    {	
    	public int Type { get; set; }
    	public string Name { get; set; }
    	public string Email { get; set; } 
    	public string Body { get; set; }
    }
    
    public ActionResult TestForm(GuestbookForm gbook)
    {
    	return View();
    }
    当客户端送出窗休到Save动作,ASP.NET MVC的DefaultModelBinder会很神奇地自动将字段信息映射到Action的gbook参数中,如表6-2所示。
    表6-2 HTML窗体元素名称与复杂模型的映射表
    窗体名称	数据模型属性
    Type		gbook.Type
    Name		gbook.Name
    Email		gbook.Email
    Body		gbook.Body
    从客户端传来的窗体信息,通过DefaultModelBinder自动创建一个GuestbookForm对
    象,并且将窗休的字段通过.NET的Reflection机制一一将字段设置到该对象的同名属性中,像Type属性为int型别,它也会将字符串形态的信息转型为int。当然,如果转型发生失败,例如客户端窗体传入的Type字段不是数值格式,那么,自动绑定过程会发生异常。
    
    6.7.4多个复杂模型绑定
    下面示范一个复杂的例了。如果你在一个窗体内要送出两个复杂模型的数据到 Action,也就是你希望在一个脔体内一次送出两条数据到Action里,就可以参考以下例子进行开发,假设窗体的HTML如下:
    <form action="/Home/ConiplexModelBinding" merhod="post">
    	<fieldset>
    		<legend>Forml</legend>
    		Type
    		<input type="radio" name="forml.Type" value="1" checked="checked" /> Typel
    		<input type="radio" name="forml.Type" value="2’" checked="checked" /> Type2
    		<br />
    		Name
    		<input id="Name" name="£orml.Name" type="text"  value="" /> <br /> 
    		Email
    		<input id="Name" name="£orml.Email" type="text"  value="" /> <br />
    		Body
    		<textarea cols="20" id="Body" name="£orml.Body" rows="2"></textarea> 
    		<br />
    	</fieldset>
    	<fieldset>
    	<legend>Form2</legend>
    		Type
    		<input type="radio" name="form2.Type" value="1" checked="checked" /> Typel
    		<input type="radio" name="form2.Type" value="2’" checked="checked" /> Type2
    		<br />
    		Name
    		<input id="Name" name="form2.Name" type="text"  value="" /> <br /> 
    		Email
    		<input id="Name" name="form2.Email" type="text"  value="" /> <br />
    		Body
    		<textarea cols="20" id="Textarea1" name="form2.Body" rows="2"></textarea> 
    		<br />
    	</fieldset>
    	<input type="submit" />
    </form>
    访注意!上述HTML只有一个<form>窗体,但是窗体内却有两组字段
    
    public ActionResult ComplexModelBinding(GuestbookForm form1, GuestbookForm form2)
    {
    	InsertlntoDB(forml);
    	InsertlntoDB(form2);
    	return Redirect("/");
    }
    你注意到ASP.NET MVC神奇的地方吗? DefaultModelBinder通过巧妙的名称映射,让窗体传来信息一一映射到Action的两个复杂模型参数中,如表6-3所小。
    表6-3 HTML窗体元素名称与复杂模型的映射表
    窗体名称	复杂横型
    forml.Type	form1.Type
    form1.Name	form1.Name
    form1.Email	form1.Email
    form1.Body	form1.Body
    form2.Type	form2.Type
    form2.Name	form2.Name
    form2.Email	form2.Email
    form2.Body	form2.Body
    这就是ASP.NET MVC微妙的特性,也是我们在第2章“创建正确的开发观念"中提到的"以习惯替换配置"观念,从此不必撰写复杂的程序代码做到此映射,只需理解ASP.NET MVC是这样帮你做好模型绑定,当你习惯了这样的绑定方式,就不需要再思考窗体到底是如何转换成复杂型別的,省下这些时间,用来开发更高阶的应用程序, 而不用担心这些细微末节的琐事。
    
    
    6.7.5判断模型绑定的验证结果
    public class GuestbookForm 
    {
    	[Required] 
    	public int Type { get; set; } 
    	[Required]
    	public string Name { get; set; } 
    	[Required]
    	public string Email { get; set; } 
    	[Required]
    	public string Body { get; set; }
    }
    public ActionResult TestForm(GuestbookForm gbook)
    {
    	if (!ModelState.IsValid)
    	{
    		//己验证出无效的模型帮定,有某些字段不符合格式要求
    		return View();
    	}
    
    	//验证成功,此时可以将信息写入数据库
    	//InsertlntoDB(gbook);
    	return Redirect("/");
    }
    TIPS
    通过ASP.NETMVC自动模型绑定请务必在动作(Action)里验证ModelState.IsValid 属性,否则那些验证失败的数据模型可能还会被你新增到数据库中。
    
    6.7.6模型绑定验证失败的错误详细信息
    除了可以在Action中验证模型绑走的验证状态外,
    若要取得在模型绑定的过程中总共有多少属性会被绑定,可以通过以下程序取得
    ModelState.Count
    若要取得特定属性在绑定过程中是否初选出现错误,可用以下程序取得:
    if(ModelState["Email"].Errors.Count > 0) 
    {
    }
    
    荇要取得特定属性在绑定过程中出现的第一个错误,以及其错误消息或Exception 对象,可用以下程序取得:
    if (ModelState["Email"].Errors.Count > 0)	{
    	ModelError err = ModelState["Email"].Erros[0]; 
    	var errMsg = err.ErrorMessage; 
    	var errExp = err.Exception;
    }
    除了可以取得模型绑定过程中内建的验证失败信息外,还可以自行增加模型绑定验证失败的信息,也可让View中针对特定字段显示适当的错误消息,不过,这必须搭配View里使用HTML辅助方法显示表单域时才会更加实用。
    public ActionResult TestForm(GuestbookForm gbook)
    {
    	if (!ModelState.IsValid)
    	{
    		//已验证出无效的模型绑定,有某些字段不符合格式要求
    		if (gbook.Email == null)
    		{ 
    			ModelState.AddModelError("Email",""请输入Email字段");
    		}
    		return View();
    	}
    	//验证成功,此时可以将信息写入数据库 
    	//InsertlntoDB(forml);
    	return Redirect("/");
    }
    6.7.7清空模型绑定状态
    在Action里除了得到这些模型绑定的洋细信息外,ModelState对象里的信息也一样会传送到View里,如果希望模型绑定状态(ModdState)不要传送到View中:,还可以将模型绑定的所有状态清空,让View页面上的强类型信息不受模型绑定状态的影响,演示 程序如下:
    public ActionResult TestForm(GuestbookForm gbook)
    {
    	if (!ModelState.IsValid)
    	{
    		//已验证出无效的模型绑定,有某些字段不符合格式要求
    		//淸空模型绑定状态 
    		ModelState.Clear();
    		return View();
    	}
    	//验证成功,此时可以将信息写入数据库
    	//InsertlntoDB(forml);
    	return Redirect("/");
    }
    
    6.7.8使用Bind属性限制可被更新的数据模型属性
    复杂模型绑定的验证技巧在实际上经常使用也非常方便,但有一个很明显的限制, 那就是模型在做绑定的时候,是在Action运行时就完成了,而且不管Model有多少字段, 只要客户端有窗体过来就会自动绑定,看起来方便,但实际上是安全风险的。
    假设你的数椐模型有10个属性,但客户端窗体只有5个字段,通过自动模型绑定机制会绑定到几个字段的数椐呢?答案是:可能是10个!
    因为客户端的表单域非常容容易被窜改,如果黑客企阁从窗体塞入一些额外的表单域,只要猜到正确的属性名称,就可以通ASP.NFT MVC的模型绑定功能自动将数椐绑定到特定对象的同名属性里.
    举个实际例子:
    public class Member
    {	
    	public int Id { get; set; }
    	public string Username { get; set; } 
    	public string Password { get; set; } 
    	public DateTime? LastLoginTime { get; set; }
    }
    而你的客户端窗体上只有让用户输入Usemame与Password而已,所以当你使用模型绑定的方式传入Member信息后,会预期LastLoginTime字段应该不会绑定到任何信息,而且该字段传入之后的同名厲性值应该为null对。程序代码如下:
    [HttpPoat]
    public ActionResult UpdateProfile(Member member)
    {
    	// TODO:更新数据库中的Member信息
    	return View();
    }
    但如果黑客这时窜改了客户端窗体,多塞一个LastLoginTime字段上去,并设置任意时间,那么,你数据库中的这条信息, 其LastLoginTime字段可能就会被用户任意穿该,如此一来,ASP.NET MVC程序就会有风险,因此不得不小心。
    此时,可通过ASP.NET MVC内建的Bind厲性(Attribute)并套用在该数椐模型的参数上,就可以明确声明有哪些字段可以被自动绑定进来,或是哪些字段该被排除在自动绑定的名单外。以下演示绑定时要排除LastLoginTime字段信息:
    [HttpPost]
    public ActionResult UpdateProfile(
    [Bind(Exclude = "LastLoginTime")]Member member)
    {
    	//更新数据库中的Member信息
    	return View();
    }
    如果你想明确指明“只有"哪些字段需要绑定,可以使用include具名参数,程序如下:
    [HttpPost]
    public ActionResult UpdateProfile(
    [Bind(Include = "Password")] Member member)
    {
    	//更新数据库中的Member信息 
    	return View();
    }
    除此之外,如果不希望在某个Action的参数都套用Bind属性的话也可以套用在数据模型声明定义的地方,这样一来.整个项目的模型都不需要额外声明了,程序如下:
    using System;
    using System.ComponentModel.DataArmotations;
    namespace MvcApplicationl.Models
    {
    	[Bind(Include=nName ,Email,Body11)] 
    	public class GuestbookForm
    	{
    		public int ID { get; set; }
    		[Required]
    		public string Name { get; set; }
    		[DataType(DataType.EmailAddress)]
    		[RegularExpression(@"^[a—z0-9._%+-]+@[a-z0-9.-]+.([a-z]{2}|c om|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs| museum)$")] 
    		public string Email { get; set; }
    		[Required]
    		public string Body { get; set; } 
    	}
    }
    
     
    6.7.9 使用 UpdateModel 与 TryUpdateModel
    设置Bind属性可以限制绑定的字段,但是模型验证却却不会手软,这个验证动作同样是在Action运行之前就己经完成,所以会造成一些闲扰。
    如果你的Model有10个字段,并通过Bind属性声明只要绑定第1-5个字段,不过, 第6-10个字段仍然被设置了模型验证,这种情况下, 虽然模型不会绑定第6-10个字段的值,但还是会对模型进行验证,这样会导致ModdState.IsValid永远为false,此时就无法通过ModelState.IsValue判断到底是1-5的字段验证失败,还是6-10的字段验证失败。 虽然可以通过ModelState["域名"]得知是否该字段发生错误,但这个判断会让ASP.NET MVC的Controller变得过于复杂,因为判断逻辑过多。
    在ASP.NET MVC 的 Controller 类別,提供了一个好用的UpdateModel或TryUpdateModel 方法可以解决这个问题,让你在Action中再來行模型绑定的动作!
    原本想在Action方法里加上要自动绑定的参数,但现在采用自动绑定功能,所有参数全部从Action方法中移除,改从Action的程序代码内通过UpdateModel方法来生成绑定的结验证属性,演示中还有两个TestForm动作方法,以个负责显示窗体,另一个负责接收窗体POST过来的信息,由于我们不打算采用模型绑定的方式运作,因此直接带FormCollection类别的参数,事实上根本用不到这个参数。
    public class GuestbookForm
    {
    	[Required]
    	public int Type { get; set; }
    	[Required]
    	public string Name { get; set; }
    	[Required]
    	public string Email { get/ set; }
    	[Required]
    	public string Body { get; set; }
    }
    
    public ActionResult TestForm()
    {
    	return View ();
    }
    
    [HttpPost]
    public ActionResult TestForm(FormCollection form)
    {
    	GuestbookForm gbook = new GuestbookForm();
    	UpdateModel<GuestbookForm>(gbook); 
    	return Redirect ("/");
    }
    在使用UpdateModel进行模型绑定时,必须传入泛型参数,而UpdateModel传入的第一个参数则是要被绑定的数据模型对象,因此,在UpdateModel的前一行必须先准备好一个数椐模型对象,才能让UpdateModel自动绑定数据上去。
    UpdateModel运行时,会进行模型绑定的标准动作,自动将客户端传入的字段信息绑定到的数椐模型对象的属性上。不过,若发生模型绑定失败,一样会引发异常.
    因为容易引发一场,所以可以改成TryUpdatcModd避免例外发生。更改程序代码后 的结果如下:
    [HttpPost]
    public ActionResult TestForm(FormCollection form)
    {
    	GuestbookForm gbook = new GuestbookForm();
    	if (!TryUpdateModel<GuestbookForm>(gbook))
    	{
    		//模型绑定发生失畋 
    		return View();
    	}
    	return Redirect ("/");
    }
    TIPS
    在运行TryUpdateModel之前,ModelState不会有任何信息,当运行完TryUpdateModel 之后,就可以通过ModelState取得模型绑定过程中完整的验证错误信息。
    
    
    6.8动作过滤器
    有时在运行Action之前或之后会需要运行一些逻辑运算,以及处一些运行过程中 所生成的异常状况,为了满足这个需求, ASP.NET MVC提供动作过滤器(Action Filter) 来处理这些需求。
    ASP.NET MVC包含以下四种不同类型的Action Filter,如表6-4所示。
    表6-4动作过滤器
    



  • 相关阅读:
    P2805 [NOI2009]植物大战僵尸
    P3833 [SHOI2012]魔法树
    LOJ121 「离线可过」动态图连通性
    POJ2774 Long Long Message
    POJ2406 Power Strings
    SPOJ 694 DISUBSTR
    POJ3261 Milk Patterns
    HIHOcoder 1466 后缀自动机六·重复旋律9
    P2504 [HAOI2006]聪明的猴子
    P3804 【模板】后缀自动机
  • 原文地址:https://www.cnblogs.com/smartsmile/p/6234083.html
Copyright © 2020-2023  润新知