zoukankan      html  css  js  c++  java
  • 控制器介绍

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

  • 相关阅读:
    Java 报错 -source 1.5 中不支持 diamond 运算符
    MacBook Java开发环境的配置
    MacBook 版本控制工具
    版本控制工具 Git SourceTree SSH 连接码云
    接口 请求https接口
    快递 共享电子面单
    快递 已发货订单重新打印电子面单
    SQL Server 分部分项导入后 数据的修改
    Hive数据的存储以及在centos7下进行Mysql的安装
    Hive初体验
  • 原文地址:https://www.cnblogs.com/brown-birds/p/3765246.html
Copyright © 2011-2022 走看看