# 模块二 基础巩固 MVC终结点

## 2.3.4 Web API -- MVC终结点 <a href="#id-234webapimvc-zhong-jie-dian" id="id-234webapimvc-zhong-jie-dian"></a>

* MVC与MVVM
* 模型绑定
* 自定义模型绑定器
* 模型验证
* 返回数据处理

### MVC与MVVM <a href="#mvc-yu-mvvm" id="mvc-yu-mvvm"></a>

#### MVC <a href="#mvc" id="mvc"></a>

ASP.NET Core MVC 概述：<https://docs.microsoft.com/zh-cn/aspnet/core/mvc/overview?view=aspnetcore-5.0>

![](https://3083743005-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8gwpNo3eyzHkX0O40HRA%2Fuploads%2FX0HgiLpJgVjBAOg6CEce%2F192.jpg?alt=media\&token=b6417edd-d9a6-4996-ab4d-559ffe86f6db)

#### MVVM <a href="#mvvm" id="mvvm"></a>

ASP.NET Core 中的 Razor Pages 介绍：<https://docs.microsoft.com/zh-cn/aspnet/core/razor-pages/?view=aspnetcore-5.0&tabs=visual-studio>

Razor Pages 没有 Controller，Model 中可以包含方法

![](https://3083743005-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8gwpNo3eyzHkX0O40HRA%2Fuploads%2FNF7SknJtfGzzaee0OLO1%2F193.jpg?alt=media\&token=a2751b99-29cd-494c-aaf3-1af090ee3c51)

#### ASP.NET Core MVC 注入 <a href="#aspnetcoremvc-zhu-ru" id="aspnetcoremvc-zhu-ru"></a>

```
services.AddControllers();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
```

#### MVC Endpoint <a href="#mvc-endpoint" id="mvc-endpoint"></a>

![](https://3083743005-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8gwpNo3eyzHkX0O40HRA%2Fuploads%2FbUZ8YQzOKhasjpL8yU6j%2F194.jpg?alt=media\&token=3026e5a9-e1cb-4a71-9f04-85c2ed71ba2c)

### 模型绑定 <a href="#mo-xing-bang-ding" id="mo-xing-bang-ding"></a>

* 什么是模型绑定
* 来源有哪些
* 复杂的数据绑定

ASP.NET Core 中的模型绑定：<https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0>

#### 什么是模型绑定 <a href="#shen-me-shi-mo-xing-bang-ding" id="shen-me-shi-mo-xing-bang-ding"></a>

控制器和 Razor 页面处理来自 HTTP 请求的数据。 例如，路由数据可以提供一个记录键，而发布的表单域可以为模型的属性提供一个值。 编写代码以检索这些值，并将其从字符串转换为 .NET 类型不仅繁琐，而且还容易出错。

模型绑定会自动化该过程。 模型绑定系统：

* 从各种源（如路由数据、表单域和查询字符串）中检索数据。
* Razor在方法参数和公共属性中向控制器和页面提供数据。
* 将字符串数据转换为 .NET 类型。
* 更新复杂类型的属性。

#### 来源有哪些 <a href="#lai-yuan-you-na-xie" id="lai-yuan-you-na-xie"></a>

* \[FromQuery] -从查询字符串获取值。
* \[FromRoute] -从路由数据中获取值。
* \[FromForm] -从已发布的表单字段中获取值。
* \[FromBody] -从请求正文中获取值。
* \[FromHeader] -从 HTTP 标头中获取值。

从路由数据中获取值

```
[HttpGet]
[Route("option/{id}")]
public IActionResult GetOption([FromRoute] int id)
{
    return Ok(new {id});
}
```

从查询字符串获取值

```
[HttpGet]
[Route("option/{id}")]
public IActionResult GetOption([FromRoute] int id, [FromQuery] string name)
{
    return Ok(new {id, name});
}
```

从 HTTP 标头中获取值

```
[HttpGet]
[Route("option/{id}")]
public IActionResult GetOption([FromRoute] int id, [FromQuery] string name,[FromHeader] string termId)
{
    return Ok(new {id, name, termId});
}
```

从已发布的表单字段中获取值

```
[HttpPost]
[Route("option/from")]
public IActionResult CreateOption([FromForm] string name, [FromForm] string id)
{
    return Ok(new {name, id});
}
```

从请求正文中获取值

```
[HttpPost]
[Route("option/body")]
public IActionResult CreateOption([FromBody] string name)
{
    return Ok(name);
}
```

#### 复杂的数据绑定 <a href="#fu-za-de-shu-ju-bang-ding" id="fu-za-de-shu-ju-bang-ding"></a>

* 对象
* 集合
* 字典

对象

```
public class Student
{
    public int Id { get; set; }

    public string Name { get; set; }
}

[HttpPost]
[Route("option/body")]
public IActionResult CreateOption([FromBody] Student student)
{
    return Ok(student);
}
```

字典

```
[HttpGet]
[Route("option")]
public IActionResult GetOption([FromQuery] Dictionary<int, string> dic)
{
    var students = new List<Student>();

    foreach (var item in dic)
    {
        students.Add(new Student {Id = item.Key, Name = item.Value});
    }

    return Ok(students);
}
```

启动程序，访问：[https://localhost:5001/config/option?dic\[1001\]=ming$dic\[1002\]=rank\&dic\[1003\]=abc](https://localhost:5001/config/option?dic%5B1001%5D=ming$dic%5B1002%5D=rank\&dic%5B1003%5D=abc)

输出：

```
[{"id":1001,"name":"ming$dic[1002]=rank"},{"id":1003,"name":"abc"}]
```

### 自定义模型绑定器 <a href="#zi-ding-yi-mo-xing-bang-ding-qi" id="zi-ding-yi-mo-xing-bang-ding-qi"></a>

ASP.NET Core 中的自定义模型绑定：<https://docs.microsoft.com/zh-cn/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0>

ModelBinder

```
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string GitHub { get; set; }
    public string Twitter { get; set; }
    public string BlogUrl { get; set; }
}

public class AuthorEntityBinder : IModelBinder
```

ModelBinderProvider

```
public class AuthorEntityBinderProvider : IModelBinderProvider

services.AddControllers(options =>
{
    options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
```

### 模型验证 <a href="#mo-xing-yan-zheng" id="mo-xing-yan-zheng"></a>

* 什么是模型验证
* 模型验证的特性与消息
* FluentValidation

#### 什么是模型验证 <a href="#shen-me-shi-mo-xing-yan-zheng" id="shen-me-shi-mo-xing-yan-zheng"></a>

ASP.NET Core MVC 和页面中的模型验证 Razor：<https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-5.0>

Web 应用负责检查 ModelState.IsValid 并做出相应响应

```
if (!ModelState.IsValid)
{
    return Page();
}
```

#### 模型验证的特性与消息 <a href="#mo-xing-yan-zheng-de-te-xing-yu-xiao-xi" id="mo-xing-yan-zheng-de-te-xing-yu-xiao-xi"></a>

* \[CreditCard]：验证属性是否具有信用卡格式。
* \[Compare]：验证模型中的两个属性是否匹配。
* \[EmailAddress]：验证属性是否具有电子邮件格式。
* \[Phone]：验证属性是否具有电话号码格式。
* \[Range]：验证属性值是否在指定的范围内。
* \[RegularExpression]：验证属性值是否与指定的正则表达式匹配。
* \[Required]：验证字段是否不为 null。
* \[StringLength]：验证字符串属性值是否不超过指定长度限制。
* \[Url]：验证属性是否具有 URL 格式。
* \[Remote]：通过在服务器上调用操作方法来验证客户端上的输入。

\[Required] \[Range]

```
public class Student
{
    [Required]
    [Range(1,10,ErrorMessage = "id 为 1-10 之间的数字")]
    public int Id { get; set; }

    public string Name { get; set; }
}
```

ModelState

```
[HttpPost]
[Route("option/body")]
public IActionResult CreateOption([FromBody] Student student)
{
    if (!ModelState.IsValid)
    {
        return ValidationProblem();
    }

    return Ok(student);
}
```

#### FluentValidation <a href="#fluentvalidation" id="fluentvalidation"></a>

不同场景下同一个模型有不同的验证规则，最好将模型与验证分开

表达式写法：

```
public class CustomerValidator : AbstractValidator<Customer> {
  public CustomerValidator() {
    RuleFor(x => x.Surname).NotEmpty();
    RuleFor(x => x.Forename).NotEmpty().WithMessage("Please specify a first name");
    RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount);
    RuleFor(x => x.Address).Length(20, 250);
    RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
  }

  private bool BeAValidPostcode(string postcode) {
    // custom postcode validating logic goes here
  }
}
```

Installation：<https://docs.fluentvalidation.net/en/latest/installation.html>

```
Install-Package FluentValidation
```

StudentValidator

```
namespace HelloApi.Validations
{
    public class StudentValidator : AbstractValidator<Student>
    {
        public StudentValidator()
        {
            RuleFor(s => s.Id).InclusiveBetween(1,10).WithMessage("id需要在1和10之间");
        }
    }
}
```

ASP.NET Core Getting Started：<https://docs.fluentvalidation.net/en/latest/aspnet.html>

```
dotnet add package FluentValidation.AspNetCore
```

ConfigureServices

单个添加

```
services.AddControllers()
    .AddFluentValidation();

// 通过依赖注入的方式（单个添加）
services.AddTransient<IValidator<Student>, StudentValidator>();
```

全部添加

```
// 通过扫描程序集的方式（全部添加）
services.AddControllers()
    .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<StudentValidator>());
```

### 返回数据处理 <a href="#fan-hui-shu-ju-chu-li" id="fan-hui-shu-ju-chu-li"></a>

* 返回数据类型
* 格式化响应数据

#### 返回数据类型 <a href="#fan-hui-shu-ju-lei-xing" id="fan-hui-shu-ju-lei-xing"></a>

ASP.NET Core Web API 中控制器操作的返回类型：<https://docs.microsoft.com/zh-cn/aspnet/core/web-api/action-return-types?view=aspnetcore-5.0>

* 特定类型
* IActionResult
* ActionResult

特定类型：最简单的操作返回基元或复杂数据类型（如 string 或自定义对象类型）

IActionResult：常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)

```
[HttpPost]
[Route("option/body")]
public IActionResult CreateOption([FromBody] Student student)
{
    if (!ModelState.IsValid)
    {
        return ValidationProblem();
    }

    //return BadRequest();

    //return NotFound();

    return Ok(student);
}
```

#### 格式化响应数据 <a href="#ge-shi-hua-xiang-ying-shu-ju" id="ge-shi-hua-xiang-ying-shu-ju"></a>

设置 ASP.NET Core Web API 中响应数据的格式：<https://docs.microsoft.com/zh-cn/aspnet/core/web-api/advanced/formatting?view=aspnetcore-5.0>

浏览器和内容协商

```
services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;// 浏览器和内容协商
});
```

添加 XML 格式支持

```
services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true; // 浏览器和内容协商
})
.AddXmlSerializerFormatters() // 添加 XML 格式支持
.AddFluentValidation();
```

启动程序，添加 XML Headers 访问：

![](https://3083743005-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8gwpNo3eyzHkX0O40HRA%2Fuploads%2FKl2O08sS24QBGFJuecUN%2F195.jpg?alt=media\&token=130613d4-5234-4f10-aaed-b0542ba9d15d)

添加基于 Newtonsoft.Json 的 JSON 格式支持

添加 nuget 包：Microsoft.AspNetCore.Mvc.NewtonsoftJson

```
services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true; // 浏览器和内容协商
})
.AddNewtonsoftJson()// 添加基于 Newtonsoft.Json 的 JSON 格式支持
.AddXmlSerializerFormatters() // 添加 XML 格式支持
.AddFluentValidation();
```

### GGitHub源码链接： <a href="#github-yuan-ma-lian-jie" id="github-yuan-ma-lian-jie"></a>

<https://github.com/MingsonZheng/ArchitectTrainingCamp/tree/main/HelloApi>
