Xiao Fan's Notes: Basic Knowledge of ASP.NET Core API and Axios Frontend Data Submission

2019年12月15日 54点热度 1人点赞 0条评论
内容目录

Collaborating with Colleagues on a Front-End and Back-End Decoupled Project

I've been working with colleagues on a decoupled front-end and back-end project, and I realize I need to fill in some gaps in my knowledge about Web API. While it's not necessary to learn full-stack development, having a grasp of foundational knowledge is important to design interfaces and APIs reasonably, facilitating communication with the front-end.

After returning to the dormitory at night, I looked to fill in my knowledge of Web API, focusing mainly on how various Web API methods and features can integrate with the front-end, and how to use tools to test APIs and Axios request interfaces.

This article primarily discusses how the front-end requests data from an API, and how the back-end processes and returns results. It does not cover login, cross-origin requests, or front-end UI.

Prerequisites: Basic knowledge of VUE, Axios, and a little bit of ASP.NET Core.

Tools: Visual Studio 2019 (or other versions) + Visual Studio Code + Swagger + Postman

Since Visual Studio 2019 does not provide intelligent prompts for Vue when writing ASP.NET Core pages, I need to use VSCode to write the front-end pages.

1. Microsoft Web API

| Feature | Binding Source |
| :--------------- | :----------------------------------- |
| [FromBody] | Request Body |
| [FromForm] | Form data in the request body |
| [FromHeader] | Request headers |
| [FromQuery] | Query string parameters |
| [FromRoute] | Route data from the current request |
| [FromServices] | Request services inserted as operation parameters |

Here’s an image from Postman:

HTTP requests carry many parameters that can be set on the front-end, such as forms, headers, files, cookies, sessions, tokens, etc.

The table above shows the methods or means used to retrieve data from HTTP requests. Objects like HttpContext are not discussed in this article.

The Microsoft.AspNetCore.Mvc namespace provides many attributes for configuring the behavior and action methods of Web API controllers:

| Feature | Description |
| :------------- | :---------------------------------------- |
| [Route] | Specifies the URL pattern for a controller or action |
| [Bind] | Specifies the prefixes and properties to include for model binding |
| [Consumes] | Specifies the data types accepted by an action |
| [Produces] | Specifies the data types returned by an action |
| [HttpGet] | Marks an action as supporting HTTP GET method |
| [HttpPost] | Marks an action as supporting HTTP POST method |
| ... ... ... | ... ... ... |

Creating a Web API Application

First, create an ASP.NET Core MVC application, then add an API controller named DefaultController.cs in the Controllers directory (instead of creating a Web API project, we create an MVC project and then create an API controller through MVC).

The default code upon creation is:

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

Search for Swashbuckle.AspNetCore in NuGet, or open the Package Manager Console and run the following command to install it:

Install-Package Swashbuckle.AspNetCore -Version 5.0.0-rc2

Open the Startup file and add the reference:

using Microsoft.OpenApi.Models;

The method above is from the Microsoft documentation; however, in my tests, if you search NuGet and find Swashbuckle.AspNetCore 4.0.1 or higher, you should use:

using Swashbuckle.AspNetCore.Swagger;

In ConfigureServices, add services; feel free to change the text in double quotes.

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

If you encounter an error, use:

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Add middleware:

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

            // Add the following content
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });

You can access the Swagger UI at /swagger.

To easily view outputs and fix ports, open Program.cs and modify:

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

1562163847(1)

Do not use IIS to host and run.

Note: This article exclusively uses [HttpPost]; globally, JsonResult is used as the return type.

2. Data Binding and Retrieval

1. Default without attributes

Directly writing action without attributes means that for simple types, it uses Query, while for complex types, it uses Json.
According to the official Microsoft documentation:

By default, model binding retrieves data from the following sources in key-value form from the HTTP request:
    Form fields
    Request body (for controllers with the [ApiController] attribute)
    Route data
    Query string parameters
    Uploaded files
For each target parameter or property, the source will be scanned in the order indicated in this list. There are several exceptions:
    Route data and query string values are used only for simple types.
    Uploaded files are only bound to target types that implement IFormFile or IEnumerable<IFormFile>.

This means that for Web API, if no annotations are added, simple types default to Query while complex types default to Json.
In MVC, complex types can lead to various situations based on the order of matching. Simple types remain Query.

Here we first discuss Query and will explain complex types (model classes, etc.) later.
Query:

        [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 });
        }

Open https://localhost:5123/swagger/index.html to view the UI.

1562138960(1)

In other words, creating an action with nothing added defaults to query.

Submit Data and Test the Interface via Postman

1562139085(1)

For actions using Query, here's the Axios code:

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

While searching for information online, I found that some people said data can also be added through params; however, in my tests, this did not seem to work.

Reasonably, if someone else can do it, why can't I...?

Axios code:

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

Including the following attempts, none worked.

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

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

If I change [HttpPost] to [HttpGet], it works with:

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

Hint:

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

