新建立MVC3項目,名為12-1ControllersAndActions,使用空模板。
Global.asax中默認的路由定義為:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional } // Parameter defaults ); }
一、兩種方法實現(xiàn)自己的控制器
1、用IController創(chuàng)建控制器
在MVC框架中,控制器類必須實現(xiàn)System.Web.Mvc命名空間的IController接口。
System.Web.Mvc.IController接口如下所示:
public interface IController { void Execute(RequestContext requestContext); }
接口只有一個方法Execute,在請求目標控制器時將被調(diào)用。
通過實現(xiàn)IController,就可以創(chuàng)建控制器類,但這是一個相當?shù)图壍慕涌冢龃罅抗ぷ鞑拍茏屪约簞?chuàng)建的控制器有效,下面只是一個簡單的演示。
鼠標右擊項目中的Controllers文件夾,選擇 Add -> Class,創(chuàng)建新類,取名為BasicController,代碼如下:
namespace _12_1ControllersAndActions.Controllers { public class BasicController:IController { public void Execute(RequestContext requestContext) { string controller = ( string )requestContext.RouteData.Values[ " controller " ]; string action = ( string )requestContext.RouteData.Values[ " action " ]; requestContext.HttpContext.Response.Write( string .Format( " Controller:{0}, Action:{1} " , controller, action)); } } }
?如果運行程序,導航到"~/Basic/Index",根據(jù)路由定義,也可以導航到"~/Basic",產(chǎn)生的結(jié)果為:
Controller:Basic,Action:Index
?
2、一般的做法是創(chuàng)建派生于Controller類的控制器
鼠標右擊項目中的Controllers文件夾,選擇 Add -> Controller,新建控制器,命名為DerivedController,代碼如下:
namespace _12_1ControllersAndActions.Controllers { public class DerivedController : Controller { // // GET: /Derived/ public ActionResult Index() { ViewBag.Message = " Hello from the DerivedController Index method. " ; return View( " MyView " ); } } }
在方法Index上鼠標右鍵,添加視圖,視圖取名為MyView
/Views/Derived/MyView.cshtml
@{ ViewBag.Title = "MyView"; } < h2 > MyView </ h2 > < h1 > Message: @ViewBag.Message </ h1 >
?運行程序,導航到"~/Derived/Index",或者根據(jù)路由定義,也可以導航到"~/Derived",產(chǎn)生的結(jié)果為:
MyView
Message:Hello from the DerivedController Index method.
?
二、控制器接收輸入
控制器常常需要訪問輸入數(shù)據(jù),也就是在請求控制器的動作方法時傳遞進來的輸入數(shù)據(jù),如查詢字符串值(指url尾部帶問號?后面跟的部分,稱為url的查詢字符串)、表單值、以及路由系統(tǒng)根據(jù)輸入url解析所得到的參數(shù)。
訪問這些數(shù)據(jù)有三種方式:
通過上下文對象(Context Objects)獲取數(shù)據(jù)。
通過參數(shù)傳遞給動作方法。
使用模型綁定(Model Binding)。
這里主要討論前兩種,模型綁定在后面討論。
1、通過上下文對象獲取數(shù)據(jù)
訪問上下文對象,通過使用一組便利屬性(Convenience Property)來進行訪問。所謂上下文對象,實際上就是訪問的與請求相關的信息。
如果要使用便利屬性來訪問與請求相關的信息,要注意一點,就是只能在派生于Controller的自定義控制器中才能使用。即,如果是通過實現(xiàn)IController接口來完成的自定義控制器里面不能使用這些便利屬性。這些便利屬性包括Request、Response、RouteData、HttpContext、Server等,每個屬性都包含了請求不同方面的信息。見p305,表12-1。
(1)下面通過一個例子來表現(xiàn)這種通過上下文對象獲取數(shù)據(jù)的方式。
在12-1ControllersAndActions項目中,HomeController控制器定義如下:
namespace _12_1ControllersAndActions.Controllers { public class HomeController : Controller { public ActionResult Index() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; ViewBag.userName = userName; ViewBag.serverName = serverName; ViewBag.clientIP = clientIP; ViewBag.dateStamp = dateStamp; // 接收Request.Form所遞交的數(shù)據(jù) // string oldProductName = Request.Form["OldName"]; // string newProductName = Request.Form["NewName"]; return View(); } } }
?
/Views/Home/Index.cshtml內(nèi)容如下:
@{ ViewBag.Title = " Index " ; } <h2>Index</h2> <h2>userName=@ViewBag.userName</h2> <h2>serverName=@ViewBag.serverName</h2> <h2>clientIP=@ViewBag.clientIP</h2> <h2>dataStamp=@ViewBag.dateStamp</h2>
這里可以注意,如果希望顯示出一些上下文數(shù)據(jù),而又不想有對應的cshtml文件,那么可以用response.write,例如:
namespace _12_1ControllersAndActions.Controllers { public class HomeController : Controller { public ActionResult Index() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; ViewBag.userName = userName; ViewBag.serverName = serverName; ViewBag.clientIP = clientIP; ViewBag.dateStamp = dateStamp; // 接收Request.Form所遞交的數(shù)據(jù) // string oldProductName = Request.Form["OldName"]; // string newProductName = Request.Form["NewName"]; return View(); } public void Index2() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; Response.Write( string .Format( " userName:{0}<br>serverName:{1}<br>clientIP:{2}<br>dataStamp:{3} " , userName, serverName, clientIP, dateStamp)); } } }
這里的Index2就沒有對應的cshtml文件對應。程序運行后,輸入地址:"~/Home/Index2"就可以查看到用Response.Write寫出的內(nèi)容。
?
?上下文對象常用的有Request.QueryString、Request.Form和RouteData.Values
(2)通過上下文對象訪問RouteData.Values的例子
上面例子中的項目12-1ControllersAndActions,在HomeController控制器中添加了一個TestInput動作方法,如下:
namespace _12_1ControllersAndActions.Controllers { public class HomeController : Controller { public ActionResult Index() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; ViewBag.userName = userName; ViewBag.serverName = serverName; ViewBag.clientIP = clientIP; ViewBag.dateStamp = dateStamp; // 接收Request.Form所遞交的數(shù)據(jù) // string oldProductName = Request.Form["OldName"]; // string newProductName = Request.Form["NewName"]; return View(); } public void Index2() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; Response.Write( string .Format( " userName:{0}<br>serverName:{1}<br>clientIP:{2}<br>dataStamp:{3} " , userName, serverName, clientIP, dateStamp)); } public void TestInput() { string inputController = ( string )RouteData.Values[ " controller " ]; string inputAction = ( string )RouteData.Values[ " action " ]; int inputId = Convert.ToInt32(RouteData.Values[ " id " ]); Response.Write( string .Format( " inputController={0}<br>inputAction={1}<br>inputId={2} " , inputController, inputAction, inputId)); } } }
在TestInput動作方法中,通過RouteData.Values["controller"]讀取到當前請求的控制器名字,通過RouteData.Values["action"]讀取到當前請求的動作方法的名字,通過RouteData.Values["id"]訪問到當前請求中的 URL里對應到路由中的自定義變量id的值讀取出來。那么這里RouteData.Values中的controller、action、id屬性來自于哪里,RouteData.Values中還有哪些屬性可用,就取決于在Global.asax中的路由定義。
先看一下為當前這個例子設計的路由定義:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); // 這是本身的默認路由,現(xiàn)在需要如果有id要限定它只能是數(shù)字,用正則表達式 // routes.MapRoute( // "Default", // Route name // "{controller}/{action}/{id}", // URL with parameters // new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults // ); routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id= @" \d+ " } ); }
現(xiàn)在這個例子,希望路由在原來的默認路由的基礎上增加一個約束,就是如果url中輸入了id,那么希望將id的值約束在數(shù)字上,如果id輸入的是非數(shù)字的值,比如字母之類就不能匹配路由。
用正則表達式加約束條件,生成匿名對象new { id=@"\d+" },但是這個約束不能直接加在原來的默認路由上,原來的默認路由為:
routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional } // Parameter defaults );
在這個默認路由中,定義了url模式里的變量有3個,分別是controller、action和id,并用new設置了這三個變量的默認參數(shù)值,其中id設置的是UrlParameter.Optional,設置為這個值,表示在匹配url時,id可以有也可以沒有,如果沒有id,那么就沒有id這個變量。如果希望有id的時候把它的值限定在數(shù)字上,不能直接在參數(shù)默認值的后面添加約束,比如:
routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional }, // Parameter defaults new { id= @" \d+ " } );
加上這個約束,就表示id必須要有值,而且要是數(shù)字,否則就不匹配。這樣一來,下面的url都不能再匹配了:
"~/"
"~/Home/Index"
"~/Home/TestInput"
也就是說id=UrlParameter.Optional就失去了意義。
解決方法就是把路由定義成兩個:
routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id= @" \d+ " }
);
按照順序依次匹配,沒有寫id的匹配第一個路由定義,寫了id的匹配第二個路由定義。
那么沒有寫id的時候,匹配第一個路由定義,就是{controller}/{action},就只有controller和action兩個變量,這個時候在動作方法中訪問RouteData.Values["id"]得到的結(jié)果就為null,但是這里的類型轉(zhuǎn)換用的是Convert.ToInt32(),當括號內(nèi)的對象為null時,得到的結(jié)果就為0。如果用的是int.Parse()遇到這種情況就會拋出異常。
int inputId = Convert.ToInt32(RouteData.Values["id"]);
當然,如果取到id的值,如果只想要字符類型,那就不用這么復雜, 可以直接用
string inputId = (string)RouteData.Values["id"];
?
對于本例,如果在url中輸入的是"~/Home/TestInput/325",那么顯示的結(jié)果為:
inputController=Home
inputAction=TestInput
inputId=325
?
(3)通過上下文對象訪問Request.QueryString的例子
Request.QueryString查詢字符串就是跟在url后面帶問號之后的內(nèi)容。例如:
http://localhost:1943/Home/TestInput2?var1=abc&var2=123
問號后面的var1=abc&var2=123就是QueryString。
?
例,假設跟上一個例題同樣的路由定義:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); // 這是本身的默認路由,現(xiàn)在需要如果有id要限定它只能是數(shù)字,用正則表達式 // routes.MapRoute( // "Default", // Route name // "{controller}/{action}/{id}", // URL with parameters // new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults // ); routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id= @" \d+ " } ); }
?
在HomeController中新增了名為TestInput2的動作方法:
public void TestInput2() { string inputController = ( string )RouteData.Values[ " controller " ]; string inputAction = ( string )RouteData.Values[ " action " ]; int inputId = Convert.ToInt32(RouteData.Values[ " id " ]); string queryVar1 = Request.QueryString[ " var1 " ]; string queryVar2 = Request.QueryString[ " var2 " ]; Response.Write( string .Format( " inputController={0}<br>inputAction={1}<br> " + " inputId={2}<br>queryVar1={3}<br>queryVar2={4} " , inputController, inputAction, inputId, queryVar1, queryVar2)); }
這里用了兩句話
string queryVar1 = Request.QueryString["var1"];
string queryVar2 = Request.QueryString["var2"];
來讀取查詢字符串中變量的值。
如果輸入的url是"~/Home/TestInput2?var1=abc&var2=123"則顯示的結(jié)果為:
inputController=Home
inputAction=TestInput2
inputId=0
queryVar1=abc
queryVar2=123
這個輸入的url,沒有匹配id,所以RouteData.Values["id"]為空,經(jīng)過Convert.ToInt32()轉(zhuǎn)換后值為0。QueryString里如果有多個變量,之間用符號&間隔。
?
擴充下這個例題,現(xiàn)在假設在/Views/Home/Index.cshtml,也就是HomeController中Index產(chǎn)生的視圖上添加:
@Html.ActionLink("Navigate", "TestInput2", new { id="123" })
這時,如果路由定義為:
routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional } // Parameter defaults );
那么ActionLink產(chǎn)生的html為:
<a href="/Home/TestInput2/123">Navigate</a>
點擊該超鏈接后顯示的結(jié)果為:
inputController=Home
inputAction=TestInput2
inputId=123
queryVar1=
queryVar2=
?
但是,如果路由定義為:
routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id = @" \d+ " } );
那么@Html.ActionLink("Navigate", "TestInput2", new { id="123" })產(chǎn)生的html為:
<a href="/Home/TestInput2?id=123">Navigate</a>
這是因為按定義順序,會首先去匹配第一個路由定義,那么反推回url。第一個路由定義中沒有id,那就認為new { id="123" }產(chǎn)生的就是查詢字符串。點擊這個超鏈接后,產(chǎn)生的顯示結(jié)果為:
inputController=Home
inputAction=TestInput2
inputId=0
queryVar1=
queryVar2=
?
這樣一來,如果是第二種路由定義,既想生成的url中產(chǎn)生id,又有查詢字符串,那就需要使用:
@Html.ActionLink("Navigate", "TestInput2/123", new { var1="ABC", var2="325" })
產(chǎn)生的html為:
<a href="/Home/TestInput2/123?var1=ABC&var2=325">Navigate</a>
注意html中的&就是&,最后產(chǎn)生的url就是"~/Home/TestInput2/123?var1=ABC&var2=325"
點擊該超鏈接后,產(chǎn)生的顯示結(jié)果為:
inputController=Home
inputAction=TestInput2
inputId=123
queryVar1=ABC
queryVar2=325
?
(4)通過上下文對象訪問Request.Form中數(shù)據(jù)的例子
利用Request.Form可以讀取提交過來的表單中的數(shù)據(jù),通過Name的屬性值來進行識別訪問。下面構(gòu)造一個例子來說明用Request.Form來讀取表單數(shù)據(jù)的情況。
假設使用的路由定義為:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id = @" \d+ " } ); }
在HomeController中添加了TestInput3()動作方法的兩個重載版本:
[HttpGet] public ViewResult TestInput3() { return View(); } [HttpPost] public ViewResult TestInput3( string dummy) { string inputController = ( string )RouteData.Values[ " controller " ]; string inputAction = ( string )RouteData.Values[ " action " ]; int inputId = Convert.ToInt32(RouteData.Values[ " id " ]); string city = Request.Form[ " City " ]; DateTime forDate = DateTime.Parse(Request.Form[ " forDate " ]); ViewBag.inputController = inputController; ViewBag.inputAction = inputAction; ViewBag.inputId = inputId; ViewBag.city = city; ViewBag.forDate = forDate; return View( " TI3Result " ); }
這里用了兩個注解屬性[HttpGet]和[HttpPost]來指定一個TestInput3用于Get請求,另一個TestInput3動作方法用于Post請求。在本例中,希望在Post請求的TestInput3動作方法中利用上下文對象Request.Form來訪問表單數(shù)據(jù),本部需要指定參數(shù)。但是同名的兩個動作方法TestInput3看來是通過重載來實現(xiàn)的,如果名字相同,返回類型相同,參數(shù)又完全一致的話,程序就不能通過編譯。所以,為了演示這個例子,就在Post請求的動作方法TestInput3上加了一個啞元參數(shù)dummy,目的就是為了完成同名的TestInput3函數(shù)的重載。
接下來,在由Get請求TestInput3時,返回的視圖是默認視圖/Views/Home/TestInput3.cshtml,在里面構(gòu)造了表單:
@{ ViewBag.Title = "TestInput3"; } < h2 > TestInput3 </ h2 > @using (Html.BeginForm()) { < p > city: < input type ="text" name ="City" /></ p > < p > forDate: < input type ="text" name ="forDate" /></ p > < input type ="submit" value ="提交" /> }
表單由
@using (Html.BeginForm())
{
? ? ...
}
指定。里面有是三個元素,第一個是文本框,name為City,第二個也是文本框,name屬性為forDate。Request.Form就依賴這些元素的name來識別和訪問指定的元素值。第三個元素是按鈕,類型為submit,按鈕顯示的文字為“提交”。點擊該按鈕后,默認將表單Post到與產(chǎn)生當前視圖同名的動作方法上,本例子中,產(chǎn)生這個視圖的動作方法是TestInput3,那么Post回去的時候,也就是傳遞給同名的TestInput3動作方法。
在響應Post的TestInput3動作方法通過:
string city = Request.Form["City"];
DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
讀取到表單中元素的值后,再利用ViewBag傳遞給顯示結(jié)果的視圖,該動作方法返回時指定了視圖名return View("TI3Result").
那么,就在/Views/Home/TI3Result.cshtml中產(chǎn)生輸出顯示的結(jié)果。添加視圖文件/Views/Home/TI3Result.cshtml如下:
@{ ViewBag.Title = "TI3Result"; } < h2 > TI3Result </ h2 > < p > inputController=@ViewBag.inputController </ p > < p > inputAction=@ViewBag.inputAction </ p > < p > inpuId=@ViewBag.inputId </ p > < p > city=@ViewBag.city </ p > < p > forDate=@ViewBag.forDate </ p >
執(zhí)行程序后,在url上輸入"~/Home/TestInput3,顯示為:
?在文本框中輸入數(shù)據(jù)如下:
點擊提交按鈕后,顯示結(jié)果為:
?
2、為動作方法設定參數(shù)傳遞數(shù)據(jù)
上下文對象常用的Request.QueryString、Request.Form和RouteData.Values等數(shù)據(jù)也可以通過動作方法的參數(shù)來設定。這里有個約定,就是參數(shù)名跟要訪問的屬性名或元素名同名,系統(tǒng)是根據(jù)名字自動去匹配。
(1)先看一個常規(guī)的例子,假設路由定義為:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional } // Parameter defaults ); }
在HomeController中新加了名為TestInput4()的動作方法:
public void TestInput4( int id) { Response.Write( string .Format( " id={0} " , id)); }
在動作方法TestInput()上設定了參數(shù),整型的名為id的參數(shù)。動作方法的參數(shù)會用名字自動去匹配Request.QueryString、Request.Form和RouteData.Values中的屬性和元素的值,依賴的就是名字。
接下來,在/Views/Home/Index.cshtml中添加ActionLink來生成超鏈接,指向HomeController中的TestInput4()動作方法。添加的代碼為:
@Html.ActionLink("NavTestInput4", "TestInput4", new{ id=325 })
請注意ActionLink是怎么根據(jù)路由定義來生成html的。
第一個參數(shù)"NavTestInput4"是超鏈接文本,第二個參數(shù)是要訪問的動作方法名字。沒有給出控制器名字,則默認為產(chǎn)生當前視圖頁面的控制器,在本例子中就是Home控制器。第三個參數(shù)用new生成匿名對象,其中有id=325,根據(jù)路由定義,id匹配路由模式中的id變量。倒退回url,生成的超鏈接為:
< a href ="/Home/TestInput4/325" > NavTestInput4 </ a >
點擊該超鏈接后,根據(jù)路由定義,匹配路由模式"{controller}/{action}/{id}",訪問到Home控制器中的TestInput4動作方法,動作方法的參數(shù)id匹配路由模式中的{id},也就是RouteData.Values["id"]就通過匹配的動作方法參數(shù)id被傳遞到了動作方法中。而且可以看到,類型也自動匹配,參數(shù)中的int id,不需要做任何類型那個轉(zhuǎn)換。顯示結(jié)果為:
id=325
(2)跟上一個例題一樣的路由定義,現(xiàn)在將HomeController中的TestInput4修改為:
public void TestInput4( int id, string var1) { Response.Write( string .Format( " id={0}<br>var1={1} " , id, var1)); }
也就是說TestInput4的參數(shù)可以去匹配Request.QueryString、Request.Form和RouteData.Values等數(shù)據(jù)中的id和var1的值。將/Views/Home/Index.cshtml中ActionLink修改為:
@Html.ActionLink("NavTestInput4", "TestInput4", new{ id=325, var1="ABC" })
根據(jù)路由定義,在ActionLink中的第三個參數(shù)new{ id=325, var1="ABC" },id匹配路由模式"{controller}/{action}/{id}"中的{id},而var1在路由模式中沒有變量叫這個名字,那就以問號?跟在url的最后面作為QueryString。本例子中的ActionLink產(chǎn)生的html為:
< a href ="/Home/TestInput4/325?var1=ABC" > NavTestInput4 </ a >
點擊該超鏈接后,訪問到Home控制器中的TestInput4動作方法。TestInput4動作方法中的參數(shù)id,接收RouteData.Values["id"]的值,參數(shù)var1接收Request.QueryString["var1"]的值。顯示的結(jié)果為:
id=325
var1=ABC
(3)注意路由變化,如果將路由定義改為:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id = @" \d+ " } ); }
假設HomeController中的動作方法TestInput4()與第(1)個例子中一樣:
public void TestInput4( int id) { Response.Write( string .Format( " id={0} " , id)); }
在/Views/Home/Index.cshtml中的ActionLink也與第(1)個例子一樣,代碼為:
@Html.ActionLink("NavTestInput4", "TestInput4", new{ id=325 })
注意,由于路由的不同,這個例子中ActionLink生成html就與第(1)個例子不一樣了。根據(jù)本例子中的路由定義,按照順序,匹配第一個路由。在第一個路由中,路由的url模式為"{controller}/{action}",路由模式中沒有id變量。那么ActionLink的第三個參數(shù)new{ id=325 }中的id就沒有路由模式中的變量與其對應,就只能跟在問號后面,放在url的最后作為QueryString。所以,本例中生成的html為:
< a href ="/Home/TestInput4?id=325" > NavTestInput4 </ a >
需要注意的是,雖然產(chǎn)生的url不一樣,但是在訪問Home控制器里的TestInput4動作方法時,仍然可以讓TestInput4的參數(shù)id正確讀取到url中的id的值。這就是與前面直接通過上下文對象RouteData.Values["id"],或Request.QueryString["id"]來訪問id值不一樣的地方。動作方法TestInput4中的參數(shù)id,會自動去匹配Request.QueryString、Request.Form和RouteData.Values中與參數(shù)同名的數(shù)據(jù)。所以本例子中TestInput4的參數(shù)id,接收的是Request.QueryString["id"]的值,顯示的結(jié)果為:
id=325
RouteData.Values和Request.QueryString中如果都有與動作方法的參數(shù)相同的變量,優(yōu)先匹配的是RouteData.Values。例如,假設在Index.cshtml中的ActionLink代碼為:
@Html.ActionLink( " NavTestInput4 " , " TestInput4/123 " , new { id= 325 })
路由匹配本例子中的第二個路由定義,路由模式為"{controller}/{action}/{id}",產(chǎn)生的html為:
< a href ="/Home/TestInput4/123?id=325" > NavTestInput4 </ a >
根據(jù)匹配的第二個路由,這里既有RouteData.Values["id"]值為123,又有Request.QueryString["id"]值為325,傳遞給Home控制器的動作方法TestInput4的時候,TestInput4的參數(shù)id優(yōu)先接收RouteData.Values["id"],顯示結(jié)果為:
id=123
(4)提交的表單,Request.Form中各元素的值也可以通過動作方法的參數(shù)傳遞。
例如,前面TestInput3的例子,其他不變,將HomeController中接收Post請求的TestInput3動作方法修改為:
[HttpPost] public ViewResult TestInput3( string controller, string action, string city, DateTime forDate, int id = 0 ) { string inputController = controller; string inputAction = action; int inputId = id; ViewBag.inputController = inputController; ViewBag.inputAction = inputAction; ViewBag.inputId = inputId; ViewBag.city = city; ViewBag.forDate = forDate; return View( " TI3Result " ); }
效果跟上一個TestInput3的例子一樣。而且forDate的類型自動就給轉(zhuǎn)換為DateTime類型。
?
三、從控制器產(chǎn)生輸出
1、不要視圖,直接用Response.Write輸出
只要是派生于Controller的類里面的動作方法,就可以直接用Response.Write().
例如,前面的例子Index2()
namespace _12_1ControllersAndActions.Controllers { public class HomeController : Controller { public void Index2() { // 訪問上下文對象的各個屬性 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; Response.Write( string .Format( " userName:{0}<br>serverName:{1}<br>clientIP:{2}<br>dataStamp:{3} " , userName, serverName, clientIP, dateStamp)); } } }
以及直接用Response.Redirect("/Some/Other/Url")也是屬于這一種。
例如,在HomeController中添加動作方法TestRe
public void TestRe() { Response.Redirect( " /Home/Index " ); }
執(zhí)行后,若輸入"~/Home/TestRe",則會自動轉(zhuǎn)移到"~/Home/Index"
?
2、理解Action Result
在動作方法中不直接使用Response對象,而是返回一個派生于ActionResult類的對象,它描述控制器要完成的操作,例如產(chǎn)生一個視圖、重定向到另一個url或動作方法等。
不同的操作用不同的派生類,它們都是ActionResult的派生類。例如,重定向:
public ActionResult TestRe() { return new RedirectResult( " /Home/Index " ); }
結(jié)果就返回RedirectResult的一個對象,因為RedirectResult派生于ActionResult,所以動作方法的返回類型可以用ActionResult,當然也可以明確使用RedirectResult作為該動作方法的返回類型。另外,各派生類也有一些控制器輔助器方法,可以簡化調(diào)用,例如RedirectResult類有輔助器方法Redirect
public ActionResult TestRe() { return Redirect( " /Home/Index " ); }
跟上面直接使用return new RedirectResult("/Home/Index");產(chǎn)生的效果是一樣的。
各個常用的派生類和用到的控制器輔助方法見p313。
?
3、從動作方法中產(chǎn)生視圖作為輸出
public class HomeController : Controller { public ViewResult Index() { return View(); }
}
產(chǎn)生視圖 /Views/Home/Index.cshtml
再如
public class HomeController : Controller { public ViewResult Index() { return View( " HomePage " ); } }
產(chǎn)生的視圖為 /Views/Home/HomePage.cshtml
return View("HomePage")參數(shù)里加上雙引號,表示給出指定的視圖名。就不是默認跟動作方法同名的視圖了。
另外,這里動作方法的返回類型用的是ViewResult,因為在知道方法返回的類型時,傾向于使用具體的類型,當然直接使用ActionResult也可以的。MVC框架在搜索視圖時,先搜索Areas再搜索Views。僅以cshtml為例,下面是搜索順序:
/Areas/<AreaName>/Views/<ControllerName>/視圖名.cshtml
/Areas/<AreaName>/Views/Shared/視圖名.cshtml
/Views/<ControllerName>/視圖名.cshtml
/Views/Shared/視圖名.cshtml
?
視圖文件在生成html時會用到/Views/Shared/_Layout.cshtml布局文件作為默認布局文件,如果要用另一個布局文件可以用?
return View( " HomePage " , " _OtherLayout);
?當然,先要保證這個布局文件在/Views/Shared/目錄中,也就是/Views/Shared/_OtherLayout.cshtml
?
四、把數(shù)據(jù)從動作方法傳遞給視圖
1、使用視圖模型對象
@model 類型
在HomeController中添加動作方法VMO(),如下:
public ViewResult VMO() { DateTime date = DateTime.Now; return View(date); }
注意,這里的return View(date);參數(shù)里的date沒有加雙引號,這表示要傳遞給視圖的數(shù)據(jù),而不是指定要渲染的視圖名,這里如果將date加上雙引號,含義就變了,就表示該動作方法要產(chǎn)生一個名為date.cshtml的視圖來進行顯示。
這里使用return View(date);就表示把對象date傳遞到與當前動作方法同名的視圖上,也就是/Views/Home/VMO.cshtml
@model DateTime @{ ViewBag.Title = " VMO " ; } <h2>VMO</h2> the day is :@Model.DayOfWeek
在開頭指定模型類型時,要用小寫的m,這里是@model DateTime。而在文中讀取模型值時,要用大寫的M,如這里的@Model.DayOfWeek.
剛才強調(diào),在動作方法中,返回View帶參數(shù)時,不要加雙引號,才表示返回的數(shù)據(jù)對象。如果要直接返回字符串對象,就需要在前面加上(object)指明這是模型對象。
public ViewResult VMO() { DateTime date = DateTime.Now; return View(( object ) " hello, world. " ); }
在VMO.cshtml中,就使用string來指定模型類型。
@model string @{ ViewBag.Title = "VMO"; } < h2 > VMO </ h2 > the day is:@Model
2、使用ViewBag傳遞數(shù)據(jù)
ViewBag允許你在這個動態(tài)對象上定義任意屬性,并在視圖中訪問它們 ,就相當于鍵/值對。
只是vs對它不提供智能感應支持。
?
3、執(zhí)行重定向
(1)重定向到字面url
假設在HomeController中有動作方法TestRe
public RedirectResult TestRe() { return Redirect( " /Home/Index " ); }
當訪問"~/Home/TestRe"時,就會重定向到"~/Home/Index"
(2)重定向到路由系統(tǒng)的url
在HomeController中,假設有前面例子中的動作方法TestInput2()
public void TestInput2() { string inputController = ( string )RouteData.Values[ " controller " ]; string inputAction = ( string )RouteData.Values[ " action " ]; int inputId = Convert.ToInt32(RouteData.Values[ " id " ]); string queryVar1 = Request.QueryString[ " var1 " ]; string queryVar2 = Request.QueryString[ " var2 " ]; Response.Write( string .Format( " inputController={0}<br>inputAction={1}<br> " + " inputId={2}<br>queryVar1={3}<br>queryVar2={4} " , inputController, inputAction, inputId, queryVar1, queryVar2)); }
下面定義動作方法TestRe()來重定向到TestInput2()
public RedirectToRouteResult TestRe() { return RedirectToRoute( new { Controller = " Home " , Action = " TestInput2 " , id = " 123 " , var1 = " ABC " , var2 = " 999 " }); }
執(zhí)行程序后,當輸入"~/Home/TestTe",將產(chǎn)生重定向。注意,產(chǎn)生的url取決于所使用的路由定義。如果路由定義為
routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " , id = UrlParameter.Optional } // Parameter defaults );
則重定向產(chǎn)生的url為
"~/Home/TestInput2/123?var1=ABC&var2=999"
但這個路由定義,如果沒有加以處理,在id處如果輸入的不是數(shù)字,那么將會拋出異常。
如果路由定義用的是下面的定義
routes.MapRoute( " Default " , // Route name " {controller}/{action} " , // URL with parameters new { controller = " Home " , action = " Index " } // Parameter defaults ); routes.MapRoute( " Default2 " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Home " , action = " Index " }, // Parameter defaults new { id = @" \d+ " } );
那么產(chǎn)生的重定向url為
"~/Home/TestInput2?id=123&var1=ABC&var2=999"
(3)重定向到一個動作方法
public RedirectToRouteResult TestRe() { return RedirectToAction( " TestInput2 " ); }
?
public RedirectToRouteResult TestRe() { return RedirectToAction( " TestInput2 " , new { id= " 123 " , var1= " ABC " , var2= " 999 " }); }
?
public RedirectToRouteResult TestRe() { return RedirectToAction( " TestInput2 " , " Home " ); }
?
public RedirectToRouteResult TestRe() { return RedirectToAction( " TestInput2 " , " Home " , new { id= " 123 " , var1= " ABC " , var2= " 999 " }); }
?
?4、返回文件及二進制數(shù)據(jù)
(1)返回文件
文件下載
public FileResult TestFile() { string fPath = AppDomain.CurrentDomain.BaseDirectory + " DownloadTest/ " ; // string fileName = @"c:\log.txt"; string fileName = fPath + " log.txt " ; string contentType = " text/plain " ; string downloadName = " Test.txt " ; return File(fileName, contentType, downloadName); }
這里的AppDomain.CurrentDomain.BaseDirectory表示讀取到當前項目的根物理路徑,末尾帶反斜杠。要下載的文件log.txt放在根目錄下的DownloadTest文件夾中。在出現(xiàn)另存為對話框的時候,下載名被改為Test.txt。
(2)發(fā)送字節(jié)數(shù)組
public FileContentResult TestFile() { byte [] data = ... // 二進制內(nèi)容 return File(data, " text/plain " , " Test.txt " ); }
(3)發(fā)送流內(nèi)容
如果所處理的數(shù)據(jù)可以通過一個打開的System.IO.Stream進行操作,可以把這個流傳遞給File方法的一個重載版本。這個流得內(nèi)容將被讀取并發(fā)送給瀏覽器。
public FileStreamResult TestFile() { Stream stream = ... // 打開某種流 return File(stream, " text/html " ); }
5、返回錯誤及http錯誤代碼
(1)指定錯誤碼
public HttpStatusCodeResult StatusCode() { return new HttpStatusCodeResult( 404 , " url cannot be serviced. " ); }
?
(2)發(fā)送404錯誤
public HttpStatusCodeResult StatusCode() { return HttpNotFound(); }
?
(3)發(fā)送401錯誤
public HttpStatusCodeResult StatusCode() { return new HttpUnauthorizedResult(); }
?
?
-lyj
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
