3.4.3 Controller与Action
在ASP.NET Core MVC中,一个Controller包括一个或多个Action,而Action则是Controller中的一些public类型的函数,它们可以接受参数、执行相关逻辑,最终返回一个结果,该结果会作为HTTP响应返回给发起HTTP请求的客户端。对于MVC视图应用而言,Action返回的结果通常是一个View,即页面;而对于Web API应用程序来说,则返回相应的资源或者HTTP状态码。
根据约定,Controller通常应放在应用程序根目录下的Controllers目录中,并且它继承自位于Microsoft.AspNetCore.Mvc命名空间下的Controller类,而这个Controller类又继承自ControllerBase抽象类。此外,在类的命名上,应以Controller结尾,如下所示。
using Microsoft.AspNetCore.Mvc; public class HomeController : Controller { … }
如果一个类并不满足上述约定,那么只要为它添加[Controller]特性,ASP.NET Core MVC仍然能够将它作为Controller处理;反之,如果为一个Controller添加[NotController]特性,那么,MVC应用程序就会忽略该Controller。
[Controller] [Route("api/[controller]")] public class Blogs { } [NonController] public class BooksController { }
在上例中,由于Blogs类带有[Controller]特性。因此,尽管它没有继承Controller类,也没有遵循Controller命名约定,但它仍然是一个Controller。不过,这样做也存在一个问题,如果一个类没有继承自Controller类,它就无法使用Controller类中的一些方法,这些方法用于在Action中方便地返回结果,如Ok()和NotFound()等。在本节的后半部分,将会详细说明Action的返回结果。
当Controller需要依赖其他服务时,通常的做法是使用构造函数注入所需要的服务,当程序运行时,ASP.NET Core会在创建Controller时自动从其依赖注入容器中获取所有依赖的服务,这种做法也遵循了“显式依赖”原则。需要注意的是,所注入的服务必须存在于容器中,否则将会发生异常。
Action是定义在Controller中的public方法,根据实际需要,可以接受参数,也可以不使用参数,它们可以返回任何类型的值,但通常是IActionResult类型或ActionResult<T>类型。此外,通常情况下,每一个Action都具有一个HTTP特性。而如果要使一个Action不起作用,只要为它添加[NotAction]特性即可。
[Route("api/[controller]")] public class BlogsController : Controller { // GET api/blogs public IActionResult Get() { } // GET api/blogs/top [HttpGet("top/{n}")] public IActionResult GetTopN(int n) { } [NonAction] public IActionResult DeleteBlog(int id) { } }
前面提到过,每个Action都应返回IActionResult类型或ActionResult<T>类型的值作为HTTP请求的结果。在ASP.NET Core MVC中,Action的返回结果有几种比较常见的类别,包括状态码、包含对象的状态码、重定向和内容。
状态码结果是最简单的一类,它们仅返回一个HTTP状态码给客户端,这一类的结果如表3-3所示。
表3-3 状态码结果
在ControllerBase类中,对于经常用到的状态码结果都提供了一个相应的方法用以直接返回相应的对象。要在Action中返回上述状态码,则只需调用对应的方法。
[HttpDelete()] public IActionResult DeleteBlog(int id) { //先检查指定的资源是否存在 if (!exist) { return NotFound(); } //删除成功 return Ok(); }
如果要返回上述状态码之外的结果,则可以使用StatusCode方法,并为该方法指明具体的状态码。
return StatusCode(403);
直接使用状态码数字有可能会出错,更简单且直观的方法是,使用Microsoft.AspNetCore. Http命名空间下的StatusCodes静态类,该类定义了所有可能的状态码常量,如图3-6所示。
图3-6 StatusCodes类的状态码列表
第二类结果是包含对象的状态码,这一类结果继承自ObjectResult,包括OkObjectResult、CreatedResult和NotFoundObjectResult等,如下所示。
public IActionResult DoSomething()
{
var result = new OkObjectResult(new { message = "操作成功", currentDate = DateTime.Now });
return result;
}
第三类结果是重定向结果,包括RedirectResult、LocalRedirectResult、RedirectToActionResult和RedirectToRouteResult等,使用方式如下。
//重定向到指定的URL return Redirect("http://www.microsoft.com/"); //重定向到当前应用程序中的另一个URL return LocalRedirect("/account/login"); //重定向到指定的Action return RedirectToAction("login"); //重定向到指定的路由 return RedirectToRoute("default", new { action = "login", controller = "account" });
第四类结果是内容结果,包括ViewResult、PartialViewResult、JsonResult和ContentResult等,其中ViewResult和PartialViewResult在MVC视图应用中非常常见,用于返回相应的页面;JsonResult用于返回JSON字符串,ContentResult用于返回一个字符串。
return Json(new { message = "This is a JSON result.", date = DateTime.Now }); return Content("Here's the ContentResult message.");
除了返回IActionResult外,当在Action要返回数据时,还可以使用ActionResult<T>类,ActionResult<T>是ASP.NET Core 2.1版中新增加的类型,它既可以表示一个ActionResult对象(ActionResult类实现了IActionResult接口),也可以表示一个具体类型(由泛型参数T指定)。
[HttpGet("{id}")] public ActionResult<Employee> Get(long id) { if(id <= 0) { return BadRequest(); } var employee = GetEmployee(id); if(employee == null) { return NotFound(); } return employee; }
ActionResult<T>的优点在于更为灵活地为Action设置返回值,同时,当使用OpenAPI(即Swagger)为API生成文档时,Action不需要使用[Produces]特性显式地指明其返回类型,因为其中的泛型参数T已经为OpenAPI指明了要返回的数据类型。