十三、视图、模型和ViewModel
在本章中,我们将介绍以下食谱:
- 使用 AutoMapper 创建和使用 ViewModel
- 理解和使用 ModelBinding
- 创建我们自己的模型绑定
- 理解和使用值提供程序
- 配置和使用验证
使用 AutoMapper 创建和使用 ViewModel
在本食谱中,您将学习如何用AutoMapper
创建和使用ViewModel
。 AutoMapper
是一个基于约定的对象到对象映射库(根据http://automapper.org/)。 我们通常使用AutoMapper
来创建ViewModel
、数据传输对象(DTO)以及DataModel
类中的更多类。 AutoMapper
使得实例化一个类和填充其他类的属性变得非常容易。
准备
我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。
怎么做……
- 首先,让我们将
AutoMapper
依赖项添加到项目中。 - 接下来,我们将创建带有假数据的
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);
}
}
- 现在,我们需要创建
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; }
}
- 每次从存储库获取数据或将数据发送到存储库时,我们都必须执行一项周期性的工作:将数据从
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>();
});
...
}
- 我们将受益于在
Startup.cs
类中为ProductRepository
类及其抽象IProductRepository
配置依赖注入,在ConfigureServices
方法中:
services.AddScoped<IProductRepository, ProductRepository>();
- 现在,我们可以在控制器的动作方法中以两种方式映射
ProductDto
和ProductViewModel
:
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);
}
}
- 最后,我们将为
Index
和AddProduct
操作方法创建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
在本食谱中,您将学习如何使用ModelBinding
。 ModelBinding
机制允许开发人员获取/设置 UI 元素的值为类的属性,反之亦然。
准备
我们将创建一个 ASP.NET Core MVC web 应用模板文件|新|项目| AspNET Core web 应用。
怎么做……
- 让我们看看
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)
- 让我们看看如何在运行中改变
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)
{ ... }
- 我们可以在类定义中直接使用
BindRequired
和BindNever
属性来包含或排除来自ModelBinding
的一些类属性:
public class Category
{
[BindNever]
public int Id { get; set; }
[BindRequired]
public string Name { get; set; }
}
- 我们可以使用
ModelBinder
属性覆盖默认的模型绑定。 有一些属性可以帮助我们参数化模型绑定,并向传入参数添加规则或约束,特别是当我们使用 API 控制器时。 - 对于来自
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
的形式检索它。
- 对于来自表单的数据,我们将使用
[FromForm]
属性:
public IActionResult PostFormMVC([FromForm]string name)
{ ... }
FromForm
通常用于向 MVC 控制器提交经典表单数据。
- 对于来自 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
- 对于来自 URL 的数据,我们将使用
[FromRoute]
属性:
public IActionResult SendDataFromRoute([FromRoute]string name)
{ ... }
FromRoute
通常用于通过任何 HTTP 谓词将数据发送到与应用配置中(在Startup.cs
的Configure
方法中)现有路由的 URL 段匹配的任何类型的控制器,例如http://www.mysite.com/{name}
。
- 对于来自请求头的数据,我们将使用以下代码行:
[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)
{ ... }
- 对于来自配置了依赖注入的服务(在
Startup.cs
的Configure
方法中)的数据,我们将使用[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 参数的原始类型(short
、int
、double
,string
,char
,等等),或者简单的类型(如 Guid, DateTime,或时间间隔)必须 nullable ModelBinding 解析参数时避免错误。 此外,ModelBinding 可以从 QueryString 或路由定义中定义的路由段进行解析。 它们也可以是复杂类型; 一般来说,一个 c#类和任何不能被.Parse
或.TryParse
函数从字符串类型解析的类型。
创建我们自己的模型绑定
在本教程中,您将了解什么是模型绑定器,以及如何使用它。
准备
我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。
怎么做……
- 首先,让我们将
AutoMapper
依赖项添加到project.json
。 - 然后,我们将创建带有假数据的
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);
}
}
- 下一步是创建
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; }
}
- 每次从存储库获取数据或将数据发送到存储库时,我们都必须执行一项周期性的工作:将数据从
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>();
});
...
}
- 我们将受益于在
Startup.cs
类中为ProductRepository
类及其抽象IProductRepository
配置依赖注入,在ConfigureServices
方法中:
services.AddScoped<IProductRepository, ProductRepository>();
- 现在,我们需要将
ProductDto
和ProductViewModel
映射到控制器的动作方法:
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);
}
}
- 最后,我们将为
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>
}
- 现在,我们应该创建
ModelBinder
来自动映射ProductViewModel
与ProductDto
。 为此,我们必须创建两个类:从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;
}
}
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();
}
}
- 前面的
ModelBinderProvider
类将确保ModelBinder
将仅应用于ProductViewModel
类型。 我们还需要供应商按照Startup.cs
的ConfigureServices
方法为整个应用注册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
类来检索AppSettings
或ConnectionString
设置。
-
让我们从创建继承自
IValueProvider
的ValueProvider
类开始。 这个类将实现IValueProvider
中的两个方法:bool ContainsPrefix(string prefix)
,该方法告诉我们ValueProvider
是否能够返回与ModelBinder
的属性匹配的值。ValueProviderResult GetValue(string key)
,此方法检索该值,并通过ValueProviderResult
类返回该值
-
接下来,我们需要创建一个从
IValueProviderFactory
接口继承的ValueProviderFactory
类。
这个类实现了如下一个方法:
Task CreateValueProviderAsync(ValueProviderFactoryContext context)
- 为了确保一切正常工作,我们必须将
ValueProviderFactory
类添加到应用中可用的ValueProviders
列表中。 我们将在Startup.cs
的ConfigureServices
方法中添加以下代码:
services.AddMVC(config =>
{
config.ValueProviderFactories.Add(new CustomValueProviderFactory());
});
配置和使用验证
在本教程中,您将学习如何使用 jQuery 配置和使用客户端验证,以及使用 c#配置和使用服务器端验证。
准备
我们将创建一个 ASP.NET Core MVC Web 应用模板通过去文件|新|项目| AspNET Core Web 应用。
怎么做……
- 首先,让我们创建一个具有验证属性的类。 这些属性将用于客户端和服务器端:
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; }
}
- 除此之外,我们将创建一个表单来发布
ProductViewModel
。 这是TagHelpers
的Form
版本:
<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
版本中的ValidationMessageFor
HtmlHelpers 是强制性的,以启用客户端和服务器验证。
- 然后,让我们通过在表单中输入值来测试客户端验证。 我们可以看到,验证在没有任何 HTTP 请求的情况下工作:
- 现在,让我们输入一些正确的值,并将表单发送给控制器:
- 我们可以看到,
ModelState
是有效的:
- 接下来,我们将添加一个个性化的消息错误,并创建错误,看看发生了什么:
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)
{
...
}
}
- 我们现在意识到,由于动态添加了这个新的约束,
ModelState
不再有效:
- 作为回报,我们看到新消息已经添加到 UI 上:
我们刚刚使用ModelState.AddModelError
方法为Model
属性添加了一个约束和一条错误消息。
我们还可以使用ModelState.GetValidationState("PropertyName")
获得属性的验证状态。
版权属于:月萌API www.moonapi.com,转载请注明出处