小范筆記:ASP.NET Core API 基礎知識與Axios前端提交資料

痴者工良發表於2019-07-04

跟同事合作前後端分離專案,自己對 WebApi 的很多知識不夠全,雖說不必要學全棧,可是也要了解基礎知識,才能合理設計介面、API,方便與前端交接。

晚上回到宿舍後,對 WebApi 的知識查漏補缺,主要補充了 WebAPi 的一些方法、特性等如何與前端契合,如何利用工具測試 API 、Axios 請求介面。

本文主要寫 WebApi 前端請求資料到 API 、後端返回處理結果,不涉及登入、跨域請求、前端 UI 等。(難一點我不會了。。。看張隊的公眾號,篇篇都看不懂。。。)

前提:會一點點 VUE、會一點 Axios、會一點點 Asp.net Core。

工具:Visual Studio 2019(或者其它版本) + Visual Studio Code + Swagger +Postman

由於 Visual Studio 2019 寫 ASP.NET Core 頁面時,沒有 Vue 的智慧提示,所以需要使用 VSCode 來寫前端頁面。

本文 程式碼 已釋出到 GitHub https://github.com/whuanle/CZGL.IKonwWebApi

 

一. 微軟WebApi

特性繫結源
[FromBody] 請求正文
[FromForm] 請求正文中的表單資料
[FromHeader] 請求標頭
[FromQuery] 請求查詢字串引數
[FromRoute] 當前請求中的路由資料
[FromServices] 作為操作引數插入的請求服務

來一張 Postman 的圖片:

HTTP 請求中,會攜帶很多引數,這些引數可以在前端設定,例如表單、Header、檔案、Cookie、Session、Token等。

那麼,上面的表格正是用來從 HTTP 請求中獲取資料的 “方法” 或者說 “手段”。HttpContext 等物件不在本文討論範圍。

Microsoft.AspNetCore.Mvc 名稱空間提供很多用於配置Web API 控制器的行為和操作方法的屬性:

特性說明
[Route] 指定控制器或操作的 URL 模式。
[Bind] 指定要包含的字首和屬性,以進行模型繫結。
[Consumes] 指定某個操作接受的資料型別。
[Produces] 指定某個操作返回的資料型別。
[HttpGet] 標識支援 HTTP GET 方法的操作。
[HttpPost] 標識支援 HTTP POST 方法的操作。
... ... ... ... ... ...

WebApi 應用

首先建立一個 Asp.Net Core MVC 應用,然後在 Controllers 目錄新增一個 API 控制器 DefaultController.cs。(這裡不建立 WebApi 而是 建立 MVC,通過 MVC 建立 API 控制器)。

建立後預設程式碼:

[Route("api/[controller]")]
[ApiController]
public class DefaultController : ControllerBase
{
}
1. 安裝 Swagger

在 Nuget 中搜尋 Swashbuckle.AspNetCore,或開啟 程式包管理器控制檯 -> 程式包管理器控制檯 ,輸入以下命令進行安裝

Install-Package Swashbuckle.AspNetCore -Version 5.0.0-rc2

開啟 Startup 檔案,新增引用

using Microsoft.OpenApi.Models;

在 ConfigureServices 中新增服務,雙引號文字內容隨便改。

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
            });

新增中介軟體

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

			// 新增下面的內容
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });

訪問 /swagger 可以訪問到 Swagger 的 UI 介面。

為了便於檢視輸出和固定埠,開啟 Progarm,cs ,修改內容

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .UseUrls("https://*:5123")
                .UseStartup<Startup>();

1562163847(1)

不要使用 IIS 託管執行。

注意:本文全部使用 [HttpPost] ;全域性使用 JsonResult 作為返回型別。

二. 資料繫結與獲取

1,預設不加

直接寫 action,不使用特性

        [HttpPost("aaa")]
        public async Task<JsonResult> AAA(int? a, int? b)
        {
            if (a == null || b == null)
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 2000, result = a + "|" + b });
        }

開啟 https://localhost:5123/swagger/index.html 檢視 UI 介面 1562138960(1)

也就是說,建立一個 action ,什麼都不加,預設是 query

通過 Postman 提交資料、測試介面

1562139085(1)

對於 Query 的 action 來說, axios 的寫法

    postaaa: function () {
                    axios.post('/api/default/aaa?a=111&b=222'
                    )
                        .then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })
                }

