控制器介绍

新建立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&amp;var2=325">Navigate</a>

注意html中的&amp;就是&,最后产生的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

原文地址:https://www.cnblogs.com/brown-birds/p/3765246.html