ASP.NET Core 如何使用表单

原文:Working with Forms
作者:Rick AndersonDave PaquetteJerrie Pelser
翻译:姚阿勇(Dr.Yao)
校对:孟帅洋(书缘)

这篇文章演示了如何使用表单以及表单中常用的 HTML 元素。HTML 的 Form 元素提供了 Web 应用向服务器回发数据的主要机制。本文的大部分在描述 Tag Helpers 以及它们如何能帮你有效地构建健壮的表单。在阅读本文之前,我们建议你阅读一下 Tag Helpers

在很多情况下,HTML Helpers 都提供了对某个 Tag Helper 的替代方法,但重要的是必须意识到 Tag Helper 不是要取代 HTML Helper,而且也并不是每个 HTML Helper 都有对应的 Tag Helper。当一个 HTML Helper 作为替代方案存在时,是有意为之的。

章节:

Form Tag Helper

表单 Form 的 Tag Helper:

  • 为 MVC 控制器 Action 或已命名的路由生成 HTML action 属性值。

  • 生成一个隐藏的 请求验证标记 来防止跨站请求伪装(当在 HTTP Post 操作方法上应用了 [ValidateAntiForgeryToken] 特性时)。

  • 提供 asp-route-<参数名> 属性, <参数名> 是路由里面添加过的值。 Html.BeginFormHtml.BeginRouteFormrouteValues 参数提供了类似的功能。

  • 有 HTML Helper 替代方法 Html.BeginFormHtml.BeginRouteForm

示例:

<form asp-controller="Demo" asp-action="Register" method="post"><!-- Input and Submit elements --></form>

上面的 Form Tag Helper 生成如下的 HTML :

<form method="post" action="/Demo/Register"><!-- Input and Submit elements --><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

MVC 运行时(runtime)根据 Form Tag Helper 的属性 asp-controllerasp-action 生成 action 属性值。Form Tag Helper 也会生成一个隐藏的 请求验证标记 来防止跨站请求伪装(当在HTTP Post 方法上应用了 [ValidateAntiForgeryToken] 特性时)。要保护纯 HTML 避免跨站请求伪装是非常困难的,Form Tag Helper 为你提供了这个服务。

使用命名路由

Tag Helper 属性 asp-route 也能为 HTML action 属性生成标记。一个应用含有名为 register路由 可以在注册页面使用如下标记:

<form asp-route="register" method="post"><!-- Input and Submit elements --></form>

Views/Account 文件夹下的很多视图(在你创建一个带有 个人用户账户 的新 Web 应用时生成的)都含有 asp-route-returnurl 属性:

<form asp-controller="Account" asp-action="Login"
  asp-route-returnurl="@ViewData["ReturnUrl"]"
  method="post" class="form-horizontal" role="form">

注意
采用内建的模版,只有在你尚未经过验证或授权的情况下去尝试访问需授权的资源时,returnUrl 才会被自动填入。当你尝试一个未授权的访问,安全中间件会根据 returnUrl 的设置将你重定向到登录页面。

Input Tag Helper

Input Tag Helper将 HTML <input> 元素绑定到 Razor 视图中的模型表达式上。

语法:

<input asp-for="<Expression Name>"/>

Input Tag Helper:

  • asp-for 属性中指定的表达式名称生成 idname HTML 属性。 asp-for="Property1.Property2" 等价于 m => m.Property1.Property2 ,就是说属性值实际上是表达式的一部分。 asp-for 属性值所使用的就是表达式的名称。

  • 基于模型类型和应用在模型属性上的 数据注释 特性来设置 HTML type 的属性值。

  • 如果 HTML type 属性已被指定,则不会覆盖它。

  • 根据应用在模型属性上的 数据注释 特性生成 HTML5 验证属性。

  • 与 HTML Helper Html.TextBoxFor and Html.EditorFor 功能重叠。详情可参见 Input Tag Helper 的 HTML Helper 替代方法 一节。

An error occurred during the compilation of a resource required to process
this request. Please review the following specific error details and modify
your source code appropriately.