在網上查詢資料時,發現有人說通過 params 新增資料也可以,不過筆者測試,貌似不行。

講道理,別人可以,為啥我不行。。。

axios 程式碼:

  postaaa: function () {
                    axios.post('/api/default/aaa', {
                        params: {
                            a: 123,
                            b: 234
                        }
                    }
                    )
                        .then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })
                }

包括下面的,都試過了,不行。

    axios.post('/api/default/aaa', {
                            a:1234,
                            b:1122
                    }
                    
                    
    axios.post('/api/default/aaa', {
                        data:{
                            a:1234,
                            b:1122
                        }
                    }

把 [HttpPost] 改成 [HttpGet] ,則可以使用

axios.post('/api/default/aaa', {
                        params: {
                            a: 123,
                            b: 234
                        }
                    }
                    ... ...

提示:

		... ...
		.then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })

.then 當請求成功時觸發,請求失敗時觸發 catch 。res 是請求成功後返回的資訊,res.data 是請求成功後伺服器返回的資訊。即是 action 處理資料後返回的資訊。

在瀏覽器,按下 F12 開啟控制檯,點選 Console ,每次請求後,這裡會列印請求結果和資料。

2, [FromBody]

官方文件解釋:請求正文。[FromBody] 針對複雜型別引數進行推斷。 [FromBody] 不適用於具有特殊含義的任何複雜的內建型別,如 IFormCollection 和 CancellationToken。 繫結源推理程式碼將忽略這些特殊型別。

算了,看得一頭霧水,手動實際試試。

剛剛開始的時候,我這樣使用:

        public async Task<JsonResult> BBB([FromBody]int? a, [FromBody]int? b)

結果編譯時就報錯,提示只能使用一個 [FromBody],於是改成

        [HttpPost("bbb")]
        public async Task<JsonResult> BBB([FromBody]int? a, int? b)
        {
            if (a == null || b == null) 
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 2000, result = a + "|" + b });
        }

開啟 Swagger UI 介面,重新整理一下

1562139375(1)

從圖片中發現,只有 b,沒有 a,而且右上角有下拉框,說明了加 [FromBody] 是 json 上傳。

那麼說明 [FromBody] 修飾得應當是物件,而不是 欄位。

修改程式如下:

	// 增加一個型別
    public class AppJson
    {
        public int? a { get; set; }
        public int? b { get; set; }
    }
    [HttpPost("bbb")]
    public async Task<JsonResult> BBB([FromBody]AppJson ss)
    {
        if (ss.a == null || ss.b == null) 
            return new JsonResult(new { code = 0, result = "aaaaaaaa" });
        return new JsonResult(new { code = 2000, result = ss.a + "|" + ss.b });
    }

再看看微軟的文件:[FromBody] 針對複雜型別引數進行推斷。,這下可理解了。。。

即是不應該對 int、string 等型別使用 [FromBody] ,而應該使用一個 複雜型別

而且,一個 action 中,應該只能使用一個 [FromBody] 。

開啟 Swagger 介面(有修改需要重新整理下介面,下面不再贅述)。

1562139627(1)

這樣才是我們要的結果嘛,前端提交的是 Json 物件。

用 Postman 測試下

1562139749(1)

證實了猜想,嘿嘿,嘿嘿嘿。

前端提交的是 Json 物件,遵循 Json 的格式規範,那麼 [FromBody] 把它轉為 Object 物件。

前端 axios 寫法:

            methods: {
                postaaa: function () {
                    axios.post('/api/default/bbb', {
                        "a": 4444,
                        "b": 5555
                    })
                        .then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })
                }
            }
3, [FromForm]
        [HttpPost("ccc")]
        public async Task<JsonResult> CCC([FromForm]int? a, [FromForm]int? b)
        {
            if (a == null || b == null)
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 200, result = a + "|" + b });
        }

當然,這樣寫也行,多個欄位或者物件都可以

        [HttpPost("ccc")]
        public async Task<JsonResult> CCC([FromForm]AppJson ss)
        {
            if (ss.a == null || ss.b == null)
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 200, result = ss.a + "|" + ss.b });
        }

1562141896(1)

根據提示,使用 Postman 進行測試

0187f3234bb69a6eea144a3a16ee5d8

