十三、视图、模型和ViewModel

在本章中,我们将介绍以下食谱:

  • 使用 AutoMapper 创建和使用 ViewModel
  • 理解和使用 ModelBinding
  • 创建我们自己的模型绑定
  • 理解和使用值提供程序
  • 配置和使用验证

使用 AutoMapper 创建和使用 ViewModel

在本食谱中,您将学习如何用AutoMapper创建和使用ViewModelAutoMapper是一个基于约定的对象到对象映射库(根据http://automapper.org/)。 我们通常使用AutoMapper来创建ViewModel数据传输对象(DTO)以及DataModel类中的更多类。 AutoMapper使得实例化一个类和填充其他类的属性变得非常容易。

准备

我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。

怎么做……

  1. 首先,让我们将AutoMapper依赖项添加到项目中。
  2. 接下来,我们将创建带有假数据的ProductRepository。 我们的HomeController将使用这个存储库中的数据。 从存储库中使用的对象将是ProductDto对象,但我们将只使用它们在存储库和控制器之间传输数据:
public class ProductDataStore
{
  public static ProductDataStore Current { get; } = new ProductDataStore();
  public List<ProductDto> Products { get; set; }
  public ProductDataStore()
  {
    Products = new List<ProductDto>()
    {
      new ProductDto { Id = 1, Name = "Laptop" },
      new ProductDto { Id = 2, Name = "Phone" },
      new ProductDto { Id = 3, Name = "Desktop" }
    };
  }
}
public interface IProductRepository
{
  ProductDto GetProduct(int id);
  IEnumerable<ProductDto> GetProducts();
  void AddProduct(ProductDto productDto);
}
public class ProductRepository : IProductRepository
{
  public List<ProductDto> Products
  {
    get
    {
      return ProductDataStore.Current.Products;
    }
  }
  public IEnumerable<ProductDto> GetProducts()
  {
    return Products.AsEnumerable();
  }
  public ProductDto GetProduct(int id)
  {
    return Products.Where(p => p.Id == id).SingleOrDefault();
  }
  public void AddProduct(ProductDto productDto)
  {
    Products.Add(productDto);
  }
}
  1. 现在,我们需要创建ProductViewModel类,我们将使用它从视图发送和检索它的属性。 这些ProductViewModel对象有时与ProductDto完全相同,但有时不同。 ViewModel包含视图中显示或从视图中检索的所有对象。 它们是比 DTO 更复杂的对象。 在这种情况下,它们将是相同的:

DTO classes usually have fewer properties than data model classes. For example, the Employee DataModel class has FirstName, LastName, Email, and Password properties, but the Employee DTO model has only FullName and Email properties.

public class ProductDto
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}
public class ProductViewModel
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}
  1. 每次从存储库获取数据或将数据发送到存储库时,我们都必须执行一项周期性的工作:将数据从Prom``ProductViewModel映射到ProductDto,反之亦然。 为了实现这一点,我们必须在Startup.cs类的Configure方法中配置我们想要自动化的映射:
public void Configure(IApplicationBuilder app, ...)
{
  ...
  AutoMapper.Mapper.Initialize(mapper =>
  {
    mapper.CreateMap<Models.ProductDto, Models.ProductViewModel>();
    mapper.CreateMap<Models.ProductViewModel, Models.ProductDto>();
  });
  ...
}
  1. 我们将受益于在Startup.cs类中为ProductRepository类及其抽象IProductRepository配置依赖注入,在ConfigureServices方法中:
services.AddScoped<IProductRepository, ProductRepository>();
  1. 现在,我们可以在控制器的动作方法中以两种方式映射ProductDtoProductViewModel:
public class HomeController : Controller
{
  private IProductRepository _productRepository;
  public HomeController(IProductRepository productRepository)
  {
    _productRepository = productRepository;
  }
  public IActionResult Index()
  {
    var productsDtoFromRepo = _productRepository.GetProducts();
    var model = Mapper.Map<IEnumerable<ProductViewModel>>
    (productsDtoFromRepo);
    return View(model);
  }
  public IActionResult GetProduct(int id)
  {
    var productDto = _productRepository.GetProduct(id);
    if (productDto == null)
    {
      return NotFound();
    }
    var productModel = Mapper.Map<ProductViewModel>(productDto);
    return Ok(productModel);
  }
  [HttpGet]
  public IActionResult AddProduct()
  {
    return View();
  }
  [HttpPost]
  public IActionResult AddProduct(ProductViewModel model)
  {
    if (ModelState.IsValid)
    {
      model.Id = _productRepository
      .GetProducts().Max(p => p.Id) + 1;
      var productDto = Mapper.Map<ProductDto>(model);
      _productRepository.AddProduct(productDto);
      return RedirectToAction("Index");
    }
    return View(model);
  }
}
  1. 最后,我们将为IndexAddProduct操作方法创建Razor视图:
    • Index.cshtml