Type expected
 'RegisterViewModel' does not contain a definition for 'Email' and no
 extension method 'Email' accepting a first argument of type 'RegisterViewModel'
 could be found (are you missing a using directive or an assembly reference?)

Input Tag Helper基于 .NET 类型来设置 HTML type 属性。下表列出了一些常见的 .NET 类型和生成出的 HTML 类型(并非所有 .NET 类型都在列)。

Booltype="checkbox"
Stringtype="text"
DateTimetype="datetime"
Bytetype="number"
Inttype="number"
Single, Doubletype="number"

下表列出了 Input Tag Helper会将其映射到指定 Input 类型的一些常见 数据注释 特性(并非所有特性都在列)。

[EmailAddress]type="email"
[Url]type="url"
[HiddenInput]type="hidden"
[Phone]type="tel"
[DataType(DataType.Password)]type="password"
[DataType(DataType.Date)]type="date"
[DataType(DataType.Time)]type="time"

示例:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email Address")]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}
@model RegisterViewModel<form asp-controller="Demo" asp-action="RegisterInput" method="post">
    Email:  <input asp-for="Email"/><br/>
    Password: <input asp-for="Password"/><br/><button type="submit">Register</button></form>

上述代码生成如下的 HTML :

<form method="post" action="/Demo/RegisterInput">
    Email:    <input type="email" data-val="true"           data-val-email="The Email Address field is not a valid e-mail address."           data-val-required="The Email Address field is required."           id="Email" name="Email" value=""/><br>
    Password:    <input type="password" data-val="true"           data-val-required="The Password field is required."           id="Password" name="Password"/><br><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

EmailPassword 属性上应用的数据注释在该模型上生成元数据。Input Tag Helper读取模型元数据并生成 HTML5data-val-* 属性(详见 Model Validation)。这些属性对验证器进行描述使其附加到 Input 字段上。这提供了 unobtrusive 的 HTML5 和 jQuery 验证。

替代 Input Tag Helper 的 Html Helper

Html.TextBoxHtml.TextBoxForHtml.EditorHtml.EditorFor 有着与 Input Tag Helper 重复的功能。Input Tag Helper 会自动设置 type 属性;Html.TextBoxHtml.TextBoxFor 则不会。Html.EditorHtml.EditorFor 会处理集合、复杂对象以及模版;Input Tag Helper 则不会。Input Tag Helper 、Html.EditorForHtml.TextBoxFor 是强类型的(它们使用 lambda 表达式);Html.TextBoxHtml.Editor 则不是(它们使用表达式名称)。

表达式名称

asp-for 属性值是一个 ModelExpression 同时也是 lambda 表达式右边的部分。因此,你不需要使用 Model 前缀,因为 asp-for="Property1" 在生成的代码中会变成 m => m.Property1

@{
    var joe = "Joe";
}<input asp-for="@joe"/>

生成以下代码:

<input type="text" id="joe" name="joe" value="Joe"/>

定位子属性

你还可以通过视图模型的属性路径定位到子属性。考虑这个更复杂的模型,它包含了一个 Address 子属性。

 public class AddressViewModel
 {
     public string AddressLine1 { get; set; }
 }
public class RegisterAddressViewModel
 {
     public string Email { get; set; }

     [DataType(DataType.Password)]
     public string Password { get; set; }

     public AddressViewModel Address { get; set; }
 }

在视图中,我们绑定了 Address.AddressLine1

@model RegisterAddressViewModel<form asp-controller="Demo" asp-action="RegisterAddress" method="post">
    Email:  <input asp-for="Email"/><br/>
    Password: <input asp-for="Password"/><br/>
    Address: <input asp-for="Address.AddressLine1"/><br/><button type="submit">Register</button></form>

以下 HTML 是根据 Address.AddressLine1 生成的:

<input type="text" id="Address_AddressLine1" name="Address.AddressLine1" value=""/>

表达式名称与集合

示例,包含一个 Colors 数组的模型:

 public class Person
 {
     public List<string> Colors { get; set; }

     public int Age { get; set; }
 }