事實上,這樣也行 ↓

form-data 和 x-www.form-urlencoded 都是鍵值形式,檔案 form-data 可以用來上傳檔案。具體的區別請自行查詢。

df8a45f6c95af394ae2fdbb269f9ae2

axios 寫法(把 Content-Type 欄位修改成 form-data 或 x-www.form-urlencoded )

 postccc: function () {
                    let fromData = new FormData()
                    fromData.append('a', 111)
                    fromData.append('b', 222)
                    axios.post('/api/default/ccc', fromData, {
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded'
                        }
                    })
                        .then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })
                }
4, [FromHeader]

[FromHeader] 不以表單形式上傳,而是跟隨 Header 傳遞引數。

        [HttpPost("ddd")]
        public async Task<JsonResult> DDD([FromHeader]int? a, [FromHeader]int? b)
        {
            if (a == null || b == null)
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 200, result = a + "|" + b });
        }

1562144122(1)

axios 寫法

postddd: function () {
                    axios.post('/api/default/ddd', {}, {
                        headers: {
                            a: 123,
                            b: 133
                        }
                    })
                        .then(res => {
                            console.log(res.data)
                            console.log(res.data.code)
                            console.log(res.data.result)
                        })
                        .catch(err => {
                            console.error(err);
                        })
                }

需要注意的是,headers 的引數,必須放在第三位。沒有要提交的表單資料,第二位就使用 {} 代替。

params 跟隨 url 一起在第一位,json 或表單資料等引數放在第二位,headers 放在第三位。

由於筆者對前端不太熟,這裡有說錯,麻煩大神評論指出啦。

5, [FromQuery]

前面已經說了,Action 引數不加修飾,預設就是 [FromQuery] ,參考第一小節。

有個地方需要記住, Action 引數不加修飾。預設就是 [FromQuery] ,有時幾種引數並在一起放到 Action 裡,會忽略掉,除錯時忘記了,造成麻煩。

6, [FromRoute]

獲取路由規則,這個跟前端上傳的引數無關;跟 URL 可以說有關,又可以說無關。

        [HttpPost("fff")]
        public async Task<JsonResult> FFFxxx(int a,int b,
                                             [FromRoute]string controller,
                                             [FromRoute]string action)
        {
            // 這裡就不處理 a和 b了
            return new JsonResult(new { code = 200, result = controller+"|"+action });
        }

1562147096

[FromRoute] 是根據路由模板獲取的,上面 API 的兩個引數和路由模板的名稱是對應的:

[FromRoute]string controller, [FromRoute]string action
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

當然,還可以加個 [FromRoute]int? id

[FromRoute] 和 [FromQuery] 區別

以此 URL 為例

https://localhost:5123/api/Default/fff?a=111&b=22

Route 會查到 controller = Default ,action = FFFxxx 。查詢到的是程式碼裡的真實名稱。

Query 會查詢到 a = 111 和 b = 22

那麼,如果路由規則裡,不在 URL 裡出現呢?

        [HttpPost("/ooo")]
        public async Task<JsonResult> FFFooo(int a, int b,
                                             [FromRoute]string controller,
                                             [FromRoute]string action)
        {
            // 這裡就不處理 a和 b了
            return new JsonResult(new { code = 200, result = controller + "|" + action });
        }

那麼,訪問地址變成 https://localhost:5123/ooo

通過 Postman ,測試

df8a45f6c95af394ae2fdbb269f9ae2

說明了 [FromRoute] 獲取的是程式碼裡的 Controller 和 Action 名稱,跟 URL 無關,根據測試結果推斷跟路由表規則也無關。

7, [FromService]

參考 https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/dependency-injection?view=aspnetcore-2.2

這個是與依賴注入容器有關,跟 URL 、路由等無關。

新建一個介面、一個類

    public interface ITest
    {
        string GGG { get; }
    }
    public class Test : ITest
    {
        public string GGG { get { return DateTime.Now.ToLongDateString(); } }
    }

在 ConfigureServices 中 注入

            services.AddSingleton<ITest, Test>();

在 DefaultController 中,建立建構函式,然後

        private readonly ITest ggg;
        public DefaultController(ITest ttt)
        {
            ggg = ttt;
        }

新增一個 API

        [HttpPost("ggg")]
        public async Task<JsonResult> GGG([FromServices]ITest t)
        {
            return new JsonResult(new { code = 200, result = t.GGG });
        }

