对于 MVC 视图应用而言,Action
返回的结果通常是一个 View
,即页面;
而对于 Web API
应用程序来说,则返回相应的资源或者 HTTP
状态码。
Controller
根据约定,继承自位于 Microsoft.AspNetCore.Mvc
命名空间下的 Controller
类,而这个 Controller
类又继承自 ControllerBase
抽象类。
如果一个类并不满足上述约定,那么只要为它添加[Controller]
特性,仍然能够将它作为 Controller
处理;反之,如果为一个 Controller
添加[NotController]
特性。
[Controller]
public class Blogs
{
}
[NonController]
public class ValueController
{
}
Action
每个 Action
都应返回 IActionResult
类型或ActionResult<T>
类型的值作为 HTTP 请求的结果。
常见的类别,包括
- 状态码
- 对象的状态码
- 重定向
- 内容
状态码
返回一个 HTTP 状态码给客户端
对应的状态码 | 描述 | ControllerBase中的方法 |
---|---|---|
200 | 操作成功 | Ok() |
400 | 错误的请求 | BadRequest() |
204 | 操作成功,但未返回任何内容 | NoContent() |
404 | 请求的资源找不到 | NotFound() |
401 | 未授权 | Unauthorized() |
415 | 无法处理请求附带的媒体格式 | 无 |
如果要返回上述状态码之外的结果,则可以使用 StatusCode
方法,并为该方法指明具体的状态码。
return StatusCode(403);
直接使用状态码数字有可能会出错,更简单且直观的方法是,使用Microsoft.AspNetCore.Http
命名空间下的 StatusCodes
静态类
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status200OK);
对象的状态码
这一类结果继承自 ObjectResult
,包括 OkObjectResult、CreatedResult
和 NotFoundObjectResult
等。
public IActionResult DoSomething()
{
var result = new OkObjectResult(new { message = "success", currentDate = DateTime.Now});
return result;
}
重定向
包括 RedirectResult、LocalRedirectResult、RedirectToActionResult
和 RedirectToRouteResult
等。
// 重定向到指定的URL
return Redirect("http://www.asp.net/");
// 重定向到当前应用程序中的另一个URL
return LocalRedirect("/account/login");
// 重定向到指定的Action
return RedirectToAction("login");
// 重定向到指定的路由
return RedirectToRoute("default", new { action = "login", controller = "Login" });
内容
包括 ViewResult、PartialViewResult、JsonResult
和 ContentResult
等,其中 ViewResult
和 PartialViewResult
在 MVC 视图应用中非常常见,用于返回相应的页面;JsonResult
用于返回JSON
字符串,ContentResult
用于返回一个字符串。
.net core web api 能够使用 /Controller/Action
访问控制器配置
控制器特性改写如下:
[ApiController]
[Route("[controller]/[action]")]
示例:
请求Get
方法的url为
WebApi官网学习记录—webapi中controller
与action
的选择
如果framework
找到一个匹配的URI
,创建一个包含占位符值的字典,key
就是这些占位符(不包括大括号),value
来自URI
或者默认值,这个字典存储在IHttpRouteData
对象中。默认值可能是RouteParameter.Optional
,此时对应的key/value
不会被添加到该字典中。
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{category}/{id}",
defaults: new { category = "all", id = RouteParameter.Optional }
);
对于URI"api/products
",route dictionary
中包括:
- controller:“product”
- category:“all”
对于URI"api/products/toys/123
",route directionary
中包括:
- controller:“products”
- category:“toys”
- id:“123”
Controller
的选择:
在route dictionary
中找到“controller
”
取到key
的值,和"Controller
"组合到一起,然后得到对应的controller
的类型名
在web api的controller
中查找这个类型名的controller
Action
的选择:
action
必须匹配HTTP method- 如果
route dictionary
存在"action",则action
的名字需与之匹配 - 对于
action
的每个参数,如果参数来自URI,则这个参数名必须能在route dictionary
或URI中带的参数中找到【可选参数与复杂类型除外】 - 尽可能匹配多的参数,最好的匹配可能是一个没有参数的方法
routes.MapHttpRoute(
name: "ApiRoot",
routeTemplate: "api/root/{id}",
defaults: new { controller = "products", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAll() {}
public Product GetById(int id, double version = 1.0) {}
[HttpGet]
public void FindProductsByName(string name) {}
public void Post(Product value) {}
public void Put(int id, Product value) {}
}
HTTP Request:
GET http://localhost:34701/api/products/1?version=1.5&details=1
依据以上原则分析最终选择的action是:GetById
Web Api 2(三)之路由与Action
的选择
路由(Route)
Web Api中的路由与Asp.net mvc中的路由基本上一样,一个路由看起来像是一个URI路径,但是路由中包含一些大括号包括的占位符(place holder
),例如:
api/{Controller}/{Action}/{Id}
当你创建一个路由的时候,你可以为一个或多个占位符设置默认值,例如下面的例子将Controller
设为Account
,如果请求访问的URL没有提供Controller
时,将会使用设置的默认的 Controller
defaults:new {Controller=Account}
你还可以为占位符设置一些限制,下面的例子限制Id
只能为数字
constraints:new{ id = @"\d+"}
Web Api中的路由主要有三个主要的功能:
- 将请求的URI与路由模板进行匹配
- 选择
Controller
- 选择
Action
当收到请求是,Web Api会尝试着将请求URI与路由表(Route Table
)中注册的路由模板进行比对,模板中的字面值(Literals
)必须被准确的匹配,如上面例子中的api
片段,请求的URL必须包含api才能匹配上面的路由,而占位符可以匹配任何值(除非你有其它特殊的限制),将请求的URL进行匹配时,只会比对路由模板中有的片段(Segment
),而路由模板中没有的则不会比对,如主机名(host name
)、查询参数(query parameters
)等,之后选择路由表中匹配的第一个路由来进行下一步的处理
Web Api提供的默认路由如下:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
default
中设置的id = RouteParameter.Optional
表示路由模板中id
是不是必须的,而是可选的,只有在请求的URL存在时才会被赋值
路由字典(Route Dictionary)
如果请求的URL
匹配到了与之匹配的路由时,Web api会创建一个Dictionary
来存放URL
中与路由模板中占位符对应的字段值,这个Dictionary
的Key
就是路由模板的占位符的名称(不包括大括号),这些值即可能来自URL
,也可能来自注册路由时设置的默认值。而这个Dictionary
就存放IHttpRouteData
类型的对象中。
注意 : 如果路由中的占位符被设置为 RouteParameter.Optional
,那么这个参数的值(即RouteParameter.Optional
)不会被加入到上述的Dictionary
中。但如果这个占位符被分配了具体的值的话,这个Dictionary
中还是会存储这个值的,例如:api/products
,那么此时这个Dictionary
中存储的就是:
- controller: “products”
- category:“all”
并不会存储: id:RouteParameter.Optional
,在比如,api/products/toys/123
,那么此时这个Dictionary
中存储的就是:
- controller:“products”
- catagory: “toys”
- id : “123”
这个字典中甚至可以包含路由模板中不存在,但在默认值中存在的值,例如:
routes.MapHttpRoute(
name:"Root",
routeTemplate:"api/root/{id}",
default:new { controller="customers",id = RouteParameter.Optional}
)
如果URL中分离出的片段为api/root/8
,那么此时这个Dictionary
中存储的是:
- controller:"customers
- id : “8”
Controller
的选择
Controller
的选择是通过IHttpControllerSelector.SelectController
的方法来实现的,这个方法接收一个** HttpRequestMessage
类型对象返回一个 HttpControllerDescriptor
,默认使用的是Web api内部提供的 DefaultHttpControllerSelector
**,这个类使用下面的逻辑来选择Controller
:
- 以"
Controller
"为Key
到上述的Dictionary
(存储在IHttpRouteData
类型中)查找对应的Value
- 在获取到的值(即请求的
Controller
名称)后面附加"Controller
"字符串去获取对应的Controller
的类型名称 - 拿第二步得到的
Controller
的名称去获取对应的Web ApiController
对象(ApiController
类型)
例如,如果路由字典(Route Dictionary
)包含键值对(key-value pair
)"Controller=products
",那么控制器的类型便是"ProductsController
",如果没有找到与之匹配的ApiController
类型或者有多个匹配,则返回一个错误给请求的客户端。
在第三步中,DefaultHttpControllerSelector
使用 IHttpControllerTypeResolver
(典型的IOC应用)接口去获取所有的ApiController
类型(均派生在ApiController
),这个接口返回满足一下条件的所有公共(Public
)类型:
- 实现
IHttpController
接口 - 非抽象类
- 类名以"
Controller
"结束
Action
的选择
在得到匹配的Controller
类型后,Web Api会调用IHttpActionSelector.SelectAction
方法去选择Action
,这个方法接收一个HttpControllerContext
类型参数,返回一个HttpActionSelector
类型实例。选择Action
的大致过程如下:
- 查看请求的Http
Method
(Get、POST等),获取请求的Action
名称,方法和获取Controller
名称的方法一致,以"Action
"为Key
到路由字典中获取对应值 - 查看路由表中注册的路由模板中的默认值
- 查看
Controller
中定义的Action
方法的参数,找出参数最匹配的一个Action
那么,满足什么条件的方法才能被视为一个Action
呢?满足如下条件的方法便会被认为是Action
- 定义在
Controller
中的所有公共的实例方法(不包括一些具有特殊名称的方法,如构造函数、事件、运算符重载等)
Web Api仅仅选择那些与请求的Http Method
匹配的Action
:
- 一个
Action
如果没有特殊说明,那么它的HttpMethod
为POST
- 你可以通过
[HttpGet]、[HttpPost]、[HttpDelete]、[HttpHead]、[HttpOptions]、[HttpPatch]、[HttpPut]
特性来显式的声明一个Action
的支持的HttpMethod
,也可以通过HttpVerbs
特性来声明。 - 如果一个
Action
的名称以Post、Get、Patch、Head、Put、Options、Delete
等字符开头的,则认为该Action支持对应的Http
Method。
Action
的参数绑定
Web Api中,Action
的参数的创建过程称为参数的绑定,遵照一下的规则:
- 简单类型(
Simple Type
)的参数从URI中获取其参数值 - 复杂类型(
Complex Type
)的参数从Http的请求报文中获取其参数值
那么如何区分简单类型和复杂类型呢?在Web Api中,如果一个数据类型支持源自字符串的类型转换,那么该数据对象就是简单类型,否则,该数据对象就是复杂类型,按照这个判断的标准,Net中的所有的基元类型(Primative Type
)和可空值类型(Nullable Type
)、外加 DateTime, Decimal, Guid, String, and TimeSpan
.都是简单类型,而一个自定义的类型默认情况下都是复杂类型.对于一个Action
来说,之多有一个参数来自于Http请求报文
注: 在C#中,能够直接被编译器识别的类型称为基元类型.如int、byte
等,其分别对应着FCL
中的System.Int32
和System.Byte
了解了上面的东西后,我们看一下Action
选择的机制(简单参数类型)
- 将
Controller
中所有满足请求HttpMethod
的Action
放入一个列表中 - 如果路由字典中存在"
Action
"条目,则从创建好的Action
列表中移除名称不匹配的Action
. - 尝试着通过URI去获取
Action
的参数
- 对于每个
Action
,获取其简单参数类型的参数列表,这其中不包括可选参数(Optional Parameters
) - 试着根据参数名称从参数列表、路由字典(
Route Dictionary
)或查询字符串(Query String
)中获取对应的参数值,这个匹配过程参数名称不区分大小写,也不依赖于参数的顺序。 - 选择一个
Action
,其参数列表中的每个参数在URI都有与之对应的参数值 - 如果有多个
Action
满足条件,则选择一个最为匹配的一个
忽略那些应用了[NonAction]
特性的Action
其它
Web Api的路由过程是支持扩展的,当内置的实现逻辑不足以满足应用的需求时,此时,便可以通过实现相应的接口来对逻辑进行自定义。其中相关的接口及其作用如下:
接口 | 描述 |
---|---|
IHttpControllerSelector | 选择控制器 |
IHttpControllerResolver | 获取控制器类型列表,默认的DefaultHttpControllerSelector从 |
获取的控制器类型列表中选择控制器 | |
IAssemblyResolver | 获取项目的程序集列表,IHttpControllerTypeResolver使用这个列表去查找控制器类型 |
IHttpControllerActivator | 创建控制器实例 |
IHttpActionSelector | 选择Action |
IHttpActionInvoker | 执行Action |
在实现某个接口后,然后使用HttpConfiguration
类的Service
集合去注册后,Web Api便会使用自定义的逻辑去替换掉内置的默认实现。
var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector),new MyControllerSelector(config));