Action 方法:

public IActionResult Edit(int id, int colorIndex)
{
    ViewData["Index"] = colorIndex;
    return View(GetPerson(id));
}

下面的 Razor 代码展示了如何访问指定的 Color 元素:

@model Person
@{
    var index = (int)ViewData["index"];
}<form asp-controller="ToDo" asp-action="Edit" method="post">
    @Html.EditorFor(m => m.Colors[index])    <label asp-for="Age"></label><input asp-for="Age"/><br/><button type="submit">Post</button></form>

Views/Shared/EditorTemplates/String.cshtml 模版:

@model string<label asp-for="@Model"></label><input asp-for="@Model"/><br/>

使用 List<T> 的例子:

 public class ToDoItem
 {
     public string Name { get; set; }

     public bool IsDone { get; set; }

下面的 Razor 代码展示了如何遍历一个集合:

@model List<ToDoItem>

<form asp-controller="ToDo" asp-action="Edit" method="post">
    <table>
        <tr> <th>Name</th> <th>Is Done</th> </tr>

        @for (int i = 0; i < Model.Count; i++)
        {
            <tr>
                @Html.EditorFor(model => model[i])
            </tr>
        }

    </table>
    <button type="submit">Save</button>
</form>
@model ToDoItem

<td>
    <label asp-for="@Model.Name"></label>
    @Html.DisplayFor(model => model.Name)
</td>
<td>
    <input asp-for="@Model.IsDone" />
</td>

@*
    This template replaces the following Razor which evaluates the indexer three times.
    <td>
         <label asp-for="@Model[i].Name"></label>
         @Html.DisplayFor(model => model[i].Name)
     </td>
     <td>
         <input asp-for="@Model[i].IsDone" />
     </td>
*@

注意
应始终使用 for (而 不是foreach )遍历列表。在 LINQ 表达式中执行索引器会产生开销应当尽量减少。


注意
上面示例中被注释的代码演示了应当如何使用 @ 操作符代替 lambda 表达式去访问列表中的每一个 ToDoItem

Textarea Tag Helper

Textarea Tag Helper 与 Input Tag Helper类似。

  • <textarea> 元素生成 idname 属性,以及数据验证属性。

  • 提供强类型。

  • HTML Helper 替代选项: Html.TextAreaFor

示例:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    public class DescriptionViewModel
    {
        [MinLength(5)]
        [MaxLength(1024)]
        public string Description { get; set; }
    }
}
@model DescriptionViewModel<form asp-controller="Demo" asp-action="RegisterTextArea" method="post"><textarea asp-for="Description"></textarea><button type="submit">Test</button></form>

生成以下代码:

<form method="post" action="/Demo/RegisterTextArea"><textarea data-val="true"   data-val-maxlength="The field Description must be a string or array type with a maximum length of &#x27;1024&#x27;."   data-val-maxlength-max="1024"   data-val-minlength="The field Description must be a string or array type with a minimum length of &#x27;5&#x27;."   data-val-minlength-min="5"   id="Description" name="Description"></textarea><button type="submit">Test</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

Label Tag Helper

  • 根据表达式名称在 <label> 元素上生成标签文字和 for 属性。

  • HTML Helper 替代选项: Html.LabelFor

Label Tag Helper 相对于纯 HTML label 元素具有以下优势:

  • 可从 Display 特性自动获得描述性的 Label 值。随着时间推移,预期的显示名称可能会变化,而结合使用 Display 特性与 Label Tag Helper将会在所有使用它的地方应用 Display

  • 在源代码里更少的标记。

  • 强类型与模型属性。

示例:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    public class SimpleViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email Address")]
        public string Email { get; set; }
    }
}
@model SimpleViewModel<form asp-controller="Demo" asp-action="RegisterLabel" method="post"><label asp-for="Email"></label><input asp-for="Email"/><br/></form>

以下是为 <label> 元素生成的 HTML :

<label for="Email">Email Address</label>