訪問時,什麼引數都不需要加,直接訪問此 API 即可。

1562148774(1)

[FromService] 跟後端的程式碼有關,跟 Controller 、Action 、URL、表單資料等無關。

小結:

特性可以幾種放在一起用,不過儘量每個 API 的引數只使用一種特性。

優先取值 Form > Route > Query

IFromFile 由於檔案的上傳,本文就不談這個了。

關於資料繫結,更詳細的內容請參考:

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2

三. action 特性方法

Microsoft.AspNetCore.Mvc 名稱空間提供可用於配置 Web API 控制器的行為和操作方法的屬性。

下表是針對於 Controller 或 Action 的特性.

特性說明
[Route] 指定控制器或操作的 URL 模式。
[Bind] 指定要包含的字首和屬性,以進行模型繫結。
[Consumes] 指定某個操作接受的資料型別。
[Produces] 指定某個操作返回的資料型別。
[HttpGet] 標識支援 HTTP GET 方法的操作。
... ...

下面使用這些屬性來指定 Controller 或 Action 接受的 HTTP 方法、返回的資料型別或狀態程式碼。

1, [Route]

在微軟文件中,把這個特性稱為 屬性路由 ,定義:屬性路由使用一組屬性將操作直接對映到路由模板。

請教了大神,大神解釋說,ASP.NET Core 有路由規則表,路由表是全域性性、唯一性的,在程式執行時,會把所有路由規則收集起來。

MVC 應用中設定路由的方法有多種,例如

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
 [Route("Home/Index")]
   public IActionResult Index()
   {
      return View();
   }
    [Route("api/[controller]")]
    [ApiController]
    public class DefaultController : ControllerBase
    {
    }

路由是全域性唯一的,可以通過不同形式使用,但是規則不能發生衝突,程式會在編譯時把路由表收集起來。

根據筆者經驗,發生衝突,應該就是在編譯階段直接報錯了。(注:筆者不敢確定)

關於路由,請參考 :

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2#token-replacement-in-route-templates-controller-action-area

2, [Bind]

筆者知道這個是繫結模型的,但是對原理不太清楚。ASP.NET Core 自動生成的可讀寫的 Controller ,預設都是使用 [Bind] 來繫結資料。

文件定義:用於對複雜型別的模型繫結。

有下面幾種相近的特性:

  • [BindRequired]
  • [BindNever]
  • [Bind]

微軟文件提示:如果釋出的表單資料是值的源,則這些屬性會影響模型繫結。

就是說,上面的特性是針對類、介面等複雜型別(下面統稱模型),對於 int、string 這些型別,可能出毛病。

[BindRequired] 、[BindNever] 只能應用於模型的屬性,如

    public class TestB
    {
        [BindNever]
        public int ID { get; set; }

        [BindRequired]
        public string Name { get; set; }
    }

但是 [BindRequired] 、[BindNever] 不在討論範圍內,這裡只說 [Bind]。

[Bind] 用於類或方法(Controller、Action),指定模型繫結中應包含的模型屬性。

在微軟官方文件,對於[Bind] 的解釋:

  • [Bind] 屬性可用於防止“建立”方案中的過多釋出情況 。 由於排除的屬性設定為 NULL 或預設值,而不是保持不變,因此它在編輯方案中無法很好地工作;
  • 因為 Bind 特性將清除未在 某個 引數中列出的欄位中的任何以前存在的資料。

一臉懵逼。

下面是我的踩坑過程,不感興趣的話直接跳過吧。筆記筆記,記得當然是自己覺得要記的哈哈哈。

新建一個類

    public class TestBind
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
        public string D { get; set; }
        public string E { get; set; }
        public string F { get; set; }
        public string G { get; set; }
    }

新建 API

        [HttpPost("hhh")]
        public async Task<JsonResult> HHH([Bind("A,B,C")] TestBind test)
        {
            if (ModelState.IsValid == true)
                return new JsonResult(test);
            return new JsonResult(new { Code = 0, Result = "驗證不通過" });
        }

15622028717670

使用 Postman 進行,測試,發現必須使用 Json 形式,才能訪問到這個 Action ,其它方式會直接 返回 錯誤。