@model List<ProductViewModel>
<br />
@foreach (var p in Model)
{
  <p>Product @p.Id : @p.Name</p>
}
@model ProductViewModel
<p>Let's add a new product</p>
@using (Html.BeginForm("AddProduct", "Home", FormMethod.Post))
{
  <div class="row">
    <div class="col-md-6">
      <div class="form-group">
        @Html.LabelFor(x => x.Name, "Product Name")
        @Html.TextBoxFor(x => x.Name,
        new { @class = "form-control" })
      </div>
      <div class="form-group">
        @Html.LabelFor(x => x.Price, "Product Price")
        @Html.TextBoxFor(x => x.Price,
        new { @class = "form-control" })
      </div>
      <div>
        <input type="submit" value="Add" />
      </div>
    </div>
  </div>
}

理解和使用 ModelBinding

在本食谱中,您将学习如何使用ModelBindingModelBinding机制允许开发人员获取/设置 UI 元素的值为类的属性,反之亦然。

准备

我们将创建一个 ASP.NET Core MVC web 应用模板文件|新|项目| AspNET Core web 应用。

怎么做……

  1. 让我们看看ModelBinding是如何处理嵌套类型的:
public class Product
{
  public int Id { get; set; }
  public int Name { get; set; }
  public decimal Price { get; set; }
 public Category Category 
  {
 get;
 set; 
  } }
public class Category {
 public int Id {
     get;
     set; 
  }
 public int Name 
  {
     get;
     set;
  } } @model R2.Models.Product
<form asp-action="Create" method="post">
  <div class="form-group">
    <label asp-for="Id"></label>
    <input asp-for="Id" class="form-control" />
  </div>
  <div class="form-group">
    <label asp-for="Name"></label>
    <input asp-for="Name" class="form-control" />
  </div>
  <div class="form-group">
    <label asp-for="Price"></label>
    <input asp-for="Price" class="form-control" />
  </div>
 <div class="form-group">
 <label asp-for="Category.Id"></label>
 <input asp-for="Category.Id" class="form-control"/>
 </div>
 <div class="form-group">
 <label asp-for="Category.Name"></label>
 <input asp-for="Category.Name" class="form-control"/>
 </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

将生成以下代码:

<div class="form-group">
  <label for="Category_Id">1</label
  <input type="text" id="Category_Id" name="Category.Id" value="" 
  class="form-control"/>
</div>
<div class="form-group">
  <label for="Category_Name">Laptop</label>
  <input type="text" id="Category_Name" name="Category.Name" value="" 
  class="form-control"/>
</div>

我们将得到Product类型与前面的形式绑定:

[HttpPost]
 public IActionResult Create(Product product)
  1. 让我们看看如何在运行中改变ModelBinding。 我们可以想象,由于任何原因(例如,我们必须在插入新产品时创建一个新类别),必须将Category对象嵌套到Product中,并将其映射到另一个CLR对象(c#类)。 由于Bind属性,我们可以在运行中修改ModelBinding:
<form asp-action="DisplayCategoryProduct" method="post">
  <div class="form-group">
    <label asp-for="Category.Id"></label>
    <input asp-for="Category.Id" class="form-control" />
  </div>
  <div class="form-group">
    <label asp-for="Category.Name"></label>
    <input asp-for="Category.Name" class="form-control" />
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

下面的代码是DisplayCategoryProduct动作方法的主体:

[HttpPost]
public IActionResult DisplayCategoryProduct
([Bind(Prefix = nameof(Product.Category))] CategoryProduct catProduct)
{ ... }
  1. 我们可以在类定义中直接使用BindRequiredBindNever属性来包含或排除来自ModelBinding的一些类属性:
public class Category
{
  [BindNever]
  public int Id { get; set; }
  [BindRequired]
  public string Name { get; set; }
}
  1. 我们可以使用ModelBinder属性覆盖默认的模型绑定。 有一些属性可以帮助我们参数化模型绑定,并向传入参数添加规则或约束,特别是当我们使用 API 控制器时。
  2. 对于来自Body请求的数据,我们可以使用[FromBody]属性:
public IActionResult PostFormApi([FromBody] Product product)
{ ... }

FromBody通常用于 Ajax 向 API 控制器提交表单数据。 默认情况下,当 JavaScript 客户端向控制器发送数据时,MVC 使用 JSON 来序列化和反序列化数据。

现在,要从 Ajax 发送数据,不需要FromBody。 为了让 Ajax 和动作一起工作,我们应该在AddProduct.cshtml页面中添加以下代码来使用 Ajax 和 jQuery 发送表单数据:

@section scripts{
  <script type="text/javascript">
    $(document).ready(function() {
      $("#productButton").click(function(){
        $.ajax({
          type: "POST",
          url: "/Home/PostToApiCtrl",
          data : $("#productForm").serialize(),
          dataType: 'json'
          //contentType: 'application/x-www-form-urlencoded; 
          charset=utf-8' //default value, 
          not mandatory to work
        });
      });
    });
  </script>
}

我们需要为控制器添加以下代码:

public async Task<IActionResult> PostToApiCtrl(Product product)
{
  if (Request.ContentType == "application/x-www-form-urlencoded; charset=UTF-8")
  {
    IFormCollection jqFormData = await Request.ReadFormAsync();
  }
  return Ok();
}

多亏了 ModelBinding,我们以Product对象的形式检索提交的表单数据,但是我们也可以使用Request.ReadFormAsync()方法以IFormCollection的形式检索它。

  1. 对于来自表单的数据,我们将使用[FromForm]属性:
public IActionResult PostFormMVC([FromForm]string name)
{ ... }

FromForm通常用于向 MVC 控制器提交经典表单数据。

  1. 对于来自 querystring 的数据,我们将使用[FromQuery]属性。 让我们创建一个引用类型,从 QueryString 参数绑定:
public class Query
{
  public int ItemsPerPage { get; set; }
  public int CurrentPage { get; set; }
}
public IActionResult GetQueryProduct([FromQuery]Query query)
{ ... }

Query类将映射到以下 URL: GET /api/GetItems?ItemsPerPage=10&CurrentPage=7 HTTP/1.1

  1. 对于来自 URL 的数据,我们将使用[FromRoute]属性:
public IActionResult SendDataFromRoute([FromRoute]string name)
{ ... }

FromRoute通常用于通过任何 HTTP 谓词将数据发送到与应用配置中(在Startup.csConfigure方法中)现有路由的 URL 段匹配的任何类型的控制器,例如http://www.mysite.com/{name}

  1. 对于来自请求头的数据,我们将使用以下代码行:
[FromHeader(Name ="...")] attribute.
public IActionResult SendDataFromHttpHeaders
([FromHeader(Name = "Accept-Language")]string language)
{ ... }

FromHeader中的Name属性可以是任何现有的请求头,如 Accept-Encoding、User-Agent 或我们创建的任何头。

下面的代码向我们展示了如何在类中使用FromHeader属性自动化 ModelBinding:

public class Headers
{
  [FromHeader]
  public string Accept { get; set; }
  [FromHeader]
  public string Referer { get; set; }
  [FromHeader(Name = "Accept-Language")]
  public string AcceptLanguage { get; set; }
  [FromHeader(Name = "User-Agent")]
  public string UserAgent { get; set; }
}
public IActionResult GetHeadersWithProduct(Headers headers)
{ ... }
  1. 对于来自配置了依赖注入的服务(在Startup.csConfigure方法中)的数据,我们将使用[FromService]属性:
public void ConfigureServices(IServiceCollection services)
{
  services.AddScoped<IProductRepository, ProductRepository>();
  services.AddMVC();
}
[HttpGet]
public IActionResult GetDataWithFromServiceParameter
([FromServices] IProductRepository repo)
{
  var products = repo.GetProducts();
  return Ok(products);
}

它是如何工作的…

ASP.NET ModelBinding 机制将动作方法参数映射到值提供者。 值提供者可以是表单数据、路由数据、QueryString 和文件。 它们可以是通过 HTTP 请求发送的任何数据。 我们也可以创建自己的价值提供者,但这将在下一个食谱中解释。

值提供者是 HTTP 请求中发布的数据。 它由 HTTP 请求主体中的所有键值对组成。 ModelBinding 机制将所提交表单的 name 字段值与类的属性进行映射,以绑定为 ActionResult 参数。

ActionResult 参数的原始类型(shortintdouble,string,char,等等),或者简单的类型(如 Guid, DateTime,或时间间隔)必须 nullable ModelBinding 解析参数时避免错误。 此外,ModelBinding 可以从 QueryString 或路由定义中定义的路由段进行解析。 它们也可以是复杂类型; 一般来说,一个 c#类和任何不能被.Parse.TryParse函数从字符串类型解析的类型。

创建我们自己的模型绑定

在本教程中,您将了解什么是模型绑定器,以及如何使用它。

准备

我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。

怎么做……

  1. 首先,让我们将AutoMapper依赖项添加到project.json
  2. 然后,我们将创建带有假数据的ProductRepository。 我们的HomeController控制器类将使用这个存储库中的数据。 从存储库中使用的对象将是ProductDto对象,但我们将只使用它们在存储库和控制器之间传输数据:
public class ProductDataStore
{
  public static ProductDataStore Current { get; } = new ProductDataStore();
  public List<ProductDto> Products { get; set; }
  public ProductDataStore()
  {
    Products = new List<ProductDto>()
    {
      new ProductDto { Id = 1, Name = "Laptop" },
      new ProductDto { Id = 2, Name = "Phone" },
      new ProductDto { Id = 3, Name = "Desktop" }
    };
  }
}
public interface IProductRepository
{
  ProductDto GetProduct(int id);
  IEnumerable<ProductDto> GetProducts();
  void AddProduct(ProductDto productDto);
}
public class ProductRepository : IProductRepository
{
  public List<ProductDto> Products
  {
    get
    {
      return ProductDataStore.Current.Products;
    }
  }
  public IEnumerable<ProductDto> GetProducts()
  {
    return Products.AsEnumerable();
  }
  public ProductDto GetProduct(int id)
  {
    return Products.Where(p => p.Id == id).SingleOrDefault();
  }
  public void AddProduct(ProductDto productDto)
  {
    Products.Add(productDto);
  }
}
  1. 下一步是创建ProductViewModel类,我们将使用它从视图发送和检索属性。 这些ProductViewModel对象有时与ProductDto完全相同,但有时不同。 类包含视图中显示或从视图中检索的所有属性。 它们可以是比DTO类更复杂的属性。 在这种情况下,它们将是相同的:
public class ProductDto
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}
public class ProductViewModel
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}
  1. 每次从存储库获取数据或将数据发送到存储库时,我们都必须执行一项周期性的工作:将数据从Prom``ProductViewModel映射到ProductDto,反之亦然。 为了实现这一点,我们必须在Startup.cs类的Configure方法中配置我们想要自动化的映射:
public void Configure(IApplicationBuilder app, ...)
{
  ...
  AutoMapper.Mapper.Initialize(mapper =>
  {
    mapper.CreateMap<Models.ProductDto, Models.ProductViewModel>();
    mapper.CreateMap<Models.ProductViewModel, Models.ProductDto>();
  });
  ...
}
  1. 我们将受益于在Startup.cs类中为ProductRepository类及其抽象IProductRepository配置依赖注入,在ConfigureServices方法中:
services.AddScoped<IProductRepository, ProductRepository>();
  1. 现在,我们需要将ProductDtoProductViewModel映射到控制器的动作方法:
public class HomeController : Controller
{
  private IProductRepository _productRepository;
  public HomeController(IProductRepository productRepository)
  {
    _productRepository = productRepository;
  }
  public IActionResult Index()
  {
    var productsDtoFromRepo = _productRepository.GetProducts();
    var model = Mapper.Map<IEnumerable<ProductViewModel>>
    (productsDtoFromRepo);
    return View(model);
  }
  public IActionResult GetProduct(int id)
  {
    var productDto = _productRepository.GetProduct(id);
    if (productDto == null)
    {
      return NotFound();
    }
    var productModel = Mapper.Map<ProductViewModel>(productDto);
    return Ok(productModel);
  }
  [HttpGet]
  public IActionResult AddProduct()
  {
    return View();
  }
  [HttpPost]
  public IActionResult AddProduct(ProductViewModel model)
  {
    if (ModelState.IsValid)
    {
      model.Id = _productRepository
      .GetProducts().Max(p => p.Id) + 1;
      var productDto = Mapper.Map<ProductDto>(model);
      _productRepository.AddProduct(productDto);
      return RedirectToAction("Index");
    }
    return View(model);
  }
}
  1. 最后,我们将为Index创建Razor视图,以及AddProduct动作方法:
    • Index.cshtml:
