新建立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