Label Tag Helper生成了 "Email" 的 for 属性值,也就是与 <input> 元素关联的 ID 。Tag Helper生成一致的 idfor 元素,因此它们可以正确地关联起来。本例中的标签文本来自于 Display 特性。如果模型没有 Display 特性,标签文本则会是表达式的属性名称。

验证 Tag Helper

有两种验证Tag Helper。Validation Message Tag Helper(用来显示模型上单个属性的验证信息),和 Validation Summary Tag Helper (用来显示验证错误汇总)。Input Tag Helper 根据模型类的数据注释给 input 元素添加 HTML5 客户端验证属性。验证也在服务端执行。Validation Tag Helper会在验证发生错误的时候显示这些错误信息。

Validaton Message Tag Helper

  • 添加 HTML5data-valmsg-for="property" 属性到 span 元素,使验证错误信息附加到指定模型属性的 input 字段上。当客户端验证发生错误,jQuery 会在 <span> 元素里显示错误信息。

  • 验证也发生在服务端。客户端可能会禁用 JavaScript 那么验证就只能在服务端完成。

  • HTML Helper 替代选项: Html.ValidationMessageFor

Validaton Message Tag Helper 与 HTML span 元素上的 asp-validation-for 属性一起使用。

<span asp-validation-for="Email"></span>

Validation Message Tag Helper将生成以下 HTML :

<span class="field-validation-valid"  data-valmsg-for="Email"  data-valmsg-replace="true"></span>

通常在模型属性相同的 Input Tag Helper后面使用 Validation Message Tag Helper 。这样可以在发生验证错误的 input 旁边显示错误信息。

注意
必须有一个正确引用了 JavaScript 和 jQuery 脚本的视图进行客户端验证。详见: Model Validation

当服务端验证发生了错误(比如你有自定义的服务端验证或者客户端验证被禁用),MVC 会把错误信息放在 <span> 元素的正文中。

<span class="field-validation-error" data-valmsg-for="Email"            data-valmsg-replace="true">
   The Email Address field is required.</span>

验证摘要Tag Helper

  • 选取带有 asp-validation-summary 属性的 <div> 元素。

  • HTML Helper 替代选项:@Html.ValidationSummary

Validation Summary Tag Helper 用来显示验证信息的摘要。 asp-validation-summary 属性值可以是下面任意一种:

ValidationSummary.AllProperty and model level
ValidationSummary.ModelOnlyModel
ValidationSummary.NoneNone

示例

在以下示例中,数据模型装饰了 DataAnnotation 特性,用以在 <input> 元素上生成验证错误信息。当发生验证错误的时候, Validation Tag Helper显示错误信息:

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email Address")]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}
@model RegisterViewModel<form asp-controller="Demo" asp-action="RegisterValidation" method="post"><div asp-validation-summary="ValidationSummary.ModelOnly"></div>
    Email:  <input asp-for="Email"/><br/><span asp-validation-for="Email"></span><br/>
    Password: <input asp-for="Password"/><br/><span asp-validation-for="Password"></span><br/><button type="submit">Register</button></form>

生成的 HTML (当模型有效时):

<form action="/DemoReg/Register" method="post"><div class="validation-summary-valid" data-valmsg-summary="true"><ul><li style="display:none"></li></ul></div>
  Email:  <input name="Email" id="Email" type="email" value=""   data-val-required="The Email field is required."   data-val-email="The Email field is not a valid e-mail address."   data-val="true"><br><span class="field-validation-valid" data-valmsg-replace="true"   data-valmsg-for="Email"></span><br>
  Password: <input name="Password" id="Password" type="password"   data-val-required="The Password field is required." data-val="true"><br><span class="field-validation-valid" data-valmsg-replace="true"   data-valmsg-for="Password"></span><br><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

Select Tag Helper

Select Tag Helperasp-forselect 元素指定模型的属性名称,而 asp-items 则指定 option 元素。例如:

<select asp-for="Country" asp-items="Model.Countries"></select>

