Note
這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本。
Warning
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文會說明何謂模型繫結、其運作方式,以及如何自訂其行為。
何謂模型繫結
控制器和 Razor Pages 使用來自 HTTP 要求的資料。 例如,路由資料可能會提供記錄索引鍵,而已張貼的表單欄位可能會提供模型屬性的值。 撰寫程式碼來擷取這些值的每一個並將它們從字串轉換成 .NET 類型,不但繁瑣又容易發生錯誤。 模型繫結會自動化此程序。 模型繫結系統:
- 從各種來源擷取資料,例如路由資料、表單欄位和查詢字串。
- 在方法參數和公開的屬性中,將資料提供給控制器和 Razor Pages。
- 將字串資料轉換成 .NET 類型。
- 更新複雜類型的屬性。
Example
假設您有下列的動作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
而應用程式收到具有此 URL 的要求:
https://contoso.com/api/pets/2?DogsOnly=true
在路由傳送系統選取該動作方法之後,模型繫結會逐步執行下列的步驟:
- 尋找第一個參數
GetById,它是名為id的整數。 - 查看 HTTP 要求中所有可用的來源,在路由資料中找到
id= "2"。 - 將字串 "2" 轉換成整數 2。
- 尋找
GetById的下一個參數 (一個名為dogsOnly的布林值)。 - 查看來源,在查詢字串中找到 "DogsOnly=true"。 名稱比對不區分大小寫。
- 將字串 "true" 轉換成布林值
true。
架構接著會呼叫 GetById 方法,針對 id 參數傳送 2、true 參數傳送 dogsOnly。
在上述範例中,模型系結目標是 簡單 型別的方法參數。 目標也可能是複雜類型的屬性。 成功系結每個屬性之後,就會針對該屬性進行 模型驗證 。 哪些數據系結至模型的記錄,以及任何系結或驗證錯誤,會儲存在 ControllerBase.ModelState 或 PageModel.ModelState 中。 若要找出此程式是否成功,應用程式會檢查 ModelState.IsValid 旗標。
Targets
模型繫結會嘗試尋找下列幾種目標的值:
- 要求路由目標的控制器動作方法參數。
- 將要求路由傳送到其中的目標 Razor Pages 處理常式方法的參數。
- 控制站的公用屬性或
PageModel類別,如由屬性指定。
[BindProperty] 屬性
可以套用至控制器或 PageModel 類別的公開屬性,以使模型繫結以該屬性為目標:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] 屬性
可以套用至控制器或 PageModel 類別,以告訴模型繫結以類別的所有公開屬性為目標:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
適用於 HTTP GET 要求的模型繫結
根據預設,屬性不會針對 HTTP GET 要求繫結。 一般而言,GET 要求只需要記錄識別碼參數。 此記錄識別碼用來查詢資料庫中的項目。 因此,不需要繫結保存模型實例的屬性。 在您要從 GET 要求將屬性繫結至資料的案例中,請將 SupportsGet 屬性設為 true:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
模型繫結簡單和複雜類型
模型繫結使用特定定義來描述其作業類型。
簡單類型會使用單一字串搭配TypeConverter方法或是使用TryParse方法來進行轉換。
複合型別是從多個輸入值轉換而來。 架構會根據是否有 TypeConverter 或 TryParse 來判斷差異。 建議您建立一個類型轉換器,或使用 TryParse 來進行 string 至 SomeType 的轉換 (這不需要外部資源或多個輸入)。
Sources
根據預設,模型繫結會從下列 HTTP 要求的來源中,取得索引鍵/值組形式的資料:
- 表單欄位
- 要求本文 (針對具有 [ApiController] 屬性的控制器。)
- 路由資料
- 查詢字串參數
- 上傳的文件
對於每個目標參數或屬性,會依照前面清單中所指示的順序掃描來源。 但也有一些例外:
- 路由數據和查詢字串值僅用於 簡單 類型。
- 上傳的檔案只繫結到實作
IFormFile或IEnumerable<IFormFile>的目標類型。
如果預設來源不正確,請使用下列其中一個屬性來指定來源:
-
[FromQuery]- 從查詢字串取得值。 -
[FromRoute]- 從路由資料取得值。 -
[FromForm]- 從發佈的表單欄位取得值。 -
[FromBody]- 從要求本文取得值。 -
[FromHeader]- 從 HTTP 標頭取得值。
這些屬性:
會個別新增至模型屬性 (而不是模型類別),如下列範例所示:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }會選擇性地接受建構函式中的模型名稱值。 如果屬性名稱與要求中的值不相符,則會提供此選項。 例如,要求中的值可能是名稱中有連字號的標頭,如下列範例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 屬性
將 [FromBody] 屬性套用至參數,以從 HTTP 要求的本文中填入其屬性。 ASP.NET Core 執行階段會將讀取本文的責任委派給輸入格式器。
本文稍後會說明輸入格式器。
當 [FromBody] 套用至複雜的類型參數時,會忽略套用至其屬性的任何繫結來源屬性。 例如,下列的 Create 動作會指定其 pet 參數從本文填入:
public ActionResult<Pet> Create([FromBody] Pet pet)
類別 Pet 會指定其 Breed 屬性從查詢字串參數填入:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
在前述範例中:
- 會忽略
[FromQuery]屬性。 -
Breed屬性不會從查詢字串參數填入。
輸入格式器只會讀取本文,而不了解繫結來源屬性。 如果在本文中找到適當的值,則會使用該值來填入 Breed 屬性。
請勿將 [FromBody] 套用至每個動作方法的多個參數。 一旦輸入格式器讀取要求串流後,即無法再次讀取以用來繫結其他 [FromBody] 參數。
其他來源
模型繫結系統從值提供者獲取原始數據。 您可以撰寫並註冊自訂值提供者,其從其他來源取得模型繫結資料。 例如,您可能想從 Cookie 或工作階段狀態取得一些資料。 若要從新來源取得資料:
- 建立會實作
IValueProvider的類別。 - 建立會實作
IValueProviderFactory的類別。 - 在
Program.cs中註冊 Factory 類別。
此範例包含 值提供者 和 工廠 範例,可從 Cookies 取得值。 請在 Program.cs 中註冊自訂值提供者 factory:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
上面的程式碼將自訂值提供者放在所有內建值提供者之後。 若要使其成為清單中的第一個,請呼叫 Insert(0, new CookieValueProviderFactory()) 而不是 Add。
無模型屬性的來源
預設情況下,如果未找到模型屬性的值,則不會建立模型狀態錯誤。 屬性設定為 null 或預設值:
- 可空的 simple 類型會被設定為
null。 - 不可為 Null 的實值型別會設為
default(T)。 例如,參數int id設為 0。 - 對於複雜類型,模型繫結會使用預設的建構函式來建立實例,而不需要設定屬性。
- 陣列設為
Array.Empty<T>(),但byte[]陣列設為null。
如果在模型屬性的表單欄位中找不到任何內容時模型狀態應無效,則請使用 [BindRequired] 屬性。
請注意,此 [BindRequired] 行為適用於從表單提交的數據進行模型繫結,而不是從請求正文中的 JSON 或 XML 數據進行。 請求體數據由 輸入格式器處理。
類型轉換錯誤
如果找到來源但無法轉換為目標類型,則模型狀態將被標記為無效。 目標參數或屬性會設為 null 或預設值,如上一節中所述。
在具有 [ApiController] 屬性的 API 控制器中,無效的模型狀態會導致自動 HTTP 400 回應。
在 Razor 頁面中,重新顯示有錯誤訊息的頁面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
當上面的程式碼重新顯示該頁面時,無效的輸入不會顯示在表單欄位中。 這是因為模型屬性已設為 null 或預設值。 無效的輸入確實會出現在錯誤訊息中。 如果您想要在表單欄位中重新顯示不正確的資料,請考慮讓模型屬性成為字串,以手動方式執行資料轉換。
如果您不希望類型轉換錯誤導致模型狀態錯誤,建議使用相同的策略。 在這種情況下,請將模型屬性設為字串。
簡單類型
如需簡單和複雜類型的說明,請參閱模型繫結簡單和複雜類型 。
模型繫結器可將來源字串轉換成的簡單型別包括:
- Boolean
- 位元組, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16、 Int32、 Int64
- Single
- TimeOnly
- TimeSpan
- UInt16、 UInt32、 UInt64
- Uri
- Version
與 IParsable<T>.TryParse 繫結
IParsable<TSelf>.TryParse API 支援繫結控制器動作參數值:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
以下的 DateRange 類別實作了 IParsable<TSelf> 以支援繫結日期範圍:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
上述 程式碼:
- 將代表兩個日期的字串轉換成
DateRange物件 - 模型繫結器使用
IParsable<TSelf>.TryParse方法來繫結DateRange。
下列控制器動作使用 DateRange 類別來繫結日期範圍:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
以下的 Locale 類別實作了 IParsable<TSelf> 以支援繫結至 CultureInfo:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
下列控制器動作使用 Locale 類別來繫結至 CultureInfo 字串:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
下列控制器動作使用 DateRange 和 Locale 類別來將日期範圍與 CultureInfo 繫結:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
GitHub 上的 API 範例應用程式顯示了上述的 API 控制器範例。
與 TryParse 繫結
TryParse API 支援繫結控制器動作參數值:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse 是建議的參數繫結方法,因為與 TryParse 不同,它不依賴反映。
下列的 DateRangeTP 類別實作了 TryParse:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
下列控制器動作使用 DateRangeTP 類別來繫結日期範圍:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
複雜類型
複雜類型必須具有公開的預設建構函式和公開的可寫入屬性才能進行繫結。 發生模型繫結時,類別會使用公用預設建構函式具現化。
針對複雜類型的每個屬性, 模型繫結會查看名稱模式prefix.property_name 的來源。 如果找不到任何專案,它只會尋找不帶有前置詞的 property_name。 是否使用前置詞的決定不是針對每個屬性來做出的。 例如,對於包含 ?Instructor.Id=100&Name=foo 的查詢,繫結到方法 OnGet(Instructor instructor),產生的類型 Instructor 的物件會包含:
- 請將
Id設定為100。 - 請將
Name設定為null。 模型繫結需要Instructor.Name,因為上面的查詢參數中使用了Instructor.Id。
Note
通常,指向 .NET 參考來源的文件連結會載入存放庫的預設分支,這代表 .NET 下一版本的最新開發進度。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
若要繫結至參數,則前置詞是參數名稱。 若要繫結至 PageModel 公用屬性,則前置詞為公用屬性名稱。 某些屬性 (Attribute) 具有 Prefix 屬性 (Property),其可讓您覆寫參數或屬性名稱的預設使用方法。
例如,假設複雜類型是下列 Instructor 類別:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前置詞 = 參數名稱
如果要繫結的模型是名為 instructorToUpdate 的參數:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型繫結從查看索引鍵 instructorToUpdate.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
前置詞 = 屬性名稱
如果要繫結的模型是控制器或 Instructor 類別名為 PageModel 的屬性:
[BindProperty]
public Instructor Instructor { get; set; }
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
自訂前置詞
如果要繫結的模型是名為 instructorToUpdate 的參數,且 Bind 屬性指定 Instructor 為前置詞:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
複雜類型目標的屬性
有數個內建屬性可用於控制複雜類型的模型繫結:
Warning
當張貼的表單資料為值來源時,這些屬性會影響模型繫結。 它們 不會影響 輸入格式化程式,這些格式化程式負責處理張貼的 JSON 和 XML 請求主體。 本文稍後會說明輸入格式器。
[Bind]屬性
可以套用至類別或方法參數。 指定模型繫結應包含哪些模型屬性。
[Bind]
不會影響輸入格式器。
在下列範例中,當呼叫任何處理常式或動作方法時,只會繫結 Instructor 模型的指定屬性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下列範例中,當呼叫 Instructor 方法時,只會繫結 OnPost 模型的指定屬性:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]屬性可用來防止建立案例中過多提交。 它在編輯場景中效果不佳,因為排除的屬性會設為 null 或預設值,而不是保持不變。 建議使用檢視模型而非 [Bind] 屬性來防範大量指派。 如需詳細資訊,請參閱關於大量指派的安全性注意事項。
[ModelBinder] 屬性
ModelBinderAttribute 可以套用至類型、屬性或參數。 它允許指定用來繫結特定實例或類型的模型繫結器類型。 例如:
[HttpPost]
public IActionResult OnPost(
[ModelBinder<MyInstructorModelBinder>] Instructor instructor)
[ModelBinder] 屬性也可以用來在它進行模型繫結時變更屬性或參數的名稱:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] 屬性
如果模型的屬性不能發生繫結,則會造成模型繫結新增模型狀態錯誤。 以下為範例:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
[BindNever] 屬性
可以套用至屬性或類型。 避免模型繫結設定模型的屬性。 套用至類型時,模型繫結系統會排除類型所定義的所有屬性。 以下為範例:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
對於屬於簡單類型集合的目標,模型绑定將尋找與 parameter_name 或 property_name 相匹配的項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設要繫結的參數是名為
selectedCourses的陣列:public IActionResult OnPost(int? id, int[] selectedCourses)表單或查詢字串資料可以是下列其中一種格式:
selectedCourses=1050&selectedCourses=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=b如果名為
index或Index的參數或屬性與集合值相鄰,請避免繫結該參數或屬性。 模型系結會嘗試使用index作為集合的索引,這可能會導致不正確的繫結。 例如,請考慮下列動作:public IActionResult Post(string index, List<Product> products)在上面的程式碼中,
index查詢字串參數繫結到index方法參數,並且也用來繫結產品集合。 重新命名index參數或使用模型繫結屬性來設定繫結,可避免此問題:public IActionResult Post(string productIndex, List<Product> products)下列格式只在表單資料中受到支援:
selectedCourses[]=1050&selectedCourses[]=2000針對前列所有範例格式,模型繫結會將有兩個項目的陣列傳遞至
selectedCourses參數:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下標數字 (... [0] ... [1] ...) 的資料格式必須確定它們從零開始按順序編號。 下標編號中如有任何間距,則會忽略間隔後的所有項目。 例如,如果下標是 0 和 2,而不是 0 和 1,則忽略第二個項目。
Dictionaries
針對 Dictionary 目標,模型系結會尋找 parameter_name 或 property_name的相符項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設目標參數是一個名為
Dictionary<int, string>的selectedCourses:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)已張貼的表單或查詢字串資料看起來會像下列其中一個範例:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics[1050]=Chemistry&selectedCourses[2000]=EconomicsselectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics針對前列所有範例格式,模型繫結會將有兩個項目的字典傳遞至
selectedCourses參數:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
建構函式繫結和記錄類型
模型繫結需要複雜類型具有無參數的建構函式。
System.Text.Json 和 Newtonsoft.Json 型的輸入格式器都支援還原序列化沒有無參數建構函式的類別。
記錄類型是一個透過網路簡潔表示資料的好方法。 ASP.NET Core 支援使用單一建構函式來進行模型繫結和驗證記錄類型:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
驗證記錄類型時,執行階段會特別針對參數 (而不是屬性) 搜尋繫結和驗證中繼資料。
以下這個架構允許對記錄類型進行繫結和驗證:
public record Person([Required] string Name, [Range(0, 100)] int Age);
若要讓上述內容能夠運作,類型必須:
- 為記錄類型。
- 確切只有一個公開的建構函式。
- 包含具有相同名稱和類型之屬性的參數。 名稱的大小寫不得不同。
不含無參數之建構函式的 POCO
沒有無參數建構函式的 POCO 無法繫結。
下列程式碼會產生一個例外狀況訊息,指出類型必須具有無參數的建構函式:
public class Person {
public Person(string Name) { }
}
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0)
{
}
}
「具有手動編寫建構函式的記錄類型」
看起來像主要構造函數的「具有手動編寫建構函式的記錄類型」可以運作
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
記錄類型、驗證和繫結中繼資料
對於記錄類型,會使用參數上的驗證和繫結中繼資料。 而屬性上的任何中繼資料會被忽略
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
驗證和中繼資料
驗證會使用參數上的中繼資料,但會使用屬性來讀取值。 在具有主要建構函式的一般案例中,兩者會相同。 不過,有一些方法可以克服它:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel 不會更新記錄類型上的參數
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
在本例中,MVC 不會嘗試再次繫結 Name。 不過,會允許更新 Age
模型繫結路由資料和查詢字串的全球化行為
ASP.NET Core 路由值提供者和查詢字串值提供者:
- 將值視為不因文化特性而異。
- 預期 URL 不因文化特性而異。
相反地,來自表單資料的值會進行區分文化特性的轉換。 這是有意為之的,以便 URL 可以跨不同地區設定來共用。
若要讓 ASP.NET Core 路由值提供者和查詢字串值提供者能夠進行區分文化特性的轉換:
- 繼承自 IValueProviderFactory
- 從 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 複製程序代碼
- 將傳遞至值提供者建構函式 的文化特性值 取代為 CultureInfo.CurrentCulture
- 將 MVC 選項中的預設值提供者 factory 取代為新的:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
特殊資料類型
模型繫結可以處理某些特殊資料類型。
IFormFile 和 IFormFileCollection
HTTP 要求包含上傳的檔案。 也支援多個檔案的 IEnumerable<IFormFile>。
CancellationToken
動作可以選擇性地將 CancellationToken 繫結為參數。 這會繫結 RequestAborted,當 HTTP 要求底層的連線被中止時,它會發出訊號。 動作可以使用此參數來取消作為控制器動作的一部分執行的長時間執行的非同步作業。
FormCollection
用來擷取已張貼表單資料中的所有值。
輸入格式化程式
要求本文中的資料可以是 JSON、XML 或一些其他格式。 為了剖析此數據,模型系結會使用設定為處理特定內容類型的 輸入格式器 。 根據預設,ASP.NET Core 包含 JSON 型輸入格式器,以使用 System.Text.Json來處理 JSON 數據。 您可以為其他內容類型新增其他格式器。
可以使用 AddJsonOptions 方法來設定預設的 JSON 輸入格式器。
builder.Services.AddControllers().AddJsonOptions(options =>
{
// Configure property naming policy (camelCase)
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
// Add enum converter to serialize enums as strings
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// Configure other JSON options
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
常見的組態選項包括:
- 屬性命名原則 - 設定 camelCase 或其他命名慣例
- 列舉轉換器 - 以字串處理列舉序列化
- 自訂轉換器 - 新增類型特定的串行化邏輯
ASP.NET Core 會根據 Consumes 屬性選取輸入格式器。 如果沒有屬性存在,它會使用 Content-Type 標頭。
使用內建的 XML 輸入格式器:
在
Program.cs中,呼叫 AddXmlSerializerFormatters 或 AddXmlDataContractSerializerFormatters。builder.Services.AddControllers() .AddXmlSerializerFormatters();將
Consumes屬性套用至要求本文應為 XML 的控制器類別或動作方法。[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)如需詳細資訊,請參閱 XML 序列化簡介。
使用輸入格式器自訂模型繫結
輸入格式器會完全負責從要求本文讀取資料。 若要自訂此程序,請設定輸入格式器所使用的 API。 本節描述如何自訂 System.Text.Json 型的輸入格式器,以了解一個名為 ObjectId 的自訂類型。
請考慮下列模型,其中包含自訂 ObjectId 屬性:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
若要在使用 System.Text.Json 時自訂模型繫結程序,請建立一個衍生自 JsonConverter<T> 的類別:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
若要使用自訂轉換器,請將 JsonConverterAttribute 屬性套用至該類型。 在下列範例中,ObjectId 類型會設定為使用 ObjectIdConverter 作為其自訂轉換器:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
如需詳細資訊,請參閱如何撰寫自訂轉換器。
排除模型繫結中的指定類型
模型繫結和驗證系統的行為是由 ModelMetadata 所驅動。 您可以將詳細數據提供者新增至ModelMetadata 來自定義。 內建的詳細資料提供者可用於停用模型繫結或驗證所指定類型。
若要停用指定類型之所有模型的模型繫結,請在 ExcludeBindingMetadataProvider 中新增 Program.cs。 例如,若要對類型為 System.Version 的所有模型停用模型繫結:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
若要停用指定類型屬性的驗證,請在 SuppressChildValidationMetadataProvider 中新增 Program.cs。 例如,若要針對類型為 System.Guid 的屬性停用驗證:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
自訂模型繫結器
您可以撰寫自訂的模型繫結器來擴充模型繫結,並使用 [ModelBinder] 屬性以針對特定目標來選取它。 深入了解自訂模型繫結。
手動模型繫結
使用 TryUpdateModelAsync 方法即可手動叫用模型繫結。 此方法已於 ControllerBase 和 PageModel 類別中定義。 方法多載可讓您指定要使用的前置詞和值提供者。 如果模型繫結失敗,此方法會傳回 false。 以下為範例:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync 會使用值提供者從表單本文、查詢字串和路由資料取得資料。
TryUpdateModelAsync 通常:
- 會與使用控制器和檢視表的 Razor Pages 和 MVC 應用程式一起使用,以防止過度發佈。
- 不會與 Web API 一起使用 (除非從表單資料、查詢字串和路由資料中取用)。 取用 JSON 的 Web API 端點會使用 輸入格式器,將請求正文反序列化為物件。
如需詳細資訊,請參閱 TryUpdateModelAsync。
[FromServices] 屬性
此屬性的名稱會遵循指定資料來源的模型繫結屬性的模式。 但它與繫結來自值提供者的資料無關。 它會從 依賴注入 容器取得型別的實例。 其目的是只有在呼叫特定方法時,才在您需要服務時提供建構函式插入的替代項目。
如果該類型的實例未在相依性插入容器中註冊,則應用程式會在嘗試繫結參數時擲回例外狀況。 若要讓參數成為選擇性參數,請使用下列其中一種方法:
- 將參數設為可為 Null。
- 設定參數的預設值。
對於可為 Null 的參數,請確定在存取該參數之前它不是 null。
MVC 中的 Json+PipeReader 反序列化
從 .NET 10 開始,ASP.NET Core 的下列功能區域會使用以 PipeReader 為基礎的 JsonSerializer.DeserializeAsync 多載,而不是 Stream:
- 最少的 API(參數繫結、讀取請求本文)
- MVC (輸入格式化程式、模型)
- HttpRequestJsonExtensions用於將請求主體讀取為 JSON 的擴展方法。
對於大部分的應用程式,從 Stream 轉換至 PipeReader 可提供更好的效能,而不需要變更應用程式程式碼。 但是,如果您的應用程式具有自訂轉換器,則轉換器可能無法正確處理 Utf8JsonReader.HasValueSequence 。 如果沒有,可能會產生錯誤,例如 ArgumentOutOfRangeException 或在反序列化時資料遺漏。 您可以使用以下選項讓您的轉換器在沒有 PipeReader 相關錯誤的情況下工作。
選項 1:暫時因應措施
快速解決方法是在沒有 PipeReader 支援的情況下返回使用 Stream。 若要實作此選項,請將 「Microsoft.AspNetCore.UseStreamBasedJsonParsing」 AppContext 參數設定為 “true”。 我們建議您僅執行此操作作為臨時解決方法,並儘快更新轉換器以支援 HasValueSequence 。 交換器可能會在 .NET 11 中移除。 它的唯一目的是讓開發人員有時間更新他們的轉換器。
選項 2:針對 JsonConverter 實作的快速修正
針對此修正,您可以從ReadOnlySequence分配一個陣列。 此範例顯示程式碼的外觀:
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
// previous code
}
選項 3:更複雜但效能更好的修正
此修正牽涉到為處理 ReadOnlySequence 建立一條獨立的程式碼路徑。
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.HasValueSequence)
{
reader.ValueSequence;
// ReadOnlySequence optimized path
}
else
{
reader.ValueSpan;
// ReadOnlySpan optimized path
}
}
如需相關資訊,請參閱
其他資源
本文會說明何謂模型繫結、其運作方式,以及如何自訂其行為。
何謂模型繫結
控制器和 Razor Pages 使用來自 HTTP 要求的資料。 例如,路由資料可能會提供記錄索引鍵,而已張貼的表單欄位可能會提供模型屬性的值。 撰寫程式碼來擷取這些值的每一個並將它們從字串轉換成 .NET 類型,不但繁瑣又容易發生錯誤。 模型繫結會自動化此程序。 模型繫結系統:
- 從各種來源擷取資料,例如路由資料、表單欄位和查詢字串。
- 在方法參數和公開的屬性中,將資料提供給控制器和 Razor Pages。
- 將字串資料轉換成 .NET 類型。
- 更新複雜類型的屬性。
Example
假設您有下列的動作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
而應用程式收到具有此 URL 的要求:
https://contoso.com/api/pets/2?DogsOnly=true
在路由傳送系統選取該動作方法之後,模型繫結會逐步執行下列的步驟:
- 尋找第一個參數
GetById,它是名為id的整數。 - 查看 HTTP 要求中所有可用的來源,在路由資料中找到
id= "2"。 - 將字串 "2" 轉換成整數 2。
- 尋找
GetById的下一個參數 (一個名為dogsOnly的布林值)。 - 查看來源,在查詢字串中找到 "DogsOnly=true"。 名稱比對不區分大小寫。
- 將字串 "true" 轉換成布林值
true。
架構接著會呼叫 GetById 方法,針對 id 參數傳送 2、true 參數傳送 dogsOnly。
在上述範例中,模型系結目標是 簡單 型別的方法參數。 目標也可能是複雜類型的屬性。 成功系結每個屬性之後,就會針對該屬性進行 模型驗證 。 哪些數據系結至模型的記錄,以及任何系結或驗證錯誤,會儲存在 ControllerBase.ModelState 或 PageModel.ModelState 中。 若要找出此程式是否成功,應用程式會檢查 ModelState.IsValid 旗標。
Targets
模型繫結會嘗試尋找下列幾種目標的值:
- 要求路由目標的控制器動作方法參數。
- 將要求路由傳送到其中的目標 Razor Pages 處理常式方法的參數。
- 控制站的公用屬性或
PageModel類別,如由屬性指定。
[BindProperty] 屬性
可以套用至控制器或 PageModel 類別的公開屬性,以使模型繫結以該屬性為目標:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] 屬性
可以套用至控制器或 PageModel 類別,以告訴模型繫結以類別的所有公開屬性為目標:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
適用於 HTTP GET 要求的模型繫結
根據預設,屬性不會針對 HTTP GET 要求繫結。 一般而言,GET 要求只需要記錄識別碼參數。 此記錄識別碼用來查詢資料庫中的項目。 因此,不需要繫結保存模型實例的屬性。 在您要從 GET 要求將屬性繫結至資料的案例中,請將 SupportsGet 屬性設為 true:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
模型繫結簡單和複雜類型
模型繫結使用特定定義來描述其作業類型。
簡單類型會使用單一字串搭配TypeConverter方法或是使用TryParse方法來進行轉換。
複合型別是從多個輸入值轉換而來。 架構會根據是否有 TypeConverter 或 TryParse 來判斷差異。 建議您建立一個類型轉換器,或使用 TryParse 來進行 string 至 SomeType 的轉換 (這不需要外部資源或多個輸入)。
Sources
根據預設,模型繫結會從下列 HTTP 要求的來源中,取得索引鍵/值組形式的資料:
- 表單欄位
- 要求本文 (針對具有 [ApiController] 屬性的控制器。)
- 路由資料
- 查詢字串參數
- 上傳的文件
對於每個目標參數或屬性,會依照前面清單中所指示的順序掃描來源。 但也有一些例外:
- 路由數據和查詢字串值僅用於 簡單 類型。
- 上傳的檔案只繫結到實作
IFormFile或IEnumerable<IFormFile>的目標類型。
如果預設來源不正確,請使用下列其中一個屬性來指定來源:
-
[FromQuery]- 從查詢字串取得值。 -
[FromRoute]- 從路由資料取得值。 -
[FromForm]- 從發佈的表單欄位取得值。 -
[FromBody]- 從要求本文取得值。 -
[FromHeader]- 從 HTTP 標頭取得值。
這些屬性:
會個別新增至模型屬性 (而不是模型類別),如下列範例所示:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }會選擇性地接受建構函式中的模型名稱值。 如果屬性名稱與要求中的值不相符,則會提供此選項。 例如,要求中的值可能是名稱中有連字號的標頭,如下列範例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 屬性
將 [FromBody] 屬性套用至參數,以從 HTTP 要求的本文中填入其屬性。 ASP.NET Core 執行階段會將讀取本文的責任委派給輸入格式器。
本文稍後會說明輸入格式器。
當 [FromBody] 套用至複雜的類型參數時,會忽略套用至其屬性的任何繫結來源屬性。 例如,下列的 Create 動作會指定其 pet 參數從本文填入:
public ActionResult<Pet> Create([FromBody] Pet pet)
類別 Pet 會指定其 Breed 屬性從查詢字串參數填入:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
在前述範例中:
- 會忽略
[FromQuery]屬性。 -
Breed屬性不會從查詢字串參數填入。
輸入格式器只會讀取本文,而不了解繫結來源屬性。 如果在本文中找到適當的值,則會使用該值來填入 Breed 屬性。
請勿將 [FromBody] 套用至每個動作方法的多個參數。 一旦輸入格式器讀取要求串流後,即無法再次讀取以用來繫結其他 [FromBody] 參數。
其他來源
模型繫結系統從值提供者獲取原始數據。 您可以撰寫並註冊自訂值提供者,其從其他來源取得模型繫結資料。 例如,您可能想從 Cookie 或工作階段狀態取得一些資料。 若要從新來源取得資料:
- 建立會實作
IValueProvider的類別。 - 建立會實作
IValueProviderFactory的類別。 - 在
Program.cs中註冊 Factory 類別。
此範例包含 值提供者 和 工廠 範例,可從 Cookies 取得值。 請在 Program.cs 中註冊自訂值提供者 factory:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
上面的程式碼將自訂值提供者放在所有內建值提供者之後。 若要使其成為清單中的第一個,請呼叫 Insert(0, new CookieValueProviderFactory()) 而不是 Add。
無模型屬性的來源
預設情況下,如果未找到模型屬性的值,則不會建立模型狀態錯誤。 屬性設定為 null 或預設值:
- 可空的 simple 類型會被設定為
null。 - 不可為 Null 的實值型別會設為
default(T)。 例如,參數int id設為 0。 - 對於複雜類型,模型繫結會使用預設的建構函式來建立實例,而不需要設定屬性。
- 陣列設為
Array.Empty<T>(),但byte[]陣列設為null。
如果在模型屬性的表單欄位中找不到任何內容時模型狀態應無效,則請使用 [BindRequired] 屬性。
請注意,此 [BindRequired] 行為適用於來自已張貼表單資料的模型繫結,不適合要求本文中的 JSON 或 XML 資料。 請求體數據由 輸入格式器處理。
類型轉換錯誤
如果找到來源但無法轉換為目標類型,則模型狀態將被標記為無效。 目標參數或屬性會設為 null 或預設值,如上一節中所述。
在具有 [ApiController] 屬性的 API 控制器中,無效的模型狀態會導致自動 HTTP 400 回應。
在 Razor 頁面中,重新顯示有錯誤訊息的頁面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
當上面的程式碼重新顯示該頁面時,無效的輸入不會顯示在表單欄位中。 這是因為模型屬性已設為 null 或預設值。 無效的輸入確實會出現在錯誤訊息中。 如果您想要在表單欄位中重新顯示不正確的資料,請考慮讓模型屬性成為字串,以手動方式執行資料轉換。
如果您不希望類型轉換錯誤導致模型狀態錯誤,建議使用相同的策略。 在這種情況下,請將模型屬性設為字串。
簡單類型
如需簡單和複雜類型的說明,請參閱模型繫結簡單和複雜類型 。
模型繫結器可將來源字串轉換成的簡單型別包括:
- Boolean
- 位元組, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16、 Int32、 Int64
- Single
- TimeOnly
- TimeSpan
- UInt16、 UInt32、 UInt64
- Uri
- Version
與 IParsable<T>.TryParse 繫結
IParsable<TSelf>.TryParse API 支援繫結控制器動作參數值:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
以下的 DateRange 類別實作了 IParsable<TSelf> 以支援繫結日期範圍:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
上述 程式碼:
- 將代表兩個日期的字串轉換成
DateRange物件 - 模型繫結器使用
IParsable<TSelf>.TryParse方法來繫結DateRange。
下列控制器動作使用 DateRange 類別來繫結日期範圍:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
以下的 Locale 類別實作了 IParsable<TSelf> 以支援繫結至 CultureInfo:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
下列控制器動作使用 Locale 類別來繫結至 CultureInfo 字串:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
下列控制器動作使用 DateRange 和 Locale 類別來將日期範圍與 CultureInfo 繫結:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
GitHub 上的 API 範例應用程式顯示了上述的 API 控制器範例。
與 TryParse 繫結
TryParse API 支援繫結控制器動作參數值:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse 是建議的參數繫結方法,因為與 TryParse 不同,它不依賴反映。
下列的 DateRangeTP 類別實作了 TryParse:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
下列控制器動作使用 DateRangeTP 類別來繫結日期範圍:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
複雜類型
複雜類型必須具有公開的預設建構函式和公開的可寫入屬性才能進行繫結。 發生模型繫結時,類別會使用公用預設建構函式具現化。
針對複雜類型的每個屬性, 模型繫結會查看名稱模式prefix.property_name 的來源。 如果找不到任何專案,它只會尋找不帶有前置詞的 property_name。 是否使用前置詞的決定不是針對每個屬性來做出的。 例如,對於包含 ?Instructor.Id=100&Name=foo 的查詢,繫結到方法 OnGet(Instructor instructor),產生的類型 Instructor 的物件會包含:
- 請將
Id設定為100。 - 請將
Name設定為null。 模型繫結需要Instructor.Name,因為上面的查詢參數中使用了Instructor.Id。
Note
通常,指向 .NET 參考來源的文件連結會載入存放庫的預設分支,這代表 .NET 下一版本的最新開發進度。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
若要繫結至參數,則前置詞是參數名稱。 若要繫結至 PageModel 公用屬性,則前置詞為公用屬性名稱。 某些屬性 (Attribute) 具有 Prefix 屬性 (Property),其可讓您覆寫參數或屬性名稱的預設使用方法。
例如,假設複雜類型是下列 Instructor 類別:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前置詞 = 參數名稱
如果要繫結的模型是名為 instructorToUpdate 的參數:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型繫結從查看索引鍵 instructorToUpdate.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
前置詞 = 屬性名稱
如果要繫結的模型是控制器或 Instructor 類別名為 PageModel 的屬性:
[BindProperty]
public Instructor Instructor { get; set; }
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
自訂前置詞
如果要繫結的模型是名為 instructorToUpdate 的參數,且 Bind 屬性指定 Instructor 為前置詞:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
複雜類型目標的屬性
有數個內建屬性可用於控制複雜類型的模型繫結:
Warning
當張貼的表單資料為值來源時,這些屬性會影響模型繫結。 它們 不會影響 輸入格式化程式,這些格式化程式負責處理張貼的 JSON 和 XML 請求主體。 本文稍後會說明輸入格式器。
[Bind]屬性
可以套用至類別或方法參數。 指定模型繫結應包含哪些模型屬性。
[Bind]
不會影響輸入格式器。
在下列範例中,當呼叫任何處理常式或動作方法時,只會繫結 Instructor 模型的指定屬性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下列範例中,當呼叫 Instructor 方法時,只會繫結 OnPost 模型的指定屬性:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]屬性可用來防止建立案例中過多提交。 它在編輯場景中效果不佳,因為排除的屬性會設為 null 或預設值,而不是保持不變。 建議使用檢視模型而非 [Bind] 屬性來防範大量指派。 如需詳細資訊,請參閱關於大量指派的安全性注意事項。
[ModelBinder] 屬性
ModelBinderAttribute 可以套用至類型、屬性或參數。 它允許指定用來繫結特定實例或類型的模型繫結器類型。 例如:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
[ModelBinder] 屬性也可以用來在它進行模型繫結時變更屬性或參數的名稱:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] 屬性
如果模型的屬性不能發生繫結,則會造成模型繫結新增模型狀態錯誤。 以下為範例:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
[BindNever] 屬性
可以套用至屬性或類型。 避免模型繫結設定模型的屬性。 套用至類型時,模型繫結系統會排除類型所定義的所有屬性。 以下為範例:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
對於屬於簡單類型集合的目標,模型绑定將尋找與 parameter_name 或 property_name 相匹配的項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設要繫結的參數是名為
selectedCourses的陣列:public IActionResult OnPost(int? id, int[] selectedCourses)表單或查詢字串資料可以是下列其中一種格式:
selectedCourses=1050&selectedCourses=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=b如果名為
index或Index的參數或屬性與集合值相鄰,請避免繫結該參數或屬性。 模型系結會嘗試使用index作為集合的索引,這可能會導致不正確的繫結。 例如,請考慮下列動作:public IActionResult Post(string index, List<Product> products)在上面的程式碼中,
index查詢字串參數繫結到index方法參數,並且也用來繫結產品集合。 重新命名index參數或使用模型繫結屬性來設定繫結,可避免此問題:public IActionResult Post(string productIndex, List<Product> products)下列格式只在表單資料中受到支援:
selectedCourses[]=1050&selectedCourses[]=2000針對前列所有範例格式,模型繫結會將有兩個項目的陣列傳遞至
selectedCourses參數:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下標數字 (... [0] ... [1] ...) 的資料格式必須確定它們從零開始按順序編號。 下標編號中如有任何間距,則會忽略間隔後的所有項目。 例如,如果下標是 0 和 2,而不是 0 和 1,則忽略第二個項目。
Dictionaries
針對 Dictionary 目標,模型系結會尋找 parameter_name 或 property_name的相符項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設目標參數是一個名為
Dictionary<int, string>的selectedCourses:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)已張貼的表單或查詢字串資料看起來會像下列其中一個範例:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics[1050]=Chemistry&selectedCourses[2000]=EconomicsselectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics針對前列所有範例格式,模型繫結會將有兩個項目的字典傳遞至
selectedCourses參數:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
建構函式繫結和記錄類型
模型繫結需要複雜類型具有無參數的建構函式。
System.Text.Json 和 Newtonsoft.Json 型的輸入格式器都支援還原序列化沒有無參數建構函式的類別。
記錄類型是一個透過網路簡潔表示資料的好方法。 ASP.NET Core 支援使用單一建構函式來進行模型繫結和驗證記錄類型:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
驗證記錄類型時,執行階段會特別針對參數 (而不是屬性) 搜尋繫結和驗證中繼資料。
以下這個架構允許對記錄類型進行繫結和驗證:
public record Person([Required] string Name, [Range(0, 100)] int Age);
若要讓上述內容能夠運作,類型必須:
- 為記錄類型。
- 確切只有一個公開的建構函式。
- 包含具有相同名稱和類型之屬性的參數。 名稱的大小寫不得不同。
不含無參數之建構函式的 POCO
沒有無參數建構函式的 POCO 無法繫結。
下列程式碼會產生一個例外狀況訊息,指出類型必須具有無參數的建構函式:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
「具有手動編寫建構函式的記錄類型」
看起來像主要構造函數的「具有手動編寫建構函式的記錄類型」可以運作
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
記錄類型、驗證和繫結中繼資料
對於記錄類型,會使用參數上的驗證和繫結中繼資料。 而屬性上的任何中繼資料會被忽略
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
驗證和中繼資料
驗證會使用參數上的中繼資料,但會使用屬性來讀取值。 在具有主要建構函式的一般案例中,兩者會相同。 不過,有一些方法可以克服它:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel 不會更新記錄類型上的參數
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
在本例中,MVC 不會嘗試再次繫結 Name。 不過,會允許更新 Age
模型繫結路由資料和查詢字串的全球化行為
ASP.NET Core 路由值提供者和查詢字串值提供者:
- 將值視為不因文化特性而異。
- 預期 URL 不因文化特性而異。
相反地,來自表單資料的值會進行區分文化特性的轉換。 這是有意為之的,以便 URL 可以跨不同地區設定來共用。
若要讓 ASP.NET Core 路由值提供者和查詢字串值提供者能夠進行區分文化特性的轉換:
- 繼承自 IValueProviderFactory
- 從 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 複製程序代碼
- 將傳遞至值提供者建構函式 的文化特性值 取代為 CultureInfo.CurrentCulture
- 將 MVC 選項中的預設值提供者 factory 取代為新的:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
特殊資料類型
模型繫結可以處理某些特殊資料類型。
IFormFile 和 IFormFileCollection
HTTP 要求包含上傳的檔案。 也支援多個檔案的 IEnumerable<IFormFile>。
CancellationToken
動作可以選擇性地將 CancellationToken 繫結為參數。 這會繫結 RequestAborted,當 HTTP 要求底層的連線被中止時,它會發出訊號。 動作可以使用此參數來取消作為控制器動作的一部分執行的長時間執行的非同步作業。
FormCollection
用來擷取已張貼表單資料中的所有值。
輸入格式化程式
要求本文中的資料可以是 JSON、XML 或一些其他格式。 為了剖析此數據,模型系結會使用設定為處理特定內容類型的 輸入格式器 。 預設情況下,ASP.NET Core 包含 JSON 型的輸入格式器,用於處理 JSON 資料。 您可以為其他內容類型新增其他格式器。
ASP.NET Core 會根據 Consumes 屬性選取輸入格式器。 如果沒有屬性存在,它會使用 Content-Type 標頭。
使用內建的 XML 輸入格式器:
在
Program.cs中,呼叫 AddXmlSerializerFormatters 或 AddXmlDataContractSerializerFormatters。builder.Services.AddControllers() .AddXmlSerializerFormatters();將
Consumes屬性套用至要求本文應為 XML 的控制器類別或動作方法。[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)如需詳細資訊,請參閱 XML 序列化簡介。
使用輸入格式器自訂模型繫結
輸入格式器會完全負責從要求本文讀取資料。 若要自訂此程序,請設定輸入格式器所使用的 API。 本節描述如何自訂 System.Text.Json 型的輸入格式器,以了解一個名為 ObjectId 的自訂類型。
請考慮下列模型,其中包含自訂 ObjectId 屬性:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
若要在使用 System.Text.Json 時自訂模型繫結程序,請建立一個衍生自 JsonConverter<T> 的類別:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
若要使用自訂轉換器,請將 JsonConverterAttribute 屬性套用至該類型。 在下列範例中,ObjectId 類型會設定為使用 ObjectIdConverter 作為其自訂轉換器:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
如需詳細資訊,請參閱如何撰寫自訂轉換器。
排除模型繫結中的指定類型
模型繫結和驗證系統的行為是由 ModelMetadata 所驅動。 您可以將詳細數據提供者新增至ModelMetadata 來自定義。 內建的詳細資料提供者可用於停用模型繫結或驗證所指定類型。
若要停用指定類型之所有模型的模型繫結,請在 ExcludeBindingMetadataProvider 中新增 Program.cs。 例如,若要對類型為 System.Version 的所有模型停用模型繫結:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
若要停用指定類型屬性的驗證,請在 SuppressChildValidationMetadataProvider 中新增 Program.cs。 例如,若要針對類型為 System.Guid 的屬性停用驗證:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
自訂模型繫結器
您可以撰寫自訂的模型繫結器來擴充模型繫結,並使用 [ModelBinder] 屬性以針對特定目標來選取它。 深入了解自訂模型繫結。
手動模型繫結
使用 TryUpdateModelAsync 方法即可手動叫用模型繫結。 此方法已於 ControllerBase 和 PageModel 類別中定義。 方法多載可讓您指定要使用的前置詞和值提供者。 如果模型繫結失敗,此方法會傳回 false。 以下為範例:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync 會使用值提供者從表單本文、查詢字串和路由資料取得資料。
TryUpdateModelAsync 通常:
- 會與使用控制器和檢視表的 Razor Pages 和 MVC 應用程式一起使用,以防止過度發佈。
- 不會與 Web API 一起使用 (除非從表單資料、查詢字串和路由資料中取用)。 取用 JSON 的 Web API 端點會使用 輸入格式器,將請求正文反序列化為物件。
如需詳細資訊,請參閱 TryUpdateModelAsync。
[FromServices] 屬性
此屬性的名稱會遵循指定資料來源的模型繫結屬性的模式。 但它與繫結來自值提供者的資料無關。 它會從 依賴注入 容器取得型別的實例。 其目的是只有在呼叫特定方法時,才在您需要服務時提供建構函式插入的替代項目。
如果該類型的實例未在相依性插入容器中註冊,則應用程式會在嘗試繫結參數時擲回例外狀況。 若要讓參數成為選擇性參數,請使用下列其中一種方法:
- 將參數設為可為 Null。
- 設定參數的預設值。
對於可為 Null 的參數,請確定在存取該參數之前它不是 null。
其他資源
本文會說明何謂模型繫結、其運作方式,以及如何自訂其行為。
何謂模型繫結
控制器和 Razor Pages 使用來自 HTTP 要求的資料。 例如,路由資料可能會提供記錄索引鍵,而已張貼的表單欄位可能會提供模型屬性的值。 撰寫程式碼來擷取這些值的每一個並將它們從字串轉換成 .NET 類型,不但繁瑣又容易發生錯誤。 模型繫結會自動化此程序。 模型繫結系統:
- 從各種來源擷取資料,例如路由資料、表單欄位和查詢字串。
- 在方法參數和公開的屬性中,將資料提供給控制器和 Razor Pages。
- 將字串資料轉換成 .NET 類型。
- 更新複雜類型的屬性。
Example
假設您有下列的動作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
而應用程式收到具有此 URL 的要求:
https://contoso.com/api/pets/2?DogsOnly=true
在路由傳送系統選取該動作方法之後,模型繫結會逐步執行下列的步驟:
- 尋找第一個參數
GetById,它是名為id的整數。 - 查看 HTTP 要求中所有可用的來源,在路由資料中找到
id= "2"。 - 將字串 "2" 轉換成整數 2。
- 尋找
GetById的下一個參數 (一個名為dogsOnly的布林值)。 - 查看來源,在查詢字串中找到 "DogsOnly=true"。 名稱比對不區分大小寫。
- 將字串 "true" 轉換成布林值
true。
架構接著會呼叫 GetById 方法,針對 id 參數傳送 2、true 參數傳送 dogsOnly。
在上例中,模型繫結目標都是簡單型別的方法參數。 目標也可能是複雜類型的屬性。 成功系結每個屬性之後,就會針對該屬性進行 模型驗證 。 哪些數據系結至模型的記錄,以及任何系結或驗證錯誤,會儲存在 ControllerBase.ModelState 或 PageModel.ModelState 中。 若要找出此程式是否成功,應用程式會檢查 ModelState.IsValid 旗標。
Targets
模型繫結會嘗試尋找下列幾種目標的值:
- 要求路由目標的控制器動作方法參數。
- 將要求路由傳送到其中的目標 Razor Pages 處理常式方法的參數。
- 控制站的公用屬性或
PageModel類別,如由屬性指定。
[BindProperty] 屬性
可以套用至控制器或 PageModel 類別的公開屬性,以使模型繫結以該屬性為目標:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] 屬性
可以套用至控制器或 PageModel 類別,以告訴模型繫結以類別的所有公開屬性為目標:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
適用於 HTTP GET 要求的模型繫結
根據預設,屬性不會針對 HTTP GET 要求繫結。 一般而言,GET 要求只需要記錄識別碼參數。 此記錄識別碼用來查詢資料庫中的項目。 因此,不需要繫結保存模型實例的屬性。 在您要從 GET 要求將屬性繫結至資料的案例中,請將 SupportsGet 屬性設為 true:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Sources
根據預設,模型繫結會從下列 HTTP 要求的來源中,取得索引鍵/值組形式的資料:
- 表單欄位
- 要求本文 (針對具有 [ApiController] 屬性的控制器。)
- 路由資料
- 查詢字串參數
- 上傳的文件
對於每個目標參數或屬性,會依照前面清單中所指示的順序掃描來源。 但也有一些例外:
- 路由資料和查詢字串值只用於簡單型別。
- 上傳的檔案只繫結到實作
IFormFile或IEnumerable<IFormFile>的目標類型。
如果預設來源不正確,請使用下列其中一個屬性來指定來源:
-
[FromQuery]- 從查詢字串取得值。 -
[FromRoute]- 從路由資料取得值。 -
[FromForm]- 從發佈的表單欄位取得值。 -
[FromBody]- 從要求本文取得值。 -
[FromHeader]- 從 HTTP 標頭取得值。
這些屬性:
會個別新增至模型屬性 (而不是模型類別),如下列範例所示:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }會選擇性地接受建構函式中的模型名稱值。 如果屬性名稱與要求中的值不相符,則會提供此選項。 例如,要求中的值可能是名稱中有連字號的標頭,如下列範例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 屬性
將 [FromBody] 屬性套用至參數,以從 HTTP 要求的本文中填入其屬性。 ASP.NET Core 執行階段會將讀取本文的責任委派給輸入格式器。
本文稍後會說明輸入格式器。
當 [FromBody] 套用至複雜的類型參數時,會忽略套用至其屬性的任何繫結來源屬性。 例如,下列的 Create 動作會指定其 pet 參數從本文填入:
public ActionResult<Pet> Create([FromBody] Pet pet)
類別 Pet 會指定其 Breed 屬性從查詢字串參數填入:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
在前述範例中:
- 會忽略
[FromQuery]屬性。 -
Breed屬性不會從查詢字串參數填入。
輸入格式器只會讀取本文,而不了解繫結來源屬性。 如果在本文中找到適當的值,則會使用該值來填入 Breed 屬性。
請勿將 [FromBody] 套用至每個動作方法的多個參數。 一旦輸入格式器讀取要求串流後,即無法再次讀取以用來繫結其他 [FromBody] 參數。
其他來源
模型繫結系統從值提供者獲取原始數據。 您可以撰寫並註冊自訂值提供者,其從其他來源取得模型繫結資料。 例如,您可能想從 Cookie 或工作階段狀態取得一些資料。 若要從新來源取得資料:
- 建立會實作
IValueProvider的類別。 - 建立會實作
IValueProviderFactory的類別。 - 在
Program.cs中註冊 Factory 類別。
此範例包含 值提供者 和 工廠 範例,可從 Cookies 取得值。 請在 Program.cs 中註冊自訂值提供者 factory:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
上面的程式碼將自訂值提供者放在所有內建值提供者之後。 若要使其成為清單中的第一個,請呼叫 Insert(0, new CookieValueProviderFactory()) 而不是 Add。
無模型屬性的來源
預設情況下,如果未找到模型屬性的值,則不會建立模型狀態錯誤。 屬性設定為 null 或預設值:
- 可為 Null 的簡單類型會設為
null。 - 不可為 Null 的實值型別會設為
default(T)。 例如,參數int id設為 0。 - 對於複雜類型,模型繫結會使用預設的建構函式來建立實例,而不需要設定屬性。
- 陣列設為
Array.Empty<T>(),但byte[]陣列設為null。
如果在模型屬性的表單欄位中找不到任何內容時模型狀態應無效,則請使用 [BindRequired] 屬性。
請注意,此 [BindRequired] 行為適用於來自已張貼表單資料的模型繫結,不適合要求本文中的 JSON 或 XML 資料。 請求體數據由 輸入格式器處理。
類型轉換錯誤
如果找到來源但無法轉換為目標類型,則模型狀態將被標記為無效。 目標參數或屬性會設為 null 或預設值,如上一節中所述。
在具有 [ApiController] 屬性的 API 控制器中,無效的模型狀態會導致自動 HTTP 400 回應。
在 Razor 頁面中,重新顯示有錯誤訊息的頁面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
當上面的程式碼重新顯示該頁面時,無效的輸入不會顯示在表單欄位中。 這是因為模型屬性已設為 null 或預設值。 無效的輸入確實會出現在錯誤訊息中。 如果您想要在表單欄位中重新顯示不正確的資料,請考慮讓模型屬性成為字串,以手動方式執行資料轉換。
如果您不希望類型轉換錯誤導致模型狀態錯誤,建議使用相同的策略。 在這種情況下,請將模型屬性設為字串。
簡單類型
模型繫結器可將來源字串轉換成的簡單型別包括:
- Boolean
- 位元組, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16、 Int32、 Int64
- Single
- TimeSpan
- UInt16、 UInt32、 UInt64
- Uri
- Version
複雜類型
複雜類型必須具有公開的預設建構函式和公開的可寫入屬性才能進行繫結。 發生模型繫結時,類別會使用公用預設建構函式具現化。
針對複雜類型的每個屬性, 模型繫結會查看名稱模式prefix.property_name 的來源。 如果找不到任何專案,它只會尋找不帶有前置詞的 property_name。 是否使用前置詞的決定不是針對每個屬性來做出的。 例如,對於包含 ?Instructor.Id=100&Name=foo 的查詢,繫結到方法 OnGet(Instructor instructor),產生的類型 Instructor 的物件會包含:
- 請將
Id設定為100。 - 請將
Name設定為null。 模型繫結需要Instructor.Name,因為上面的查詢參數中使用了Instructor.Id。
Note
通常,指向 .NET 參考來源的文件連結會載入存放庫的預設分支,這代表 .NET 下一版本的最新開發進度。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
若要繫結至參數,則前置詞是參數名稱。 若要繫結至 PageModel 公用屬性,則前置詞為公用屬性名稱。 某些屬性 (Attribute) 具有 Prefix 屬性 (Property),其可讓您覆寫參數或屬性名稱的預設使用方法。
例如,假設複雜類型是下列 Instructor 類別:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前置詞 = 參數名稱
如果要繫結的模型是名為 instructorToUpdate 的參數:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型繫結從查看索引鍵 instructorToUpdate.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
前置詞 = 屬性名稱
如果要繫結的模型是控制器或 Instructor 類別名為 PageModel 的屬性:
[BindProperty]
public Instructor Instructor { get; set; }
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
自訂前置詞
如果要繫結的模型是名為 instructorToUpdate 的參數,且 Bind 屬性指定 Instructor 為前置詞:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
複雜類型目標的屬性
有數個內建屬性可用於控制複雜類型的模型繫結:
Warning
當張貼的表單資料為值來源時,這些屬性會影響模型繫結。 它們 不會影響 輸入格式化程式,這些格式化程式負責處理張貼的 JSON 和 XML 請求主體。 本文稍後會說明輸入格式器。
[Bind]屬性
可以套用至類別或方法參數。 指定模型繫結應包含哪些模型屬性。
[Bind]
不會影響輸入格式器。
在下列範例中,當呼叫任何處理常式或動作方法時,只會繫結 Instructor 模型的指定屬性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下列範例中,當呼叫 Instructor 方法時,只會繫結 OnPost 模型的指定屬性:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]屬性可用來防止建立案例中過多提交。 它在編輯場景中效果不佳,因為排除的屬性會設為 null 或預設值,而不是保持不變。 建議使用檢視模型而非 [Bind] 屬性來防範大量指派。 如需詳細資訊,請參閱關於大量指派的安全性注意事項。
[ModelBinder] 屬性
ModelBinderAttribute 可以套用至類型、屬性或參數。 它允許指定用來繫結特定實例或類型的模型繫結器類型。 例如:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
[ModelBinder] 屬性也可以用來在它進行模型繫結時變更屬性或參數的名稱:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] 屬性
如果模型的屬性不能發生繫結,則會造成模型繫結新增模型狀態錯誤。 以下為範例:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
[BindNever] 屬性
可以套用至屬性或類型。 避免模型繫結設定模型的屬性。 套用至類型時,模型繫結系統會排除類型所定義的所有屬性。 以下為範例:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
對於屬於簡單類型集合的目標,模型绑定將尋找與 parameter_name 或 property_name 相匹配的項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設要繫結的參數是名為
selectedCourses的陣列:public IActionResult OnPost(int? id, int[] selectedCourses)表單或查詢字串資料可以是下列其中一種格式:
selectedCourses=1050&selectedCourses=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=b如果名為
index或Index的參數或屬性與集合值相鄰,請避免繫結該參數或屬性。 模型系結會嘗試使用index作為集合的索引,這可能會導致不正確的繫結。 例如,請考慮下列動作:public IActionResult Post(string index, List<Product> products)在上面的程式碼中,
index查詢字串參數繫結到index方法參數,並且也用來繫結產品集合。 重新命名index參數或使用模型繫結屬性來設定繫結,可避免此問題:public IActionResult Post(string productIndex, List<Product> products)下列格式只在表單資料中受到支援:
selectedCourses[]=1050&selectedCourses[]=2000針對前列所有範例格式,模型繫結會將有兩個項目的陣列傳遞至
selectedCourses參數:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下標數字 (... [0] ... [1] ...) 的資料格式必須確定它們從零開始按順序編號。 下標編號中如有任何間距,則會忽略間隔後的所有項目。 例如,如果下標是 0 和 2,而不是 0 和 1,則忽略第二個項目。
Dictionaries
針對 Dictionary 目標,模型系結會尋找 parameter_name 或 property_name的相符項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設目標參數是一個名為
Dictionary<int, string>的selectedCourses:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)已張貼的表單或查詢字串資料看起來會像下列其中一個範例:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics[1050]=Chemistry&selectedCourses[2000]=EconomicsselectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics針對前列所有範例格式,模型繫結會將有兩個項目的字典傳遞至
selectedCourses參數:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
建構函式繫結和記錄類型
模型繫結需要複雜類型具有無參數的建構函式。
System.Text.Json 和 Newtonsoft.Json 型的輸入格式器都支援還原序列化沒有無參數建構函式的類別。
記錄類型是一個透過網路簡潔表示資料的好方法。 ASP.NET Core 支援使用單一建構函式來進行模型繫結和驗證記錄類型:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
驗證記錄類型時,執行階段會特別針對參數 (而不是屬性) 搜尋繫結和驗證中繼資料。
以下這個架構允許對記錄類型進行繫結和驗證:
public record Person([Required] string Name, [Range(0, 100)] int Age);
若要讓上述內容能夠運作,類型必須:
- 為記錄類型。
- 確切只有一個公開的建構函式。
- 包含具有相同名稱和類型之屬性的參數。 名稱的大小寫不得不同。
不含無參數之建構函式的 POCO
沒有無參數建構函式的 POCO 無法繫結。
下列程式碼會產生一個例外狀況訊息,指出類型必須具有無參數的建構函式:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
「具有手動編寫建構函式的記錄類型」
看起來像主要構造函數的「具有手動編寫建構函式的記錄類型」可以運作
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
記錄類型、驗證和繫結中繼資料
對於記錄類型,會使用參數上的驗證和繫結中繼資料。 而屬性上的任何中繼資料會被忽略
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
驗證和中繼資料
驗證會使用參數上的中繼資料,但會使用屬性來讀取值。 在具有主要建構函式的一般案例中,兩者會相同。 不過,有一些方法可以克服它:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel 不會更新記錄類型上的參數
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
在本例中,MVC 不會嘗試再次繫結 Name。 不過,會允許更新 Age
模型繫結路由資料和查詢字串的全球化行為
ASP.NET Core 路由值提供者和查詢字串值提供者:
- 將值視為不因文化特性而異。
- 預期 URL 不因文化特性而異。
相反地,來自表單資料的值會進行區分文化特性的轉換。 這是有意為之的,以便 URL 可以跨不同地區設定來共用。
若要讓 ASP.NET Core 路由值提供者和查詢字串值提供者能夠進行區分文化特性的轉換:
- 繼承自 IValueProviderFactory
- 從 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 複製程序代碼
- 將傳遞至值提供者建構函式 的文化特性值 取代為 CultureInfo.CurrentCulture
- 將 MVC 選項中的預設值提供者 factory 取代為新的:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
特殊資料類型
模型繫結可以處理某些特殊資料類型。
IFormFile 和 IFormFileCollection
HTTP 要求包含上傳的檔案。 也支援多個檔案的 IEnumerable<IFormFile>。
CancellationToken
動作可以選擇性地將 CancellationToken 繫結為參數。 這會繫結 RequestAborted,當 HTTP 要求底層的連線被中止時,它會發出訊號。 動作可以使用此參數來取消作為控制器動作的一部分執行的長時間執行的非同步作業。
FormCollection
用來擷取已張貼表單資料中的所有值。
輸入格式化程式
要求本文中的資料可以是 JSON、XML 或一些其他格式。 為了剖析此數據,模型系結會使用設定為處理特定內容類型的 輸入格式器 。 預設情況下,ASP.NET Core 包含 JSON 型的輸入格式器,用於處理 JSON 資料。 您可以為其他內容類型新增其他格式器。
ASP.NET Core 會根據 Consumes 屬性選取輸入格式器。 如果沒有屬性存在,它會使用 Content-Type 標頭。
使用內建的 XML 輸入格式器:
在
Program.cs中,呼叫 AddXmlSerializerFormatters 或 AddXmlDataContractSerializerFormatters。builder.Services.AddControllers() .AddXmlSerializerFormatters();將
Consumes屬性套用至要求本文應為 XML 的控制器類別或動作方法。[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)如需詳細資訊,請參閱 XML 序列化簡介。
使用輸入格式器自訂模型繫結
輸入格式器會完全負責從要求本文讀取資料。 若要自訂此程序,請設定輸入格式器所使用的 API。 本節描述如何自訂 System.Text.Json 型的輸入格式器,以了解一個名為 ObjectId 的自訂類型。
請考慮下列模型,其中包含自訂 ObjectId 屬性:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
若要在使用 System.Text.Json 時自訂模型繫結程序,請建立一個衍生自 JsonConverter<T> 的類別:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
若要使用自訂轉換器,請將 JsonConverterAttribute 屬性套用至該類型。 在下列範例中,ObjectId 類型會設定為使用 ObjectIdConverter 作為其自訂轉換器:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
如需詳細資訊,請參閱如何撰寫自訂轉換器。
排除模型繫結中的指定類型
模型繫結和驗證系統的行為是由 ModelMetadata 所驅動。 您可以將詳細數據提供者新增至ModelMetadata 來自定義。 內建的詳細資料提供者可用於停用模型繫結或驗證所指定類型。
若要停用指定類型之所有模型的模型繫結,請在 ExcludeBindingMetadataProvider 中新增 Program.cs。 例如,若要對類型為 System.Version 的所有模型停用模型繫結:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
若要停用指定類型屬性的驗證,請在 SuppressChildValidationMetadataProvider 中新增 Program.cs。 例如,若要針對類型為 System.Guid 的屬性停用驗證:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
自訂模型繫結器
您可以撰寫自訂的模型繫結器來擴充模型繫結,並使用 [ModelBinder] 屬性以針對特定目標來選取它。 深入了解自訂模型繫結。
手動模型繫結
使用 TryUpdateModelAsync 方法即可手動叫用模型繫結。 此方法已於 ControllerBase 和 PageModel 類別中定義。 方法多載可讓您指定要使用的前置詞和值提供者。 如果模型繫結失敗,此方法會傳回 false。 以下為範例:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync 會使用值提供者從表單本文、查詢字串和路由資料取得資料。
TryUpdateModelAsync 通常:
- 會與使用控制器和檢視表的 Razor Pages 和 MVC 應用程式一起使用,以防止過度發佈。
- 不會與 Web API 一起使用 (除非從表單資料、查詢字串和路由資料中取用)。 取用 JSON 的 Web API 端點會使用 輸入格式器,將請求正文反序列化為物件。
如需詳細資訊,請參閱 TryUpdateModelAsync。
[FromServices] 屬性
此屬性的名稱會遵循指定資料來源的模型繫結屬性的模式。 但它與繫結來自值提供者的資料無關。 它會從 依賴注入 容器取得型別的實例。 其目的是只有在呼叫特定方法時,才在您需要服務時提供建構函式插入的替代項目。
如果該類型的實例未在相依性插入容器中註冊,則應用程式會在嘗試繫結參數時擲回例外狀況。 若要讓參數成為選擇性參數,請使用下列其中一種方法:
- 將參數設為可為 Null。
- 設定參數的預設值。
對於可為 Null 的參數,請確定在存取該參數之前它不是 null。
其他資源
本文會說明何謂模型繫結、其運作方式,以及如何自訂其行為。
檢視或下載範例程式碼 (如何下載)。
何謂模型繫結
控制器和 Razor Pages 使用來自 HTTP 要求的資料。 例如,路由資料可能會提供記錄索引鍵,而已張貼的表單欄位可能會提供模型屬性的值。 撰寫程式碼來擷取這些值的每一個並將它們從字串轉換成 .NET 類型,不但繁瑣又容易發生錯誤。 模型繫結會自動化此程序。 模型繫結系統:
- 從各種來源擷取資料,例如路由資料、表單欄位和查詢字串。
- 在方法參數和公開的屬性中,將資料提供給控制器和 Razor Pages。
- 將字串資料轉換成 .NET 類型。
- 更新複雜類型的屬性。
Example
假設您有下列的動作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
而應用程式收到具有此 URL 的要求:
http://contoso.com/api/pets/2?DogsOnly=true
在路由傳送系統選取該動作方法之後,模型繫結會逐步執行下列的步驟:
- 尋找第一個參數
GetById,它是名為id的整數。 - 查看 HTTP 要求中所有可用的來源,在路由資料中找到
id= "2"。 - 將字串 "2" 轉換成整數 2。
- 尋找
GetById的下一個參數 (一個名為dogsOnly的布林值)。 - 查看來源,在查詢字串中找到 "DogsOnly=true"。 名稱比對不區分大小寫。
- 將字串 "true" 轉換成布林值
true。
架構接著會呼叫 GetById 方法,針對 id 參數傳送 2、true 參數傳送 dogsOnly。
在上例中,模型繫結目標都是簡單型別的方法參數。 目標也可能是複雜類型的屬性。 成功系結每個屬性之後,就會針對該屬性進行 模型驗證 。 哪些數據系結至模型的記錄,以及任何系結或驗證錯誤,會儲存在 ControllerBase.ModelState 或 PageModel.ModelState 中。 若要找出此程式是否成功,應用程式會檢查 ModelState.IsValid 旗標。
Targets
模型繫結會嘗試尋找下列幾種目標的值:
- 要求路由目標的控制器動作方法參數。
- 將要求路由傳送到其中的目標 Razor Pages 處理常式方法的參數。
- 控制站的公用屬性或
PageModel類別,如由屬性指定。
[BindProperty] 屬性
可以套用至控制器或 PageModel 類別的公開屬性,以使模型繫結以該屬性為目標:
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
[BindProperties] 屬性
可用於 ASP.NET Core 2.1 或更高版本。 可以套用至控制器或 PageModel 類別,以告訴模型繫結以類別的所有公開屬性為目標:
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
適用於 HTTP GET 要求的模型繫結
根據預設,屬性不會針對 HTTP GET 要求繫結。 一般而言,GET 要求只需要記錄識別碼參數。 此記錄識別碼用來查詢資料庫中的項目。 因此,不需要繫結保存模型實例的屬性。 在您要從 GET 要求將屬性繫結至資料的案例中,請將 SupportsGet 屬性設為 true:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
Sources
根據預設,模型繫結會從下列 HTTP 要求的來源中,取得索引鍵/值組形式的資料:
- 表單欄位
- 要求本文 (針對具有 [ApiController] 屬性的控制器。)
- 路由資料
- 查詢字串參數
- 上傳的文件
對於每個目標參數或屬性,會依照前面清單中所指示的順序掃描來源。 但也有一些例外:
- 路由資料和查詢字串值只用於簡單型別。
- 上傳的檔案只繫結到實作
IFormFile或IEnumerable<IFormFile>的目標類型。
如果預設來源不正確,請使用下列其中一個屬性來指定來源:
-
[FromQuery]- 從查詢字串取得值。 -
[FromRoute]- 從路由資料取得值。 -
[FromForm]- 從發佈的表單欄位取得值。 -
[FromBody]- 從要求本文取得值。 -
[FromHeader]- 從 HTTP 標頭取得值。
這些屬性:
會個別新增至模型屬性 (不是模型類別),如下列範例所示:
public class Instructor { public int ID { get; set; } [FromQuery(Name = "Note")] public string NoteFromQueryString { get; set; }會選擇性地接受建構函式中的模型名稱值。 如果屬性名稱與要求中的值不相符,則會提供此選項。 例如,要求中的值可能是名稱中有連字號的標頭,如下列範例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 屬性
將 [FromBody] 屬性套用至參數,以從 HTTP 要求的本文中填入其屬性。 ASP.NET Core 執行階段會將讀取本文的責任委派給輸入格式器。
本文稍後會說明輸入格式器。
當 [FromBody] 套用至複雜的類型參數時,會忽略套用至其屬性的任何繫結來源屬性。 例如,下列的 Create 動作會指定其 pet 參數從本文填入:
public ActionResult<Pet> Create([FromBody] Pet pet)
類別 Pet 會指定其 Breed 屬性從查詢字串參數填入:
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
在前述範例中:
- 會忽略
[FromQuery]屬性。 -
Breed屬性不會從查詢字串參數填入。
輸入格式器只會讀取本文,而不了解繫結來源屬性。 如果在本文中找到適當的值,則會使用該值來填入 Breed 屬性。
請勿將 [FromBody] 套用至每個動作方法的多個參數。 一旦輸入格式器讀取要求串流後,即無法再次讀取以用來繫結其他 [FromBody] 參數。
其他來源
模型繫結系統從值提供者獲取原始數據。 您可以撰寫並註冊自訂值提供者,其從其他來源取得模型繫結資料。 例如,您可能想從 Cookie 或工作階段狀態取得一些資料。 若要從新來源取得資料:
- 建立會實作
IValueProvider的類別。 - 建立會實作
IValueProviderFactory的類別。 - 在
Startup.ConfigureServices中註冊 Factory 類別。
範例應用程式包含 值提供者 和 處理站 範例,可從 Cookie 取得值。 以下是 Startup.ConfigureServices 中的註冊碼:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
顯示的程式碼會將自訂值提供者放在所有內建值提供者的後面。 若要使其成為清單中的第一個,請呼叫 Insert(0, new CookieValueProviderFactory()) 而不是 Add。
無模型屬性的來源
預設情況下,如果未找到模型屬性的值,則不會建立模型狀態錯誤。 屬性設定為 null 或預設值:
- 可為 Null 的簡單類型會設為
null。 - 不可為 Null 的實值型別會設為
default(T)。 例如,參數int id設為 0。 - 對於複雜類型,模型繫結會使用預設的建構函式來建立實例,而不需要設定屬性。
- 陣列設為
Array.Empty<T>(),但byte[]陣列設為null。
如果在模型屬性的表單欄位中找不到任何內容時模型狀態應無效,則請使用 [BindRequired] 屬性。
請注意,此 [BindRequired] 行為適用於來自已張貼表單資料的模型繫結,不適合要求本文中的 JSON 或 XML 資料。 請求體數據由 輸入格式器處理。
類型轉換錯誤
如果找到來源但無法轉換為目標類型,則模型狀態將被標記為無效。 目標參數或屬性會設為 null 或預設值,如上一節中所述。
在具有 [ApiController] 屬性的 API 控制器中,無效的模型狀態會導致自動 HTTP 400 回應。
在 Razor 頁面中,重新顯示有錯誤訊息的頁面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
用戶端驗證會擷取大多數本應提交到 Razor Pages 表單的錯誤資料。 此驗證會讓您更難觸發上述醒目提示的程式碼。 範例應用程式包含一個 [以無效日期提交] 按鈕 (該按鈕會將錯誤資料放入 [雇用日期] 欄位中並提交表單)。 此按鈕會顯示當資料轉換錯誤發生時,重新顯示頁面的程式碼如何運作。
當上面的程式碼重新顯示該頁面時,無效的輸入不會顯示在表單欄位中。 這是因為模型屬性已設為 null 或預設值。 無效的輸入確實會出現在錯誤訊息中。 但是,如果您想要在表單欄位中重新顯示不正確的資料,請考慮讓模型屬性成為字串,以手動方式執行資料轉換。
如果您不希望類型轉換錯誤導致模型狀態錯誤,建議使用相同的策略。 在這種情況下,請將模型屬性設為字串。
簡單類型
模型繫結器可將來源字串轉換成的簡單型別包括:
- Boolean
- 位元組, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16、 Int32、 Int64
- Single
- TimeSpan
- UInt16、 UInt32、 UInt64
- Uri
- Version
複雜類型
複雜類型必須具有公開的預設建構函式和公開的可寫入屬性才能進行繫結。 發生模型繫結時,類別會使用公用預設建構函式具現化。
針對複雜類型的每個屬性,模型系結會查看名稱模式的來源 prefix.property_name。 如果找不到任何專案,它只會尋找不帶有前置詞的 property_name。
若要繫結至參數,則前置詞是參數名稱。 若要繫結至 PageModel 公用屬性,則前置詞為公用屬性名稱。 某些屬性 (Attribute) 具有 Prefix 屬性 (Property),其可讓您覆寫參數或屬性名稱的預設使用方法。
例如,假設複雜類型是下列 Instructor 類別:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前置詞 = 參數名稱
如果要繫結的模型是名為 instructorToUpdate 的參數:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型繫結從查看索引鍵 instructorToUpdate.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
前置詞 = 屬性名稱
如果要繫結的模型是控制器或 Instructor 類別名為 PageModel 的屬性:
[BindProperty]
public Instructor Instructor { get; set; }
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
自訂前置詞
如果要繫結的模型是名為 instructorToUpdate 的參數,且 Bind 屬性指定 Instructor 為前置詞:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型繫結從查看索引鍵 Instructor.ID 的來源開始。 如果找不到,它會尋找不含前置詞的 ID。
複雜類型目標的屬性
有數個內建屬性可用於控制複雜類型的模型繫結:
[Bind][BindRequired][BindNever]
Warning
當張貼的表單資料為值來源時,這些屬性會影響模型繫結。 它們 不會影響 輸入格式化程式,這些格式化程式負責處理張貼的 JSON 和 XML 請求主體。 本文稍後會說明輸入格式器。
[Bind]屬性
可以套用至類別或方法參數。 指定模型繫結應包含哪些模型屬性。
[Bind]
不會影響輸入格式器。
在下列範例中,當呼叫任何處理常式或動作方法時,只會繫結 Instructor 模型的指定屬性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下列範例中,當呼叫 Instructor 方法時,只會繫結 OnPost 模型的指定屬性:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]屬性可用來防止建立案例中過多提交。 它在編輯場景中效果不佳,因為排除的屬性會設為 null 或預設值,而不是保持不變。 建議使用檢視模型而非 [Bind] 屬性來防範大量指派。 如需詳細資訊,請參閱關於大量指派的安全性注意事項。
[ModelBinder] 屬性
ModelBinderAttribute 可以套用至類型、屬性或參數。 它允許指定用來繫結特定實例或類型的模型繫結器類型。 例如:
[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
[ModelBinder] 屬性也可以用來在它進行模型繫結時變更屬性或參數的名稱:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
public string Name { get; set; }
}
[BindRequired] 屬性
只能套用至模型屬性,不能套用到方法參數。 如果模型的屬性不能發生繫結,則會造成模型繫結新增模型狀態錯誤。 以下為範例:
public class InstructorWithCollection
{
public int ID { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
[BindRequired]
public DateTime HireDate { get; set; }
[BindNever] 屬性
只能套用至模型屬性,不能套用到方法參數。 避免模型繫結設定模型的屬性。 以下為範例:
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
Collections
對於屬於簡單類型集合的目標,模型绑定將尋找與 parameter_name 或 property_name 相匹配的項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設要繫結的參數是名為
selectedCourses的陣列:public IActionResult OnPost(int? id, int[] selectedCourses)表單或查詢字串資料可以是下列其中一種格式:
selectedCourses=1050&selectedCourses=2000selectedCourses[0]=1050&selectedCourses[1]=2000[0]=1050&[1]=2000selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b[a]=1050&[b]=2000&index=a&index=b如果名為
index或Index的參數或屬性與集合值相鄰,請避免繫結該參數或屬性。 模型系結會嘗試使用index作為集合的索引,這可能會導致不正確的繫結。 例如,請考慮下列動作:public IActionResult Post(string index, List<Product> products)在上面的程式碼中,
index查詢字串參數繫結到index方法參數,並且也用來繫結產品集合。 重新命名index參數或使用模型繫結屬性來設定繫結,可避免此問題:public IActionResult Post(string productIndex, List<Product> products)下列格式只在表單資料中受到支援:
selectedCourses[]=1050&selectedCourses[]=2000針對前列所有範例格式,模型繫結會將有兩個項目的陣列傳遞至
selectedCourses參數:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下標數字 (... [0] ... [1] ...) 的資料格式必須確定它們從零開始按順序編號。 下標編號中如有任何間距,則會忽略間隔後的所有項目。 例如,如果下標是 0 和 2,而不是 0 和 1,則忽略第二個項目。
Dictionaries
針對 Dictionary 目標,模型系結會尋找 parameter_name 或 property_name的相符項目。 如果找不到相符項目,它會尋找其中一種沒有前置詞的受支援格式。 例如:
假設目標參數是一個名為
Dictionary<int, string>的selectedCourses:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)已張貼的表單或查詢字串資料看起來會像下列其中一個範例:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics[1050]=Chemistry&selectedCourses[2000]=EconomicsselectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics針對前列所有範例格式,模型繫結會將有兩個項目的字典傳遞至
selectedCourses參數:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
建構函式繫結和記錄類型
模型繫結需要複雜類型具有無參數的建構函式。
System.Text.Json 和 Newtonsoft.Json 型的輸入格式器都支援還原序列化沒有無參數建構函式的類別。
C# 9 引入了記錄類型 (這是一個透過網路簡潔表示資料的好方法)。 ASP.NET Core 新增了一個使用單一建構函式來進行模型繫結和驗證記錄類型的支援:
public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
...
}
}
Person/Index.cshtml:
@model Person
<label>Name: <input asp-for="Name" /></label>
...
<label>Age: <input asp-for="Age" /></label>
驗證記錄類型時,執行階段會特別針對參數 (而不是屬性) 搜尋繫結和驗證中繼資料。
以下這個架構允許對記錄類型進行繫結和驗證:
public record Person([Required] string Name, [Range(0, 100)] int Age);
若要讓上述內容能夠運作,類型必須:
- 為記錄類型。
- 確切只有一個公開的建構函式。
- 包含具有相同名稱和類型之屬性的參數。 名稱的大小寫不得不同。
不含無參數之建構函式的 POCO
沒有無參數建構函式的 POCO 無法繫結。
下列程式碼會產生一個例外狀況訊息,指出類型必須具有無參數的建構函式:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
「具有手動編寫建構函式的記錄類型」
看起來像主要構造函數的「具有手動編寫建構函式的記錄類型」可以運作
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
記錄類型、驗證和繫結中繼資料
對於記錄類型,會使用參數上的驗證和繫結中繼資料。 而屬性上的任何中繼資料會被忽略
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
驗證和中繼資料
驗證會使用參數上的中繼資料,但會使用屬性來讀取值。 在具有主要建構函式的一般案例中,兩者會相同。 不過,有一些方法可以克服它:
public record Person([Required] string Name)
{
private readonly string _name;
public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}
TryUpdateModel 不會更新記錄類型上的參數
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
在本例中,MVC 不會嘗試再次繫結 Name。 不過,會允許更新 Age
模型繫結路由資料和查詢字串的全球化行為
ASP.NET Core 路由值提供者和查詢字串值提供者:
- 將值視為不因文化特性而異。
- 預期 URL 不因文化特性而異。
相反地,來自表單資料的值會進行區分文化特性的轉換。 這是有意為之的,以便 URL 可以跨不同地區設定來共用。
若要讓 ASP.NET Core 路由值提供者和查詢字串值提供者能夠進行區分文化特性的轉換:
- 繼承自 IValueProviderFactory
- 從 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 複製程序代碼
- 將傳遞至值提供者建構函式 的文化特性值 取代為 CultureInfo.CurrentCulture
- 將 MVC 選項中的預設值提供者 factory 取代為新的:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
});
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var query = context.ActionContext.HttpContext.Request.Query;
if (query != null && query.Count > 0)
{
var valueProvider = new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture);
context.ValueProviders.Add(valueProvider);
}
return Task.CompletedTask;
}
}
特殊資料類型
模型繫結可以處理某些特殊資料類型。
IFormFile 和 IFormFileCollection
HTTP 要求包含上傳的檔案。 也支援多個檔案的 IEnumerable<IFormFile>。
CancellationToken
動作可以選擇性地將 CancellationToken 繫結為參數。 這會繫結 RequestAborted,當 HTTP 要求底層的連線被中止時,它會發出訊號。 動作可以使用此參數來取消作為控制器動作的一部分執行的長時間執行的非同步作業。
FormCollection
用來擷取已張貼表單資料中的所有值。
輸入格式化程式
要求本文中的資料可以是 JSON、XML 或一些其他格式。 為了剖析此數據,模型系結會使用設定為處理特定內容類型的 輸入格式器 。 預設情況下,ASP.NET Core 包含 JSON 型的輸入格式器,用於處理 JSON 資料。 您可以為其他內容類型新增其他格式器。
ASP.NET Core 會根據 Consumes 屬性選取輸入格式器。 如果沒有屬性存在,它會使用 Content-Type 標頭。
使用內建的 XML 輸入格式器:
安裝
Microsoft.AspNetCore.Mvc.Formatters.XmlNuGet 套件。在
Startup.ConfigureServices中,呼叫 AddXmlSerializerFormatters 或 AddXmlDataContractSerializerFormatters。services.AddRazorPages() .AddMvcOptions(options => { options.ValueProviderFactories.Add(new CookieValueProviderFactory()); options.ModelMetadataDetailsProviders.Add( new ExcludeBindingMetadataProvider(typeof(System.Version))); options.ModelMetadataDetailsProviders.Add( new SuppressChildValidationMetadataProvider(typeof(System.Guid))); }) .AddXmlSerializerFormatters();將
Consumes屬性套用至要求本文應為 XML 的控制器類別或動作方法。[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)如需詳細資訊,請參閱 XML 序列化簡介。
使用輸入格式器自訂模型繫結
輸入格式器會完全負責從要求本文讀取資料。 若要自訂此程序,請設定輸入格式器所使用的 API。 本節描述如何自訂 System.Text.Json 型的輸入格式器,以了解一個名為 ObjectId 的自訂類型。
請考慮下列模型,其中包含一個名為 ObjectId 的自訂 Id 屬性:
public class ModelWithObjectId
{
public ObjectId Id { get; set; }
}
若要在使用 System.Text.Json 時自訂模型繫結程序,請建立一個衍生自 JsonConverter<T> 的類別:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
}
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Id);
}
}
若要使用自訂轉換器,請將 JsonConverterAttribute 屬性套用至該類型。 在下列範例中,ObjectId 類型會設定為使用 ObjectIdConverter 作為其自訂轉換器:
[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
public ObjectId(int id) =>
Id = id;
public int Id { get; }
}
如需詳細資訊,請參閱如何撰寫自訂轉換器。
排除模型繫結中的指定類型
模型繫結和驗證系統的行為是由 ModelMetadata 所驅動。 您可以將詳細數據提供者新增至ModelMetadata 來自定義。 內建的詳細資料提供者可用於停用模型繫結或驗證所指定類型。
若要停用指定類型之所有模型的模型繫結,請在 ExcludeBindingMetadataProvider 中新增 Startup.ConfigureServices。 例如,若要對類型為 System.Version 的所有模型停用模型繫結:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
若要停用指定類型屬性的驗證,請在 SuppressChildValidationMetadataProvider 中新增 Startup.ConfigureServices。 例如,若要針對類型為 System.Guid 的屬性停用驗證:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
自訂模型繫結器
您可以撰寫自訂的模型繫結器來擴充模型繫結,並使用 [ModelBinder] 屬性以針對特定目標來選取它。 深入了解自訂模型繫結。
手動模型繫結
使用 TryUpdateModelAsync 方法即可手動叫用模型繫結。 此方法已於 ControllerBase 和 PageModel 類別中定義。 方法多載可讓您指定要使用的前置詞和值提供者。 如果模型繫結失敗,此方法會傳回 false。 以下為範例:
if (await TryUpdateModelAsync<InstructorWithCollection>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
_instructorsInMemoryStore.Add(newInstructor);
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();
TryUpdateModelAsync 會使用值提供者從表單本文、查詢字串和路由資料取得資料。
TryUpdateModelAsync 通常:
- 會與使用控制器和檢視表的 Razor Pages 和 MVC 應用程式一起使用,以防止過度發佈。
- 不會與 Web API 一起使用 (除非從表單資料、查詢字串和路由資料中取用)。 取用 JSON 的 Web API 端點會使用 輸入格式器,將請求正文反序列化為物件。
如需詳細資訊,請參閱 TryUpdateModelAsync。
[FromServices] 屬性
此屬性的名稱會遵循指定資料來源的模型繫結屬性的模式。 但它與繫結來自值提供者的資料無關。 它會從 依賴注入 容器取得型別的實例。 其目的是只有在呼叫特定方法時,才在您需要服務時提供建構函式插入的替代項目。