新建立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
);
}
一、兩種方法實現自己的控制器
1、用IController創建控制器
在MVC框架中,控制器類必須實現System.Web.Mvc命名空間的IController接口。
System.Web.Mvc.IController接口如下所示:
public
interface
IController
{
void
Execute(RequestContext requestContext);
}
接口只有一個方法Execute,在請求目標控制器時將被調用。
通過實現IController,就可以創建控制器類,但這是一個相當低級的接口,要做大量工作才能讓自己創建的控制器有效,下面只是一個簡單的演示。
鼠標右擊項目中的Controllers文件夾,選擇 Add -> Class,創建新類,取名為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",根據路由定義,也可以導航到"~/Basic",產生的結果為:
Controller:Basic,Action:Index
?
2、一般的做法是創建派生于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",或者根據路由定義,也可以導航到"~/Derived",產生的結果為:
MyView
Message:Hello from the DerivedController Index method.
?
二、控制器接收輸入
控制器常常需要訪問輸入數據,也就是在請求控制器的動作方法時傳遞進來的輸入數據,如查詢字符串值(指url尾部帶問號?后面跟的部分,稱為url的查詢字符串)、表單值、以及路由系統根據輸入url解析所得到的參數。
訪問這些數據有三種方式:
通過上下文對象(Context Objects)獲取數據。
通過參數傳遞給動作方法。
使用模型綁定(Model Binding)。
這里主要討論前兩種,模型綁定在后面討論。
1、通過上下文對象獲取數據
訪問上下文對象,通過使用一組便利屬性(Convenience Property)來進行訪問。所謂上下文對象,實際上就是訪問的與請求相關的信息。
如果要使用便利屬性來訪問與請求相關的信息,要注意一點,就是只能在派生于Controller的自定義控制器中才能使用。即,如果是通過實現IController接口來完成的自定義控制器里面不能使用這些便利屬性。這些便利屬性包括Request、Response、RouteData、HttpContext、Server等,每個屬性都包含了請求不同方面的信息。見p305,表12-1。
(1)下面通過一個例子來表現這種通過上下文對象獲取數據的方式。
在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所遞交的數據
//
string oldProductName = Request.Form["OldName"];
//
string newProductName = Request.Form["NewName"];
return
View();
}
}
}
?
/Views/Home/Index.cshtml內容如下:
@{
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>
這里可以注意,如果希望顯示出一些上下文數據,而又不想有對應的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所遞交的數據
//
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寫出的內容。
?
?上下文對象常用的有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所遞交的數據
//
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}
"
);
//
這是本身的默認路由,現在需要如果有id要限定它只能是數字,用正則表達式
//
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+
"
}
);
}
現在這個例子,希望路由在原來的默認路由的基礎上增加一個約束,就是如果url中輸入了id,那么希望將id的值約束在數字上,如果id輸入的是非數字的值,比如字母之類就不能匹配路由。
用正則表達式加約束條件,生成匿名對象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設置了這三個變量的默認參數值,其中id設置的是UrlParameter.Optional,設置為這個值,表示在匹配url時,id可以有也可以沒有,如果沒有id,那么就沒有id這個變量。如果希望有id的時候把它的值限定在數字上,不能直接在參數默認值的后面添加約束,比如:
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必須要有值,而且要是數字,否則就不匹配。這樣一來,下面的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"]得到的結果就為null,但是這里的類型轉換用的是Convert.ToInt32(),當括號內的對象為null時,得到的結果就為0。如果用的是int.Parse()遇到這種情況就會拋出異常。
int inputId = Convert.ToInt32(RouteData.Values["id"]);
當然,如果取到id的值,如果只想要字符類型,那就不用這么復雜, 可以直接用
string inputId = (string)RouteData.Values["id"];
?
對于本例,如果在url中輸入的是"~/Home/TestInput/325",那么顯示的結果為:
inputController=Home
inputAction=TestInput
inputId=325
?
(3)通過上下文對象訪問Request.QueryString的例子
Request.QueryString查詢字符串就是跟在url后面帶問號之后的內容。例如:
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}
"
);
//
這是本身的默認路由,現在需要如果有id要限定它只能是數字,用正則表達式
//
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"則顯示的結果為:
inputController=Home
inputAction=TestInput2
inputId=0
queryVar1=abc
queryVar2=123
這個輸入的url,沒有匹配id,所以RouteData.Values["id"]為空,經過Convert.ToInt32()轉換后值為0。QueryString里如果有多個變量,之間用符號&間隔。
?
擴充下這個例題,現在假設在/Views/Home/Index.cshtml,也就是HomeController中Index產生的視圖上添加:
@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產生的html為:
<a href="/Home/TestInput2/123">Navigate</a>
點擊該超鏈接后顯示的結果為:
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" })產生的html為:
<a href="/Home/TestInput2?id=123">Navigate</a>
這是因為按定義順序,會首先去匹配第一個路由定義,那么反推回url。第一個路由定義中沒有id,那就認為new { id="123" }產生的就是查詢字符串。點擊這個超鏈接后,產生的顯示結果為:
inputController=Home
inputAction=TestInput2
inputId=0
queryVar1=
queryVar2=
?
這樣一來,如果是第二種路由定義,既想生成的url中產生id,又有查詢字符串,那就需要使用:
@Html.ActionLink("Navigate", "TestInput2/123", new { var1="ABC", var2="325" })
產生的html為:
<a href="/Home/TestInput2/123?var1=ABC&var2=325">Navigate</a>
注意html中的&就是&,最后產生的url就是"~/Home/TestInput2/123?var1=ABC&var2=325"
點擊該超鏈接后,產生的顯示結果為:
inputController=Home
inputAction=TestInput2
inputId=123
queryVar1=ABC
queryVar2=325
?
(4)通過上下文對象訪問Request.Form中數據的例子
利用Request.Form可以讀取提交過來的表單中的數據,通過Name的屬性值來進行識別訪問。下面構造一個例子來說明用Request.Form來讀取表單數據的情況。
假設使用的路由定義為:
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來訪問表單數據,本部需要指定參數。但是同名的兩個動作方法TestInput3看來是通過重載來實現的,如果名字相同,返回類型相同,參數又完全一致的話,程序就不能通過編譯。所以,為了演示這個例子,就在Post請求的動作方法TestInput3上加了一個啞元參數dummy,目的就是為了完成同名的TestInput3函數的重載。
接下來,在由Get請求TestInput3時,返回的視圖是默認視圖/Views/Home/TestInput3.cshtml,在里面構造了表單:
@{
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到與產生當前視圖同名的動作方法上,本例子中,產生這個視圖的動作方法是TestInput3,那么Post回去的時候,也就是傳遞給同名的TestInput3動作方法。
在響應Post的TestInput3動作方法通過:
string city = Request.Form["City"];
DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
讀取到表單中元素的值后,再利用ViewBag傳遞給顯示結果的視圖,該動作方法返回時指定了視圖名return View("TI3Result").
那么,就在/Views/Home/TI3Result.cshtml中產生輸出顯示的結果。添加視圖文件/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
>
執行程序后,在url上輸入"~/Home/TestInput3,顯示為:
?在文本框中輸入數據如下:
點擊提交按鈕后,顯示結果為:
?
2、為動作方法設定參數傳遞數據
上下文對象常用的Request.QueryString、Request.Form和RouteData.Values等數據也可以通過動作方法的參數來設定。這里有個約定,就是參數名跟要訪問的屬性名或元素名同名,系統是根據名字自動去匹配。
(1)先看一個常規的例子,假設路由定義為:
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()上設定了參數,整型的名為id的參數。動作方法的參數會用名字自動去匹配Request.QueryString、Request.Form和RouteData.Values中的屬性和元素的值,依賴的就是名字。
接下來,在/Views/Home/Index.cshtml中添加ActionLink來生成超鏈接,指向HomeController中的TestInput4()動作方法。添加的代碼為:
@Html.ActionLink("NavTestInput4", "TestInput4", new{ id=325 })
請注意ActionLink是怎么根據路由定義來生成html的。
第一個參數"NavTestInput4"是超鏈接文本,第二個參數是要訪問的動作方法名字。沒有給出控制器名字,則默認為產生當前視圖頁面的控制器,在本例子中就是Home控制器。第三個參數用new生成匿名對象,其中有id=325,根據路由定義,id匹配路由模式中的id變量。倒退回url,生成的超鏈接為:
<
a
href
="/Home/TestInput4/325"
>
NavTestInput4
</
a
>
點擊該超鏈接后,根據路由定義,匹配路由模式"{controller}/{action}/{id}",訪問到Home控制器中的TestInput4動作方法,動作方法的參數id匹配路由模式中的{id},也就是RouteData.Values["id"]就通過匹配的動作方法參數id被傳遞到了動作方法中。而且可以看到,類型也自動匹配,參數中的int id,不需要做任何類型那個轉換。顯示結果為:
id=325
(2)跟上一個例題一樣的路由定義,現在將HomeController中的TestInput4修改為:
public
void
TestInput4(
int
id,
string
var1)
{
Response.Write(
string
.Format(
"
id={0}<br>var1={1}
"
, id, var1));
}
也就是說TestInput4的參數可以去匹配Request.QueryString、Request.Form和RouteData.Values等數據中的id和var1的值。將/Views/Home/Index.cshtml中ActionLink修改為:
@Html.ActionLink("NavTestInput4", "TestInput4", new{ id=325, var1="ABC" })
根據路由定義,在ActionLink中的第三個參數new{ id=325, var1="ABC" },id匹配路由模式"{controller}/{action}/{id}"中的{id},而var1在路由模式中沒有變量叫這個名字,那就以問號?跟在url的最后面作為QueryString。本例子中的ActionLink產生的html為:
<
a
href
="/Home/TestInput4/325?var1=ABC"
>
NavTestInput4
</
a
>
點擊該超鏈接后,訪問到Home控制器中的TestInput4動作方法。TestInput4動作方法中的參數id,接收RouteData.Values["id"]的值,參數var1接收Request.QueryString["var1"]的值。顯示的結果為:
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)個例子不一樣了。根據本例子中的路由定義,按照順序,匹配第一個路由。在第一個路由中,路由的url模式為"{controller}/{action}",路由模式中沒有id變量。那么ActionLink的第三個參數new{ id=325 }中的id就沒有路由模式中的變量與其對應,就只能跟在問號后面,放在url的最后作為QueryString。所以,本例中生成的html為:
<
a
href
="/Home/TestInput4?id=325"
>
NavTestInput4
</
a
>
需要注意的是,雖然產生的url不一樣,但是在訪問Home控制器里的TestInput4動作方法時,仍然可以讓TestInput4的參數id正確讀取到url中的id的值。這就是與前面直接通過上下文對象RouteData.Values["id"],或Request.QueryString["id"]來訪問id值不一樣的地方。動作方法TestInput4中的參數id,會自動去匹配Request.QueryString、Request.Form和RouteData.Values中與參數同名的數據。所以本例子中TestInput4的參數id,接收的是Request.QueryString["id"]的值,顯示的結果為:
id=325
RouteData.Values和Request.QueryString中如果都有與動作方法的參數相同的變量,優先匹配的是RouteData.Values。例如,假設在Index.cshtml中的ActionLink代碼為:
@Html.ActionLink(
"
NavTestInput4
"
,
"
TestInput4/123
"
,
new
{ id=
325
})
路由匹配本例子中的第二個路由定義,路由模式為"{controller}/{action}/{id}",產生的html為:
<
a
href
="/Home/TestInput4/123?id=325"
>
NavTestInput4
</
a
>
根據匹配的第二個路由,這里既有RouteData.Values["id"]值為123,又有Request.QueryString["id"]值為325,傳遞給Home控制器的動作方法TestInput4的時候,TestInput4的參數id優先接收RouteData.Values["id"],顯示結果為:
id=123
(4)提交的表單,Request.Form中各元素的值也可以通過動作方法的參數傳遞。
例如,前面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的類型自動就給轉換為DateTime類型。
?
三、從控制器產生輸出
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
"
);
}
執行后,若輸入"~/Home/TestRe",則會自動轉移到"~/Home/Index"
?
2、理解Action Result
在動作方法中不直接使用Response對象,而是返回一個派生于ActionResult類的對象,它描述控制器要完成的操作,例如產生一個視圖、重定向到另一個url或動作方法等。
不同的操作用不同的派生類,它們都是ActionResult的派生類。例如,重定向:
public
ActionResult TestRe()
{
return
new
RedirectResult(
"
/Home/Index
"
);
}
結果就返回RedirectResult的一個對象,因為RedirectResult派生于ActionResult,所以動作方法的返回類型可以用ActionResult,當然也可以明確使用RedirectResult作為該動作方法的返回類型。另外,各派生類也有一些控制器輔助器方法,可以簡化調用,例如RedirectResult類有輔助器方法Redirect
public
ActionResult TestRe()
{
return
Redirect(
"
/Home/Index
"
);
}
跟上面直接使用return new RedirectResult("/Home/Index");產生的效果是一樣的。
各個常用的派生類和用到的控制器輔助方法見p313。
?
3、從動作方法中產生視圖作為輸出
public
class
HomeController : Controller
{
public
ViewResult Index()
{
return
View();
}
}
產生視圖 /Views/Home/Index.cshtml
再如
public
class
HomeController : Controller
{
public
ViewResult Index()
{
return
View(
"
HomePage
"
);
}
}
產生的視圖為 /Views/Home/HomePage.cshtml
return View("HomePage")參數里加上雙引號,表示給出指定的視圖名。就不是默認跟動作方法同名的視圖了。
另外,這里動作方法的返回類型用的是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
?
四、把數據從動作方法傳遞給視圖
1、使用視圖模型對象
@model 類型
在HomeController中添加動作方法VMO(),如下:
public
ViewResult VMO()
{
DateTime date
=
DateTime.Now;
return
View(date);
}
注意,這里的return View(date);參數里的date沒有加雙引號,這表示要傳遞給視圖的數據,而不是指定要渲染的視圖名,這里如果將date加上雙引號,含義就變了,就表示該動作方法要產生一個名為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.
剛才強調,在動作方法中,返回View帶參數時,不要加雙引號,才表示返回的數據對象。如果要直接返回字符串對象,就需要在前面加上(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傳遞數據
ViewBag允許你在這個動態對象上定義任意屬性,并在視圖中訪問它們 ,就相當于鍵/值對。
只是vs對它不提供智能感應支持。
?
3、執行重定向
(1)重定向到字面url
假設在HomeController中有動作方法TestRe
public
RedirectResult TestRe()
{
return
Redirect(
"
/Home/Index
"
);
}
當訪問"~/Home/TestRe"時,就會重定向到"~/Home/Index"
(2)重定向到路由系統的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
"
});
}
執行程序后,當輸入"~/Home/TestTe",將產生重定向。注意,產生的url取決于所使用的路由定義。如果路由定義為
routes.MapRoute(
"
Default
"
,
//
Route name
"
{controller}/{action}/{id}
"
,
//
URL with parameters
new
{ controller =
"
Home
"
, action =
"
Index
"
, id = UrlParameter.Optional }
//
Parameter defaults
);
則重定向產生的url為
"~/Home/TestInput2/123?var1=ABC&var2=999"
但這個路由定義,如果沒有加以處理,在id處如果輸入的不是數字,那么將會拋出異常。
如果路由定義用的是下面的定義
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+
"
}
);
那么產生的重定向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、返回文件及二進制數據
(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文件夾中。在出現另存為對話框的時候,下載名被改為Test.txt。
(2)發送字節數組
public
FileContentResult TestFile()
{
byte
[] data = ...
//
二進制內容
return
File(data,
"
text/plain
"
,
"
Test.txt
"
);
}
(3)發送流內容
如果所處理的數據可以通過一個打開的System.IO.Stream進行操作,可以把這個流傳遞給File方法的一個重載版本。這個流得內容將被讀取并發送給瀏覽器。
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)發送404錯誤
public
HttpStatusCodeResult StatusCode()
{
return
HttpNotFound();
}
?
(3)發送401錯誤
public
HttpStatusCodeResult StatusCode()
{
return
new
HttpUnauthorizedResult();
}
?
?
-lyj
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