示例:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
    public class CountryViewModel
    {
        public string Country { get; set; }

        public List<SelectListItem> Countries { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "MX", Text = "Mexico" },
            new SelectListItem { Value = "CA", Text = "Canada" },
            new SelectListItem { Value = "US", Text = "USA"  },
        };
    }
}

Index 方法初始化 CountryViewModel ,设置已选国家然后把它传给 Index 视图。

  public IActionResult Index()
  {
      var model = new CountryViewModel();
      model.Country = "CA";
      return View(model);
  }

HTTP POST Index 方法显示选择的项:

  [HttpPost]
  [ValidateAntiForgeryToken]
  public IActionResult Index(CountryViewModel model)
  {
      if (ModelState.IsValid)
      {
          var msg = model.Country +  " selected";
          return RedirectToAction("IndexSuccess", new { message = msg});
      }

      // If we got this far, something failed; redisplay form.
      return View(model);
  }

Index 视图:

@model CountryViewModel<form asp-controller="Home" asp-action="Index" method="post"><select asp-for="Country" asp-items="Model.Countries"></select><br/><button type="submit">Register</button></form>

生成以下 HTML (选择了 "CA" ):

<form method="post" action="/"><select id="Country" name="Country"><option value="MX">Mexico</option><option selected="selected" value="CA">Canada</option><option value="US">USA</option></select><br/><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

注意
我们不推荐将 ViewBagViewData 用于 Select Tag Helper 。视图模型在提供 MVC 元数据方面更加健壮并且通常来说问题更少。

asp-for 属性值是一个特例,不需要 Model 前缀,而其他的 Tag Helper 属性则需要(比如 asp-items )。

<select asp-for="Country" asp-items="Model.Countries"></select>

枚举绑定