@model List<ProductViewModel>
<br />
@foreach (var p in Model)
{
  <p>Product @p.Id : @p.Name</p>
}
@model ProductViewModel
<p>Let's add a new product</p>
@using (Html.BeginForm("AddProduct", "Home", FormMethod.Post))
{
  <div class="row">
    <div class="col-md-6">
      <div class="form-group">
        @Html.LabelFor(x => x.Name, "Product Name")
        @Html.TextBoxFor(x => x.Name,
        new { @class = "form-control" })
      </div>
      <div class="form-group">
        @Html.LabelFor(x => x.Price, "Product Price")
        @Html.TextBoxFor(x => x.Price,
        new { @class = "form-control" })
      </div>
      <div>
        <input type="submit" value="Add" />
       </div>
    </div>
  </div>
}
  1. 现在,我们应该创建ModelBinder来自动映射ProductViewModelProductDto。 为此,我们必须创建两个类:从IModelBinder继承的AutoMapperModelBinder和从IModelBinderProvider继承的AutoMapperModelBinderProvider
public class AutoMapperModelBinder : IModelBinder {
 public Task BindModelAsync(ModelBindingContext bindingContext)
  {
    ProductDto productDto = new ProductDto();
    var modelType = bindingContext.ModelType;
    if (modelType == typeof(ProductViewModel))
    {
      var model = (ProductViewModel)bindingContext.Model;
      if(model != null)
      {
        productDto = Mapper.Map<ProductDto>(model);
      }
 bindingContext.Result = ModelBindingResult.Success(productDto);
    }
 return Task.CompletedTask;
  }
}
  1. ModelBinder实现了BindModelAsync方法,它返回一个任务。 ProductDto必须作为ModelBindingResult.Success方法的参数返回,该方法被赋值给BindingContext.Result属性:
public class AutoMapperModelBinderProvider : IModelBinderProvider {
 public IModelBinder GetBinder(ModelBinderProviderContext context)
  {
    var type = context.BindingInfo.BinderType;
    if (type != typeof(ProductViewModel))
    {
      return null;
    }
 return new AutoMapperModelBinder();
  }
}
  1. 前面的ModelBinderProvider类将确保ModelBinder将仅应用于ProductViewModel类型。 我们还需要供应商按照Startup.csConfigureServices方法为整个应用注册ModelBinder:
public void ConfigureServices(IServiceCollection services)
{
  services.AddScoped<IProductRepository, ProductRepository>();
 services.AddMVC(config => { config.ModelBinderProviders.Insert(0,new AutoMapperModelBinderProvider());
 }); }

当我们添加新的ModelBinderProvider类时,我们必须将它作为ModelBinderProviders列表中的第一个元素添加,以确保它将首先执行。

理解和使用值提供程序

在本食谱中,您将了解什么是值提供程序,以及如何使用它。

准备

