【转载】Asp .Net Web Api路由路径问题
原文章地址:https://www.cnblogs.com/devtester/p/8897302.html
MVC也好,WebAPI也好,据我所知,有部分人是因为复杂的路由,而不想去学的。曾经见过一位程序猿,在他MVC程序中,一切皆路由,url中是完全拒绝"?"和“&”。对此,我也不好说什么,搞不好是个人风格。路由虽然重要,但其实也只是实现MVC的一种手段,并非你用的路由越多,你的url完全不使用参数,你的MVC就越纯正。说实话,笔者一开始对路由也感到恐惧,但是阅读了官方文档后,发现路由其实也可以很简单,关键在于我们如何使用。由于笔者也是初学者,有什么错漏的地方,欢迎大家指正。
本系列文章使用的是vs2017,WebAPI版本是2。本系列大多数内容并非原创,而是来自官网的教程(https://docs.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/),如果你英文不好,可以将链接中的en-us改成zh-cn。中文版地址:https://docs.microsoft.com/zh-cn/aspnet/web-api/overview/getting-started-with-aspnet-web-api/。不过建议你可以的话,还是看英文版本,因为有些翻译是完全走样。
废话不多说,马上来看看如何新建一个WebAPI项目。打开vs2017,文件-新建-项目
选择空模板,勾选webapi
添加模型类,在右侧资源管理器的Models文件夹上右键-添加-类
类的代码如下:

namespace ProductsApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}

添加空的控制器,在Controllers文件夹上右键-添加-控制器,选择Web API2 控制器 - 空
控制器名称为:ProductsController,代码如下:

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http; namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = , Name = "Tomato Soup", Category = "Groceries", Price = },
new Product { Id = , Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = , Name = "Hammer", Category = "Hardware", Price = 16.99M }
}; public IEnumerable<Product> GetAllProducts()
{
return products;
} public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
}

这样,一个简单的WebAPI就完成了。完成后,文件结构如下:
调用的话,我们直接使用IIS新建一个网站,端口为1111
打开edge浏览器,输入地址:http://localhost:1111/api/Products,效果如下(注意,每次修改完代码后,需要重新生成一下),
一切都很简单,代码也都不复杂,不过明显有两个问题,一个是,为什么默认就调用了GetAllProducts()了,另一个是,我们明明返回一个列表的,怎么到了客户端就变成json了呢?
第一个问题,就是本文所要研究的问题。说到路由,笔者想起一桩往事。笔者自从接触asp.net以来,一直都在使用webform,即使在MVC大行其道的时候。有一次,接到一个外包项目,利用某开源社区框架做业务的扩展,由于该开源框架用的是MVC,于是就问对方的技术负责人,业务扩展项目是否也必须用MVC,对方答道,用MVC干嘛,绕来绕去不是更麻烦吗?他这句话让我深以为然,大有惺惺相惜之感。我这样说,并非要贬低MVC,更无意挑起MVC与WebForm之争,而是在遍地MVCer的情况下,还能找到WebFormer而高兴。实际上,只要能满足客户要求,谁会在意你用MVC还是WebForm呢。
废话不多说,回到我们的问题,为什么我们输入地址:http://localhost:1111/api/Products,就是在调用GetAllProducts()呢?首先,我们看看App_Start文件夹下的WebApiConfig.cs文件,这个文件是用来配置路由的,代码如下:

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}

上面的代码,实质上就是定义了一个默认的路由规则。
我们再看看Global.asax
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
很明显,在程序第一次启动的时候,我们的路由规则就被配置加载了。这个默认的规则就是 api/{controller}/{id},其中{controller}匹配一个控制类,例如ProductsController,{id}是可选的,匹配的是public方法(也就是action)的参数。那么,Controller里的public方法,也就是action该如何匹配呢?
从官方的文档可以查到这么几句话:If you are familiar with ASP.NET MVC, Web API routing is very similar to MVC routing. The main difference is that Web API uses the HTTP method, not the URI path, to select the action。意思就是,如果你熟悉MVC,那么API的路由是跟MVC的路由非常相似的。两者之间的不同,是Web API使用http方法,而非URI路径去选择action。这里的action,就是我们Controller里面的public 方法。
也就是说,默认路由api/{controller}/{id},首先匹配一个Controller类,然后用http请求方法匹配Action方法名,最后,用{id},匹配Action中的参数。
http请求方法是什么东西?如果你是传统的asp开发者,或是php开发者,相信都会非常熟悉。例如我们以前写表单html,通常都会这样写:
<form action="form_action.asp" method="get">
....
</form>
里面的method就是我们所说的http请求方法,最常见的就是get和post,get的话,就是将参数放到url上去提交,post的话,参数不会显示在url中。更多的http方法,可以点击这里。
既然知道WebAPI的默认路由,是用http请求方法去匹配控制类中的action,那么就好办了,我们在地址栏输入地址:http://localhost:1111/api/Products ,其实就是相当于在使用get方法与ProductsController中的Action进行匹配了。
然而,上面代码中,两个Action方法都没明确表明是用什么http请求方法,那怎么确定调用哪一个方法呢?get跟GetAllProducts()到底有什么关系呢,以至于GetAllProducts()可以被默认调用?或许有的人已经看出来了,没错,调用的方法GetAllProducts()那么巧,也是以Get开头的。这就是我们匹配的其中一个条件。如果Controller中,public方法的名字(也就是action的名字),是以"Get", "Post", "Put", "Delete", "Head", "Options", 或 "Patch"开头,那么按照约定,该方法(action)匹配对应的http请求方法的调用。如果开头没有上述的关键字,默认表示该方法只支持Post。
例如GetAllProducts()方法,就表示使用http的get方法调用。DeleteProduct(int id)就表示用http的deletel方法调用。由于我们调用的地址是:http://localhost:1111/api/Products,翻译成匹配规则就是,匹配ProductsController中,一个使用get,同时没有参数的Action(也就是public 方法),即GetAllProducts()。如果我们有另一个Get方法,同时也是没有参数的话,就会报错。例如,我们增加一个方法:
public string GetTest()
{
return "GetTest is called";
}
该方法明显也是匹配Get方法,同时没有参数。重新生成下项目,然后用PostMan调用一下,会发现匹配多个的错误。(PostMan的安装就不说了,很简单,不断下一步。)
我们在原来的基础上,修改一下ProductsController的代码,增加一个方法(红色字体部分),代码如下:

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http; namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = , Name = "Tomato Soup", Category = "Groceries", Price = },
new Product { Id = , Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = , Name = "Hammer", Category = "Hardware", Price = 16.99M }
}; public IEnumerable<Product> GetAllProducts()
{
return products;
} public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
} public string SayHello()
{
return "Hello,World";
}
}
}