{
    "errors": {
        "": [
            "A non-empty request body is required."
        ]
    },
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "0HLO03IFQFTQU:00000007"
}

通過兩次 Postman 進行測試

15622032271015

15622037112944

經過測試,我猜想

ModelState.IsValid 跟模型裡的驗證規則有關係,跟 [Bind] 沒關係(儘管用於測試的 TestB 類中沒有寫驗證規則),因此不能使用 ModelState.IsValid 驗證 [Bind] 是否符合規則。

Action 的引數:[Bind("A,B,C")] TestBind test,剛開始的時候我以為請求的資料中必須包含 A、B、C。

測試後發現不是。。。再認真看了文件 :因為 Bind 特性將清除未在 某個 引數中列出的欄位中的任何以前存在的資料。

我修改一下:

        [HttpPost("hhh")]
        public async Task<JsonResult> HHH(
            string D, string E,[Bind("A,B,C")] TestBind test)
        {
            if (ModelState.IsValid == true)
                return new JsonResult(new { data1 = test, data2 = D, data3 = E });
            return new JsonResult(new { Code = 0, Result = "驗證不通過" });
        }

引數變成了 string D, string E,[Bind("A,B,C")] TestBind test

使用 Swagger 進行測試:

15622043721294返回結果

{
  "data1": {
    "a": "string",
    "b": "string",
    "c": "string",
    "d": "string",
    "e": "string",
    "f": "string",
    "g": "string"
  },
  "data2": null,
  "data3": null
}

改成

        [HttpPost("hhh")]
        public async Task<JsonResult> HHH([Bind("A,B,C")] TestBind test, string J, string Q)
        {
            if (ModelState.IsValid == true)
                return new JsonResult(new { data1 = test, data2 = J, data3 = Q });
            return new JsonResult(new { Code = 0, Result = "驗證不通過" });
        }

返回結果

{
  "data1": {
    "a": "string",
    "b": "string",
    "c": "string",
    "d": "string",
    "e": "string",
    "f": "string",
    "g": "string"
  },
  "data2": null,
  "data3": null
}

文件中對 [Bind] 描述最多的是:防止過多釋出。

通過上面的測試,首先肯定的是一個 Action 裡,有多個引數 如

[Bind("A,B,C")] TestBind test, string D, string E string J, string Q

注意,下面的結論是錯的!

那麼 D、E 因為於 除了 Test, J、Q就會無效,通過百度,[Bind] 修飾的 Action ,前端請求的資料只有 Test 裡面的資料有效,其它 Query等形式一併上傳的資料都會失效,防止黑客在提交資料時摻雜其它特殊引數。應該就是這樣理解吧。

上面是一開始我的結論,直到多次測試,我發現是錯的。

可是有一個地方不明白,

[Bind("A,B,C")]
[Bind("A,B,C,D,E,F,G")]

這兩者的區別是是什麼。還是沒搞清楚。

突然想到 Query,當欄位沒有使用特性修飾時,預設為 Query 。

最終踩坑測試程式碼

模型類

    public class TestBind
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
        public string D { get; set; }
        public string E { get; set; }
        public string F { get; set; }
        public string G { get; set; }
    }

Action

        [HttpPost("hhh")]
        public async Task<JsonResult> HHH(
            string A, string B,
            string E, string F, string G,
            [Bind("A,B,C,D")] TestBind test,
             string C, string D,
             string J, string Q)
        {
            if (ModelState.IsValid == true)
                return new JsonResult(new
                {
                    data1 = test,
                    dataA = A,
                    dataB = B,
                    dataC = C,
                    dataD = D,
                    dataE = E,
                    dataF = F,
                    dataG = G,
                    dataJ = J,
                    dataQ = Q
                });
            return new JsonResult(new { Code = 0, Result = "驗證不通過" });
        }

Swagger 測試

15622129564070

Postman 測試

15622126298494

15622126493775

{
    "data1": {
        "a": "111",
        "b": "111",
        "c": "111",
        "d": "111",
        "e": "111",
        "f": "111",
        "g": "111"
    },
    "dataA": "222",
    "dataB": "222",
    "dataC": "222",
    "dataD": "222",
    "dataE": "222",
    "dataF": "222",
    "dataG": "222",
    "dataJ": "222",
    "dataQ": "222"
}

再在 Swagger 或 Postman ,換著法子嘗試各種不同組合的輸入。