.then is triggered upon successful requests, and .catch is triggered on failures. res contains the information returned upon a successful request, and res.data is the server's return information after the action has processed the data.

In your browser, press F12 to open the console and click Console; every time a request is made, it prints the request results and data here.

2. Using [FromBody]

The official documentation explains: the request body. [FromBody] infers for complex type parameters. [FromBody] is not suitable for any complex built-in types with special meanings, such as IFormCollection and CancellationToken. The binding source inference code will ignore these special types.

Let’s manually experiment with this since the documentation can be quite confusing.

In the beginning, I used:

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

The result during compilation indicated an error, stating that only one [FromBody] can be used, so I changed it to:

        [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 });
        }

Refresh the Swagger UI:

1562139375(1)

From the image, it can be seen that only b appears, and the dropdown in the upper right indicates that the input format for [FromBody] should be JSON upload.

This implies that the [FromBody] attribute should be placed on objects rather than fields.

Thus, I modified the program as follows:

	// Add a type    
    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 });    
    }

Now, reflecting on Microsoft’s documentation regarding [FromBody], which implies it infers for complex type parameters, it becomes clear that [FromBody] should not be used for int, string, and similar types, but should be applied to a complex type.

Also, an action should only use one [FromBody].

Open the Swagger interface (and remember to refresh it after modifications).

1562139627(1)

Now, this is the result we want; the front end submits a JSON object.

Testing with Postman:

1562139749(1)

This confirms the hypothesis! The front end submits a JSON object, adhering to JSON format specifications, and [FromBody] converts it into an 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 = Defaultaction = FFFxxx 。查询到的是代码里的真实名称。

Query 会查询到 a = 111b = 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 特性将清除未在 某个参数中列出的字段中的任何以前存在的数据。

一脸懵逼。

下面是我的踩坑过程,不感兴趣的话直接跳过吧。笔记笔记,记得当然是自己觉得要记的哈哈哈。

Create a new class

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; }
}

Create an 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 = "Validation failed" });
}

15622028717670

Using Postman for testing, it was found that it must be in JSON format to access this Action; other methods will directly return an error.

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

Tested with Postman twice

15622032271015

15622037112944

Based on testing, I suspect that

ModelState.IsValid is related to the validation rules in the model and not related to [Bind] (even though the TestBind class used for testing does not have validation rules), so you cannot use ModelState.IsValid to validate whether [Bind] is conforming to rules.

The Action parameter: [Bind("A,B,C")] TestBind test, at first I thought that the request data must include A, B, and C.

However, testing showed it was not necessary... After carefully reading the documentation: because the Bind attribute clears any previously existing data in fields not listed in a certain parameter.

I made a modification:

[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 = "Validation failed" });
}

The parameters changed to string D, string E, [Bind("A,B,C")] TestBind test.

Tested with Swagger:

15622043721294

Return result

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

Changed it to

[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 = "Validation failed" });
}

Return result

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

Documentation on the [Bind] describes primarily: preventing overposting.

Through the above tests, it is confirmed that there can be multiple parameters in one Action as

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

Notice, the conclusion below is incorrect!

Thus, D and E, because they are in addition to Test, J and Q would not be effective. According to Baidu, the Action decorated with [Bind] will have only the data within Test be valid when the frontend requests; any other Query and other forms of uploaded data will be ineffective, preventing hackers from mixing special parameters during data submission. This should be understood like this.

Initially, that was my conclusion until multiple tests revealed it to be wrong.

However, there is one thing I don't understand,

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

What is the difference between these two? I still don't quite understand.

Suddenly I thought of Query; when fields do not have characteristics, they default to Query.

Final testing code with pitfalls

Model class

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 = "Validation failed" });
}

Swagger testing

15622129564070

Postman testing

15622126298494

15622126493794

{
    "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"
}

Then in Swagger or Postman, tried various combinations of inputs.

I was confused. After testing for a long time, I couldn't find out anything.

I truly don't understand what "preventing overposting" means in [Bind].

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

I still don't quite understand the difference. I think I’ll stop here.

I will go to Stack Overflow to ask a question, here is the link: https://stackoverflow.com/questions/56884876/asp-net-core-bind-how-to-use-it/56885153#56885153

Got an answer:

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.

Well, never mind, testing it out hasn’t worked, so I’ll give up.

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

Currently, I only understand that [Consumes] and [Produces] are filters used to indicate the types of data that the Controller or Action can accept. It's basically used like this:

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

But how to actually apply them? I searched for a long time but couldn't find any results. I found an answer on Stack Overflow:

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

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

Decorating Actions to specify how the Action can be accessed and the name for access.

For example:

[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 });
    }
}

The access address is https://localhost:5123/api/Default/aaa

During use, it is affected by the Controller and Action routing.

But routing can also be controlled independently.

Taking the above controller as an example