我们在PostMan中输入地址:http://localhost:1111/Products,http请求方法,选择Post,按照我们的规则,应该会调用SayHello方法,实际效果如下:
如果我们将url改成:http://localhost:1111/api/Products/1,但方法依然是Post,那么按照上面说的,先找Post的方法,而三个方法中,只有SayHello符合,虽然后面加了id,并且值为1,由于它是可选的,所以,在post下,调用的依然是SayHello,如下图:
假如,我们将Post方法改为Get,那么就会选择调用我们的GetProduct方法,效果如下:
这个就是WebAPI默认的路由,主要使用Http请求方法来匹配Controller里的Action。而这个匹配的规则,就是使用前缀来决定哪一个最匹配,如果前缀都不是http方法,表示默认匹配Post。是不是感觉很简单呢,如果这样还觉得复杂,没关系,下面还有更简单的方法,就是属性路由。
上面的这种路由匹配规则,其实是属于约定的路由。在调用的时候,你还多多少少需要想一下,究竟url是怎样,会调用哪个方法,会不会有多个方法同时匹配等等。但是使用属性路由,你就可以完全的“精准定位”。属性路由,就是利用特性,重新定义路由。例如:

[HttpPost]
[Route("aaa/bbb")]
public IEnumerable<Product> GetAllProducts()
{
return products;
}

HttpPost强制了这个方法是需要使用Post来调用,Route强制定义了这个方法的调用路径。虽然这个方法是以Get开头,但是[HttpPost]优先级大于这个约定,我们用PostMan来测试下,我们依然先输入之前的地址:http://localhost:1111/Products,方法为Get,可以看到抛出Not Found这个错误
直到我们将地址改为:http://localhost:1111/aaa/bbb,http方法改为Post的时候,调用才成功。是不是太厉害了,我们可以随便定义访问这个方法的路由,什么约定的规则完全可以置之不理,我们可以完全实现“精准定位”,路由变得不再复杂了,一切都在我们的掌握之中。
那么,我们怎样才能使用这种属性路由呢,首先,我们要打开App_Start文件夹中的WebApiConfig.cs文件,确保一下这句代码存在:

public static void Register(HttpConfiguration config)
{
// 确保开启了属性路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}

其次,要引入命名空间:using System.Web.Http;就是这么简单,我们就可以使用属性路由。看到WebApiConfig.cs的代码,有人担心,会不会是因为config.MapHttpAttributeRoutes()代码在前,所以优先级才大于约定的路由呢,我们可以换一下次序,代码改成这样:

public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//次序在后
config.MapHttpAttributeRoutes();
}

结果发现,属性路由的优先级依然大于约定的路由,如果你还担心,大可直接删除约定的路由,将上述的代码改成这样:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
这样,你就可以完全不用考虑约定的路由对属性路由的影响,一切的映射都在你的牢牢掌握中。不过坏处就是,你的每个方法,都要显式指定http方法和路由。但我觉得这个代价是值得的,因为我们再不用花时间去绕来绕去,再不用担心增加了新方法会不会造成路由冲突等问题,我们只需定好命名规则,保证我们每个方法定义的路由不重复即可。说着说着,我也觉得自己跟文章开头说的那位程序猿一样,在走向一个极端,他是在玩命的用路由,而我是在拼命的阻止路由的多匹配性,追求唯一确定,尽量不让路由造成我的负担,也许,这也是一种风格?
剩下的都是很简单,例如,路由前缀,还是用官方的例子:

public class BooksController : ApiController
{
[Route("api/books")]
public IEnumerable<Book> GetBooks() { ... } [Route("api/books/{id:int}")]
public Book GetBook(int id) { ... } [Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
}

每个方法的路由前缀都是“api/books",是不是显得很重复,我们可以将前缀抽取,为整个控制器增加公共的前缀,代码如下:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... } // GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... } // POST api/books
[Route("")]
public HttpResponseMessage Post(Book book) { ... }
}

路由前缀的重写,我们可以使用波浪符对前缀进行重写,例如:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable<Book> GetByAuthor(int authorId) { ... } // ...
}

除此之外,还有路由约束,例如Route("api/books/{id:int}"),表示id是一个32位整数,如果是可选的,可以在后面加"?",例如Route("api/books/{id:int?}")
更详细的使用可以参考官网文档:https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
有了属性路由,我们甚至可以极端的抛弃约定的路由,从而实现”精准定位“,一切定位都可以牢牢的掌握在自己手中。相信这样,大家应该不会再害怕面对路由了吧。
【转载】Asp .Net Web Api路由路径问题的更多相关文章
- ASP.NET Web API 路由对象介绍
ASP.NET Web API 路由对象介绍 前言 在ASP.NET.ASP.NET MVC和ASP.NET Web API这些框架中都会发现有路由的身影,它们的原理都差不多,只不过在不同的环境下作了 ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
- ASP.NET Web API 框架研究 ASP.NET Web API 路由
ASP.NET Web API 核心框架是一个独立的.抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到 ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- ASP.NET Web API 路由
路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...
- Asp.Net Web APi 路由的特点
在ASP.NET Web API中,路由是基于HTTP协议 GET请求路由到以GET开头的控制器方法,POST请求路由到以POST开头的控制器方法中,GET方法和GetProducts,都能与GET请 ...
- 转载 ASP.NET Web API 学习
转载关于ASP.NET Web API 的学习网址 http://www.cnblogs.com/aehyok/p/3432158.html http://www.mashangpiao.net/Ar ...
- 剖析Asp.Net Web API路由系统---WebHost部署方式
上一篇我们剖析了Asp.Net路由系统,今天我们再来简单剖析一下Asp.Net Web API以WebHost方式部署时,Asp.Net Web API的路由系统内部是怎样实现的.还是以一个简单实例开 ...
- ASP.NET Web API路由解析
前言 本篇文章比较长,仔细思考阅读下来大约需要15分钟,涉及类图有可能在手机显示不完整,可以切换电脑版阅读. 做.Net有好几年时间了从ASP.NET WebForm到ASP.NET MVC再到ASP ...
随机推荐
- 设计模式课程 设计模式精讲 14-2 组合模式coding
1 代码演练 1.1 代码演练1(组合模式1) 1.2 代码演练2(组合模式1之完善) 1 代码演练 1.1 代码演练1(组合模式1) 需求: 打印出木木网的课程结构, 我们用一个组建类作为接口,课程 ...
- nginx_1_初始nginx
一.nginx简介: nginx是一个性能优秀的web服务器,同时还提供反向代理,负载均衡,邮件代理等功能.是俄罗斯人用C语言开发的开源软件. 二.安装nginx step1:安装依赖库 pcre(支 ...
- JavaScript图形实例:正弦曲线
正弦曲线的坐标方程为: Y=A*SIN(X) (A为振幅) 1.正弦曲线 在弧度为0~4π的正弦曲线上取360个点,将这些点用线连接起来,可以绘制出正弦曲线.编写如下的HTML代码. <! ...
- Pandas 用法汇总
一.生成数据表 1.首先导入pandas 库,一般会用到 numpy 库,所以我们先导入备用: import numpy as np import pandas as pd 2.生成 CSV 或者 x ...
- leetcode209 Minimum Size Subarray Sum
""" Given an array of n positive integers and a positive integer s, find the minimal ...
- JavaScript高级程序设计(第3版)每章小结(1-5)
第一章 JavaScript的简介 第二章 在HTML中使用JavaScript 把JavaScript插入到HTML页面中要使用<Script>元素.使用这个元素可以把JavaScrip ...
- TensorFlow Serving简介
一.TensorFlow Serving简介 TensorFlow Serving是GOOGLE开源的一个服务系统,适用于部署机器学习模型,灵活.性能高.可用于生产环境. TensorFlow Ser ...
- JForum项目搭建
JForum 是采用Java开发的功能强大且稳定的论坛系统.它提供了抽象的接口.高效的论坛引擎以及易于使用的管理界面,同时具有完全的权限控制.多语言支持(包括中文).高性能.可自定义的用户接口.安全. ...
- Matplotlib 入门
章节 Matplotlib 安装 Matplotlib 入门 Matplotlib 基本概念 Matplotlib 图形绘制 Matplotlib 多个图形 Matplotlib 其他类型图形 Mat ...
- centos6 初次安装成功,未显示eth0网卡的信息
https://www.cnblogs.com/yecao8888/p/6364830.html