我们将创建一个 ASP.NET Core MVC Web 应用模板文件|新|项目| AspNET Core Web 应用(。 Web 应用。

怎么做……

ASP.NET ModelBinding 机制将动作方法参数映射到值提供者。 值提供者可以是 form data、route data、QueryString 和 Files。 它们可以是通过 HTTP 请求发送的任何数据,但不是惟一的。

创建自定义值提供程序的一个有趣的例子是为 HTTP 头、cookie 值等创建值提供程序; 但是,我们也可以创建一个ValueProvider类来检索AppSettingsConnectionString设置。

  1. 让我们从创建继承自IValueProviderValueProvider类开始。 这个类将实现IValueProvider中的两个方法:

    • bool ContainsPrefix(string prefix),该方法告诉我们ValueProvider是否能够返回与ModelBinder的属性匹配的值。
    • ValueProviderResult GetValue(string key),此方法检索该值,并通过ValueProviderResult类返回该值
  2. 接下来,我们需要创建一个从IValueProviderFactory接口继承的ValueProviderFactory类。

这个类实现了如下一个方法:

Task CreateValueProviderAsync(ValueProviderFactoryContext context)
  1. 为了确保一切正常工作,我们必须将ValueProviderFactory类添加到应用中可用的ValueProviders列表中。 我们将在Startup.csConfigureServices方法中添加以下代码:
services.AddMVC(config =>
{
  config.ValueProviderFactories.Add(new CustomValueProviderFactory());
});

配置和使用验证

在本教程中,您将学习如何使用 jQuery 配置和使用客户端验证,以及使用 c#配置和使用服务器端验证。

准备

我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。

怎么做……

  1. 首先,让我们创建一个具有验证属性的类。 这些属性将用于客户端和服务器端:
public class ProductViewModel
{
  public int Id { get; set; }
  [Required]
  [RegularExpression(@"^[a-zA-Z]{1,40}$",
  ErrorMessage = "The field must be a string")]
  public string Name { get; set; }
  [Required]
  [Range(0.01, double.MaxValue,
  ErrorMessage = "Please enter a positive number")]
  public decimal Price { get; set; }
}
  1. 除此之外,我们将创建一个表单来发布ProductViewModel。 这是TagHelpersForm版本:
<form asp-controller="Home" asp-action="AddProduct" method="post" asp-antiforgery="true">
  <div asp-validation-summary="None"></div>
  <div class="form-group">
    <labelasp-for="Name"></label>
    <inputtype="text" asp-for="Name" />
    <spanasp-validation-for="Name" class="text-danger"></span>
  </div>
  <div class="form-group">
    <labelasp-for="Price"></label>
    <inputtype="text" asp-for="Price" />
    <spanasp-validation-for="Price" class="text-danger"></span>
  </div>
  <div>
    <input type="submit" value="Add" />
  </div>
</form>

这是Razor版本:

@using (Html.BeginForm("AddProduct", "Home", FormMethod.Post))
{
  @Html.AntiForgeryToken()
  <div class="row">
    <div class="col-md-6">
      @Html.ValidationSummary(false)
      <div class="form-group">
        @Html.LabelFor(x => x.Name, "Product Name")
        @Html.TextBoxFor(x => x.Name,
        new { @class = "form-control" })
        @Html.ValidationMessageFor(x => x.Name)
      </div>
      <div class="form-group">
        @Html.LabelFor(x => x.Price, "Product Price")
        @Html.TextBoxFor(x => x.Price,
        new { @class = "form-control" })
        @Html.ValidationMessageFor(x => x.Price)
      </div>
      <div>
        <input type="submit" value="Add" />
      </div>
    </div>
  </div>
}

它们都生成相同的 HTML 代码,并且在控制器中发布相同的数据。 对于两者,我们必须添加对jquery.validate的引用:

@section scripts{
  <script src="~/lib/jquery-validation/dist/jquery.validate.js">
  </script>
  <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"
  </script>
}

ValidationSummary不是强制性的,但是TagHelper版本中的asp-validation-for属性和Razor版本中的ValidationMessageForHtmlHelpers 是强制性的,以启用客户端和服务器验证。

  1. 然后,让我们通过在表单中输入值来测试客户端验证。 我们可以看到,验证在没有任何 HTTP 请求的情况下工作:

  1. 现在,让我们输入一些正确的值,并将表单发送给控制器:

  1. 我们可以看到,ModelState是有效的:

  1. 接下来,我们将添加一个个性化的消息错误,并创建错误,看看发生了什么:
public IActionResult AddProduct(ProductViewModel model)
{
  if (!model.Name.Contains("tv"))
  {
    ModelState.AddModelError(nameof(model.Name),
    "The model name must contain the word 'tv'");
  }
  if (ModelState.IsValid)
  {
    ...
  }
}
  1. 我们现在意识到,由于动态添加了这个新的约束,ModelState不再有效:

  1. 作为回报,我们看到新消息已经添加到 UI 上:

我们刚刚使用ModelState.AddModelError方法为Model属性添加了一个约束和一条错误消息。

我们还可以使用ModelState.GetValidationState("PropertyName")获得属性的验证状态。