```c#
[HttpPost("aaa")]    // Relative path

Access address xxx:xxx/api/Default/aaa

[HttpPost("/aaa")]   // Absolute path

Access address xxx:xxx/aaa

IV. Return Types

1. Query Memo Table

The Microsoft.AspNetCore.Mvc namespace contains various operation methods and types that control MVC. The author extracts types related to MVC or API return types from the namespace to generate the following table:

| Type | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------- |
| AcceptedAtActionResult | An ActionResult that returns an Accepted (202) response with a Location header. |
| AcceptedAtRouteResult | An ActionResult that returns an 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 a TValue instance or an ActionResult. |
| BadRequestObjectResult | An ObjectResult that when executed will produce a Bad Request (400) response. |
| BadRequestResult | A 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 | A 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 Status200OK response. |
| PartialViewResult | Represents an ActionResult that renders a partial view to the response. |
| PhysicalFileResult | A 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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.redire...


<br />

| Type | Description |
| -------------------- | ------------------------------------------------------------ |
| [RedirectResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.redirectresult?view=aspnetcore-2.2) | An [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) 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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.redirecttopageresult?view=aspnetcore-2.2) | An [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) that returns a Found (302) or Moved Permanently (301) response with a Location header. Targets a registered route. |
| [RedirectToRouteResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.redirecttorouteresult?view=aspnetcore-2.2) | An [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) 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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.signinresult?view=aspnetcore-2.2) | An [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) that on execution invokes AuthenticationManager.SignInAsync. |
| [SignOutResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.signoutresult?view=aspnetcore-2.2) | An [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) that on execution invokes AuthenticationManager.SignOutAsync. |
| [StatusCodeResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.statuscoderesult?view=aspnetcore-2.2) | Represents an [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) that when executed will produce an HTTP response with the given response status code. |
| [UnauthorizedObjectResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unauthorizedobjectresult?view=aspnetcore-2.2) | An [ObjectResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.objectresult?view=aspnetcore-2.2) that when executed will produce an Unauthorized (401) response. |
| [UnauthorizedResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unauthorizedresult?view=aspnetcore-2.2) | Represents an [UnauthorizedResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unauthorizedresult?view=aspnetcore-2.2) that when executed will produce an Unauthorized (401) response. |
| [UnprocessableEntityObjectResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unprocessableentityobjectresult?view=aspnetcore-2.2) | An [ObjectResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.objectresult?view=aspnetcore-2.2) that when executed will produce a Unprocessable Entity (422) response. |
| [UnprocessableEntityResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unprocessableentityresult?view=aspnetcore-2.2) | A [StatusCodeResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.statuscoderesult?view=aspnetcore-2.2) that when executed will produce a Unprocessable Entity (422) response. |
| [UnsupportedMediaTypeResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.unsupportedmediatyperesult?view=aspnetcore-2.2) | A [StatusCodeResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.statuscoderesult?view=aspnetcore-2.2) that when executed will produce a UnsupportedMediaType (415) response. |
| [ViewComponentResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.viewcomponentresult?view=aspnetcore-2.2) | An [IActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.iactionresult?view=aspnetcore-2.2) which renders a view component to the response. |
| [ViewResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.viewresult?view=aspnetcore-2.2) | Represents an [ActionResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.actionresult?view=aspnetcore-2.2) that renders a view to the response. |
| [VirtualFileResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.virtualfileresult?view=aspnetcore-2.2) | A [FileResult](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.fileresult?view=aspnetcore-2.2) that on execution writes the file specified using a virtual path to the response using mechanisms provided by the host. |


Retaining this for reference while writing WebApi hehe.

The types mainly inherit from two interfaces:

| Type                 | Description                                                         |
| -------------------- | ------------------------------------------------------------ |
| IActionResult        | Defines a contract that represents the result of an action method. |
| IViewComponentResult | Result type of a [ViewComponent](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.viewcomponent?view=aspnetcore-2.2). |

Note that some of the above are abstract classes, such as FileResult, while FileStreamResult implements FileResult. Some classes follow an inheritance relationship.


##### 2. Return Data Types

1. Specific types
2. IActionResult type
3. ActionResult type

The return data type from an action must be one of the three types listed above.


##### 3. Directly returning primitive or complex data types

```c#
[HttpGet] public IEnumerable<Product> Get() { return _repository.GetProducts(); }
4. IActionResult Type

Response status codes, JSON, redirection, URL jumps, and so forth fall under IActionResult.

The MVC Controller and the API Controller share many similarities and differences.

The API Controller inherits from ControllerBase.

The MVC Controller inherits from Controller and Controller inherits from

Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable

The Controller in the API is the most primitive.

In the API, the return types need to be instantiated, using new keyword; in MVC, the return types "do not need to be instantiated."

Of course, some, like FileResult, are abstract classes and cannot be instantiated.

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

In MVC, the action method defaults to [HttpGet], so it can be accessed without this attribute;

whereas in the API, an action without [Httpxxx] cannot be accessed by default.

痴者工良

高级程序员劝退师

文章评论