【ASP.NET Core】MVC控制器的各種自定義:IActionHttpMethodProvider 接口

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();

雖然是非標準的請求方式,但的確可用。

 

 

好了,今天咱們就聊到這兒了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章