enum 属性用于 <select> 并根据 enum 的值生成 `SelectListItemselectlistitem] 元素通常是很方便的。

示例:

 public class CountryEnumViewModel
 {
     public CountryEnum EnumCountry { get; set; }
 }
using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    {
        Mexico,
        [Display(Name = "United States of America")]
        USA,
        Canada,
        France,
        Germany,
        Spain
    }
}

GetEnumSelectList 方法生产一个 SelectList 枚举对象.

@model CountryEnumViewModel<form asp-controller="Home" asp-action="IndexEnum" method="post"><select asp-for="EnumCountry"            asp-items="Html.GetEnumSelectList<CountryEnum>()"> >    </select><br/><button type="submit">Register</button></form>

你可以使用 Display 特性装饰你的枚举数从而获得更丰富的 UI :

using System.ComponentModel.DataAnnotations;

namespace FormsTagHelper.ViewModels
{
    public enum CountryEnum
    {
        [Display(Name = "United Mexican States")]
        Mexico,
        [Display(Name = "United States of America")]
        USA,
        Canada,
        France,
        Germany,
        Spain
    }
}

生成以下的 HTML :

<form method="post" action="/Home/IndexEnum"><select data-val="true" data-val-required="The EnumCountry field is required."              id="EnumCountry" name="EnumCountry"><option value="0">United Mexican States</option><option value="1">United States of America</option><option value="2">Canada</option><option value="3">France</option><option value="4">Germany</option><option selected="selected" value="5">Spain</option></select><br/><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

选项分组

当视图模型包含一个或多个 SelectListGroup 对象时,会生成 HTML <optgroup> 元素。

CountryViewModelGroupSelectListItem 元素分到 "North America" 和 "Europe" 分组中:

public class CountryViewModelGroup
 {
     public CountryViewModelGroup()
     {
         var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
         var EuropeGroup = new SelectListGroup { Name = "Europe" };

         Countries = new List<SelectListItem>
         {
             new SelectListItem
             {
                 Value = "MEX",
                 Text = "Mexico",
                 Group = NorthAmericaGroup
             },
             new SelectListItem
             {
                 Value = "CAN",
                 Text = "Canada",
                 Group = NorthAmericaGroup
             },
             new SelectListItem
             {
                 Value = "US",
                 Text = "USA",
                 Group = NorthAmericaGroup
             },
             new SelectListItem
             {
                 Value = "FR",
                 Text = "France",
                 Group = EuropeGroup
             },
             new SelectListItem
             {
                 Value = "ES",
                 Text = "Spain",
                 Group = EuropeGroup
             },
             new SelectListItem
             {
                 Value = "DE",
                 Text = "Germany",
                 Group = EuropeGroup
             }
       };
     }

     public string Country { get; set; }

     public List<SelectListItem> Countries { get; }
 }

下面展示了这两个分组:

生成的 HTML :

<form method="post" action="/Home/IndexGroup"><select id="Country" name="Country"><optgroup label="North America"><option value="MEX">Mexico</option><option value="CAN">Canada</option><option value="US">USA</option></optgroup><optgroup label="Europe"><option value="FR">France</option><option value="ES">Spain</option><option value="DE">Germany</option></optgroup></select><br/><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

多选

如果 asp-for 属性中指定的模型属性是一个 IEnumerable 类型, Select Tag Helper 将会自动生成 multiple = "multiple"。例如,已知以下模型:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace FormsTagHelper.ViewModels
{
    public class CountryViewModelIEnumerable
    {
        public IEnumerable<string> CountryCodes { get; set; }

        public List<SelectListItem> Countries { get; } = new List<SelectListItem>
        {
            new SelectListItem { Value = "MX", Text = "Mexico" },
            new SelectListItem { Value = "CA", Text = "Canada" },
            new SelectListItem { Value = "US", Text = "USA"    },
            new SelectListItem { Value = "FR", Text = "France" },
            new SelectListItem { Value = "ES", Text = "Spain"  },
            new SelectListItem { Value = "DE", Text = "Germany"}
         };
    }
}

使用以下视图:

@model CountryViewModelIEnumerable<form asp-controller="Home" asp-action="IndexMultiSelect" method="post"><select asp-for="CountryCodes" asp-items="Model.Countries"></select><br/><button type="submit">Register</button></form>

生成如下 HTML :

<form method="post" action="/Home/IndexMultiSelect"><select id="CountryCodes"    multiple="multiple"    name="CountryCodes"><option value="MX">Mexico</option><option value="CA">Canada</option><option value="US">USA</option><option value="FR">France</option><option value="ES">Spain</option><option value="DE">Germany</option></select><br/><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>

无选择

想要允许无选择,可添加一个 “未选择” 项到选择列表。如果该模型属性是一个 值类型,则需要使其为可空值 nullable

@model CountryViewModel<form asp-controller="Home" asp-action="IndexEmpty" method="post"><select asp-for="Country" asp-items="Model.Countries"><option value="">&lt;none&gt;</option></select><br/><button type="submit">Register</button></form>

如果你在多个页面里使用“未选择”项,可以创建一个模版避免重复的 HTML:

@model CountryViewModel<form asp-controller="Home" asp-action="IndexEmpty" method="post">
    @Html.EditorForModel()    <br/><button type="submit">Register</button></form>

Views/Shared/EditorTemplates/CountryViewModel.cshtml 模版:

@model CountryViewModel<select asp-for="Country" asp-items="Model.Countries"><option value="">--none--</option></select>

添加 HTML <option> 元素并不局限于 无选择 的情况。比如,下面的视图和 Action 方法会生成和上面类似的 HTML :

  public IActionResult IndexOption(int id)
  {
      var model = new CountryViewModel();
      model.Country = "CA";
      return View(model);
  }
@model CountryViewModel<form asp-controller="Home" asp-action="IndexEmpty" method="post"><select asp-for="Country"><option value="">&lt;none&gt;</option><option value="MX">Mexico</option><option value="CA">Canada</option><option value="US">USA</option></select><br/><button type="submit">Register</button></form>

<option> 元素将会根据当前的 Country 值被正确选中(加上 selected="selected" 属性)。

<form method="post" action="/Home/IndexEmpty"><select id="Country" name="Country"><option value="">&lt;none&gt;</option><option value="MX">Mexico</option><option value="CA" selected="selected">Canada</option><option value="US">USA</option></select><br/><button type="submit">Register</button><input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>"/></form>