IActionHttpMethodProvider 介面的結構很簡單,實現該介面只要實現一個屬性即可——HttpMethods。該屬性是一個字串序列。
這啥意思呢?這個字串序列代表的就是受支援的 HTTP 請求方式。比如,如果此屬性返回 GET POST,那麼被修飾的物件既支援 HTTP-GET 請求,也支援 HTTP-POST 請求。咱們在寫 Web API 時最熟悉的這幾個特性類就是實現了 IActionHttpMethodProvider 介面。
[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[HttpHead]
[HttpPatch]
[HttpOptions]
這幾個特性類不僅實現了 IActionHttpMethodProvider 介面,還實現了 IRouteTemplateProvider。所以它們可以當 [Route] 特性來用,又可以限制 HTTP 請求方式,一舉兩得。
咱們在實際專案中,如果希望一個操作方法同時支援多種請求方法,可以疊加使用以上特性類。比如
[HttpPut] [HttpPost] public float GetAFloat()
還可以自己定義一個特性類,實現 IActionHttpMethodProvider 介面,從 HttpMethods 屬性返回一組請求方式。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class CustHttpmethodsAttribute : Attribute, IActionHttpMethodProvider { private readonly string[] _httpmethods; // 建構函式 public CustHttpmethodsAttribute(params string[] httpmethods) { _httpmethods = httpmethods; } // 這個是實現介面的成員 public IEnumerable<string> HttpMethods => _httpmethods; }
很簡單的一個類,HttpGet 等特性類只能應用到操作方法上,這裡老周把限制放寬一些,讓其可以應用到類和方法上,即可以用在控制器和操作方法上面。
通過建構函式的引數可以傳遞一個或 N 個HTTP請求方式。正因為如此,該特性類在同一個目標上就不需要多次應用了,所以,我把 AllowMultiple 設定為 false。
接下來,測試一下這廝能不能用。
[CustHttpmethods("PUT", "POST"), Route("manage/[action]")] public class StudentController : ControllerBase { public IActionResult AddNew(Student stu) { // 要是 StuSerail 或 Name 屬性沒找到 // IsValid 就返回 false if(!ModelState.IsValid) { return Content("資料無效"); } return Content($"新增成功!\n學號:{stu.StuSerial}\n姓名:{stu.Name}\n年齡:{stu.Age}\n微信名稱:{stu.WXName}"); } }
下面是 Student 類的定義。
public class Student { /// <summary> /// 學號 /// </summary> [BindRequired] public long StuSerial { get; set; } /// <summary> /// 姓名 /// </summary> [BindRequired] public string? Name { get; set; } = "宇宙人"; /// <summary> /// 微信名字 /// </summary> public string? WXName { get; set; } = "釣魚佬"; /// <summary> /// 年齡 /// </summary> public int Age { get; set; } = 0; }
應用了 BindRequired 特性的意思就是:在模型繫結時,如果沒能在使用者端提交的資料中找到這些屬性的值,那麼模型的繫結狀態(Model State)就會設定 IsValid 屬性為 false。在 Student 類中,StuSerial 和 Name 屬性要求必須繫結有效。
根據上述程式碼的設計,Student 控制器中的所有操作都只允許 HTTP-PUT 和 HTTP-POST 兩種請求。下面咱們來驗證一下,前面寫的 CustHttpmethods 特性是否生效。
這裡老周用 .NET Tools 提供的 http-repl 工具來測試——嗯,別多想,肯定是個命令列工具。如果你還沒安裝,可以用這條命令安裝它。
dotnet tool install -g Microsoft.dotnet-httprepl
其中,-g 表示安裝到使用者的預設路徑中。
用法:httprepl http://localhost:1254,回車後進入互動模式,只要輸入相關命令即可。如 get post delete put patch 等,幫助資訊可以用 help 來檢視。
咱們把 echo 選項開啟,這樣該工具就會顯示請求的 HTTP 訊息。
echo on
執行應用程式,試試 HTTP-GET 能不能存取。
get /manage/addnew?stuserial=123456&name=小吳&age=22
得到的響應訊息如下:
顯然,GET 方法無法通過。
那試試 POST 。
post /manage/addnew -h "Content-Type=application/x-www-form-urlencoded" -c stu.stuserial=123456&stu.name=小明&stu.age=24&stu.wxname=二哈
-h 表示要新增的 HTTP 訊息頭,多個頭可以多次使用-h,總之一個頭用一次-h;-c 表示正文(body)。
這一次提交很順利,得到伺服器的正確迴應。
正文部分的 stu 字首可以省略。
stuserial=123456&name=小高&age=20&wxname=工具人
POST 請求沒有問題,再試試 PUT。
put /manage/addnew -h Content-Type:application/x-www-form-urlencoded -c stuserial=67980&name=小張&age=19&wxname=掃雷冠軍
請求也成功完成,伺服器有正確的響應。
咱們繼續實驗。剛才測試的都是標準的 HTTP 請求方式,要是咱們來個非規範的會怎麼樣呢?比如,弄個叫 「SET」 的請求方法。
[CustHttpmethods("SET"), Route("manage/[action]")] public class StudentController : ControllerBase { …… }
實驗繼續。
這一次咱們不能用 http-repl 工具了,因為 SET 不是規範的請求方式,測試工具不支援。但可以寫個控制檯應用程式來測試。
// 這一行主要是為了等伺服器執行起來 // 當此專案與伺服器專案一起啟動時用得上 await Task.Delay(1000); Uri rootUrl = new("https://localhost:12550"); HttpClient client = new(); // form-data IDictionary<string, string> data = new Dictionary<string, string>() { ["stuserial"] = "76008", ["name"] = "小青", ["age"] = "19", ["wxname"] = "南方小鱘" }; FormUrlEncodedContent content = new FormUrlEncodedContent(data); HttpRequestMessage reqmsg = new HttpRequestMessage(); // 這個不是標準的,得自己寫上 reqmsg.Method = new HttpMethod("SET"); reqmsg.RequestUri = new Uri(rootUrl, "/manage/addnew"); reqmsg.Content = content; // 傳送 var resp = await client.SendAsync(reqmsg); Console.WriteLine($"響應程式碼:{(int)resp.StatusCode}"); Console.WriteLine($"響應內容:\n{await resp.Content.ReadAsStringAsync()}"); // 這兩行只是為了讓程式能停下來罷了,沒其他用途 Console.WriteLine(); Console.ReadKey();
雖然是非標準的請求方式,但的確可用。
好了,今天咱們就聊到這兒了。