我懵逼了。試了半天試不出什麼。

實在不理解 [Bind] 裡,“防止過多釋出” 是什麼意思

[Bind("A,B,C")]
[Bind("A,B,C,D,E,F,G")]

這兩者的區別是是什麼。還是沒搞清楚。算了,不踩了。

我再到 stackoverflow 提問題,地址 https://stackoverflow.com/questions/56884876/asp-net-core-bind-how-to-use-it/56885153#56885153

獲得一個回答:

What's the difference between [Bind("A,B,C")] and [Bind("A,B,C,D,E,F,G")]?

The former tells the model binder to include only the properties of TestBind named A, B and C. The latter tells the model binder to include those same properties plus D, E, F and G.

Are you testing by posting data for all properties of your model? You should notice that the values you post for the excluded properties are not bound.

算了,嘿嘿,測試不出來,放棄。

3, [Consumes]、[Produces]
        [Consumes("application/json")]
        [Produces("application/json")]
        [Produces("application/xml")] 
        [Produces("text/html")]
		... ...

目前只瞭解到 [Consumes]、[Produces] 是篩選器,用來表示 Controller 或 Action 所能接受的資料型別。大概就是像下面這樣使用:

    [Consumes("application/json")]
    [Produces("application/json")]
    public class DefaultTestController : ControllerBase
    {

    }

但是如何實際應用呢?我找了很久,都沒有找到什麼結果。在 stackoverflow 找到一個回答:

https://stackoverflow.com/questions/41462509/adding-the-produces-filter-globally-in-asp-net-core

4, [HttpGet]、[HttpPost]、[HttpDelete]、[HttpPut]

修飾 Action ,用來標識這個 Action 能夠通過什麼方式訪問、訪問名稱。

例如:

    [Route("api/[controller]")]
    [ApiController]
    public class DefaultController : ControllerBase
    {
        [HttpPost("aaa")]
        public async Task<JsonResult> AAA(int? a, int? b)
        {
            if (a == null | b == null)
                return new JsonResult(new { code = 0, result = "aaaaaaaa" });
            return new JsonResult(new { code = 200, result = a + "|" + b });
        }
    }

訪問地址 https://localhost:5123/api/Default/aaa

使用時,會受到 Controller 和 Action 路由的影響。

但 本身亦可控制路由。以上面的控制器為例

[HttpPost("aaa")]    //相對路徑

訪問地址 xxx:xxx/api/Default/aaa

[HttpPost("/aaa")]   //絕對路徑

訪問地址 xxx:xxx/aaa

四,返回型別

1, 查詢備忘表

Microsoft.AspNetCore.Mvc 名稱空間中,包含控制 MVC 的各種操作方法和型別,筆者從名稱空間中抽出與 MVC 或 API 返回型別有關的型別,生成表格:

型別描述
AcceptedAtActionResult An ActionResult that returns a Accepted (202) response with a Location header.
AcceptedAtRouteResult An ActionResult that returns a Accepted (202) response with a Location header.
AcceptedResult An ActionResult that returns an Accepted (202) response with a Location header.
AcceptVerbsAttribute Specifies what HTTP methods an action supports.
ActionResult A default implementation of IActionResult.
ActionResult A type that wraps either an TValue instance or an ActionResult.
BadRequestObjectResult An ObjectResult that when executed will produce a Bad Request (400) response.
BadRequestResult StatusCodeResult that when executed will produce a Bad Request (400) response.
ChallengeResult An ActionResult that on execution invokes AuthenticationManager.ChallengeAsync.
ConflictObjectResult An ObjectResult that when executed will produce a Conflict (409) response.
ConflictResult StatusCodeResult that when executed will produce a Conflict (409) response.
ContentResult  
CreatedAtActionResult An ActionResult that returns a Created (201) response with a Location header.
CreatedAtRouteResult An ActionResult that returns a Created (201) response with a Location header.
CreatedResult An ActionResult that returns a Created (201) response with a Location header.
EmptyResult Represents an ActionResult that when executed will do nothing.
FileContentResult Represents an ActionResult that when executed will write a binary file to the response.
FileResult Represents an ActionResult that when executed will write a file as the response.
FileStreamResult Represents an ActionResult that when executed will write a file from a stream to the response.
ForbidResult An ActionResult that on execution invokes AuthenticationManager.ForbidAsync.
JsonResult An action result which formats the given object as JSON.
LocalRedirectResult An ActionResult that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), or Permanent Redirect (308) response with a Location header to the supplied local URL.
NotFoundObjectResult An ObjectResult that when executed will produce a Not Found (404) response.
NotFoundResult Represents an StatusCodeResult that when executed will produce a Not Found (404) response.
OkObjectResult An ObjectResult that when executed performs content negotiation, formats the entity body, and will produce a Status200OK response if negotiation and formatting succeed.
OkResult An StatusCodeResult that when executed will produce an empty Status200OKresponse.
PartialViewResult Represents an ActionResult that renders a partial view to the response.
PhysicalFileResult FileResult on execution will write a file from disk to the response using mechanisms provided by the host.
RedirectResult An ActionResult that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), or Permanent Redirect (308) response with a Location header to the supplied URL.
RedirectToActionResult An ActionResult that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), or Permanent Redirect (308) response with a Location header. Targets a controller action.
RedirectToPageResult An ActionResult that returns a Found (302) or Moved Permanently (301) response with a Location header. Targets a registered route.
RedirectToRouteResult An ActionResult that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), or Permanent Redirect (308) response with a Location header. Targets a registered route.
SignInResult An ActionResult that on execution invokes AuthenticationManager.SignInAsync.
SignOutResult An ActionResult that on execution invokes AuthenticationManager.SignOutAsync.
StatusCodeResult Represents an ActionResult that when executed will produce an HTTP response with the given response status code.
UnauthorizedObjectResult An ObjectResult that when executed will produce a Unauthorized (401) response.
UnauthorizedResult Represents an UnauthorizedResult that when executed will produce an Unauthorized (401) response.
UnprocessableEntityObjectResult An ObjectResult that when executed will produce a Unprocessable Entity (422) response.
UnprocessableEntityResult StatusCodeResult that when executed will produce a Unprocessable Entity (422) response.
UnsupportedMediaTypeResult StatusCodeResult that when executed will produce a UnsupportedMediaType (415) response.
ViewComponentResult An IActionResult which renders a view component to the response.
ViewResult Represents an ActionResult that renders a view to the response.
VirtualFileResult FileResult that on execution writes the file specified using a virtual path to the response using mechanisms provided by the host.

留著寫 WebApi 時查詢備忘嘿嘿。

那些型別主要繼承的兩個介面:

型別描述
IActionResult Defines a contract that represents the result of an action method.
IViewComponentResult Result type of a ViewComponent.

注意的是,上面有些是抽象類,例如 FileResult,而 FileStreamResult 實現了 FileResult 。有些類是繼承關係。

2, 返回的資料型別
  1. 特定型別
  2. IActionResult 型別
  3. ActionResult 型別

Action 的 return ,返回的資料型別必定是上面三種。

3, 直接返回基元或複雜資料型別
[HttpGet]
public IEnumerable<Product> Get()
{
    return _repository.GetProducts();
}
4, IActionResult 型別

響應狀態碼、Json、重定向、URL 跳轉等,屬於 IActionResult。

MVC 的 Controller 與 API 的 Controller 有很多相同的地方,亦有很多不同的地方。

API 的 Controller 繼承 ControllerBase

MVC 的 Controller 繼承 Controller而 Controller 繼承

Controller :   ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable

API 裡的 Controller 是最原始的。

API 裡的 返回型別需要例項化, new 一下; MVC 裡的返回型別,“不需要例項化”。

當然,有些例如 FileResult 是抽象類,不能被例項化。

API:

        [HttpGet("returnaaa")]
        public async Task<IActionResult> ReturnAAA()
        {
            return new ViewResult();  
            return new JsonResult(new { code="test"});
            return new RedirectToActionResult("DefaultController","ReturnAAA","");
            return new NoContentResult("666");
            return new NotFoundResult();
            ...
        }

MVC

        public async Task<IActionResult> Test()
        {
            return View();
            return Json(new { code = "test" });
            return RedirectToAction("DefaultController", "ReturnAAA", "");
            return NoContent("666");
            return NotFound();
            ...
        }

MVC 中,Action 預設是 [HttpGet],不加也可以被訪問到;

而 API 的Action,不加 [Httpxxx],則預設不能被訪問到。

相關文章