大部分人不能將核心運行時(System.Web 中的類)和 ASP.NET Web Forms 應用程序平臺(System.Web.UI 中的類)區分開來。數組
ASP.NET 開發團隊在簡單的核心運行時抽象之上建立了複雜的 Web Form 抽象和 ASP.NET MVC。正由於 ASP.NET MVC 框架創建在公共抽象之上,因此 ASP.NET MVC 框架能實現的任何功能,任何人也均可以實現。ASP.NET MVC 框架自己也由若干層抽象組成,從而使得開發人員可以選擇他們須要的 MVC 片斷,替換或修改他們不須要的片斷。對於後續的每個版本,ASP.NET MVC 團隊都開放了更多的框架內部定製點。服務器
ASP.NET MVC 4 中的模型系統包括幾個可擴展部分,其中包括使用元數據描述模型、驗證模型以及影響從請求中構造模型的能力。app
將請求數據(表單數據、查詢字符串數據、路由信息)轉換爲模型的過程稱爲模型綁定。模型綁定分爲兩個階段:框架
真實模型綁定過程當中使用的值都來自值提供器。值提供器的做用僅僅是訪問可以在模型綁定過程當中正確使用的信息。ASP.NET MVC 框架自帶的若干值提供器能夠提供如下數據源中的數據:ide
值提供器來自值提供器工廠,而且系統按照值提供器的註冊順序來從中搜尋數據(上面是默認順序)。開發人員能夠編寫本身的值提供器工廠和值提供器,而且還能夠把它們插入到包含在 ValueProviderFactories.Factories 中的工廠列表中。當模型綁按期間須要使用額外的數據源時,開發人員一般會選擇編寫本身的值提供器工廠和值提供器。函數
除了 ASP.NET MVC 自己包含的值提供器工廠之外,開發團隊也在 ASP.NET MVC Futures 中包含了另外一些值提供器工廠和值提供器:ui
模型擴展的另外一部分是模型綁定器。它們從值提供器系統中獲取值,並利用獲取的值建立新模型或者填充已有模型。ASP.NET MVC 中的默認模型綁定器(DefaultModelBinder)是一段很是強大的代碼,它能夠對傳統類、集合類、列表、數組、字典進行模型綁定。但默認模型綁定器不支持不可變對象(對象初始值經過構造函數設置,以後不能改變)。this
例如,因爲 CLR 中 Point 類是不可變的,所以咱們必須使用它的值構造一個新實例:spa
public class PointModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProvider = bindingContext.ValueProvider;
int x = (int)valueProvider.GetValue("X").ConvertTo(typeof(int));
int y = (int)valueProvider.GetValue("Y").ConvertTo(typeof(int));
return new Point(x, y);
}
}
當建立一個新的模型綁定器時,須要告知 MVC 框架存在一個新的模型綁定器(能夠在 ModelBinders.Binders 的全局列表中註冊新的模型綁定器)以及什麼時候使用它(可用 [ModelBinder] 特性來裝飾綁定類)。code
模型綁定器每每容易被忽略的一個責任是:驗證它們要綁定的值。上面的示例代碼未包含任何驗證邏輯,所以看上去很是簡單。下面是一個更完整的模型綁定器版本:
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// We first attempt to find values based on the model name, and if we can't find
// anything for the model name, we'll fall back to the empty prefix as appropriate.
if (!String.IsNullOrEmpty(bindingContext.ModelName) &&
!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
if (!bindingContext.FallbackToEmptyPrefix)
return null;
bindingContext = new ModelBindingContext
{
ModelMetadata = bindingContext.ModelMetadata,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
}
// We have to create a new model, because Point is immutable. When the type
// isn't immutable, we can always take in the existing object, when one exists,
// and update it instead. The existing object, if one exists, is available
// via bindingContext.Model. Instead, we'll put a temporary (empty) object into
// the binding context so that validation can succeed while we validate all
// the parameter values.
bindingContext.ModelMetadata.Model = new Point();
return new Point(
Get<int>(controllerContext, bindingContext, "X"),
Get<int>(controllerContext, bindingContext, "Y")
);
}
上述代碼新增了 2 項內容:
下面是 Get 方法的完整函數:
private TModel Get<TModel>(ControllerContext controllerContext,
ModelBindingContext bindingContext,
string name)
{
// Get the fully qualified name, because model state needs to use that, and not just
// the simple property name.
string fullName = name;
if (!String.IsNullOrWhiteSpace(bindingContext.ModelName))
fullName = bindingContext.ModelName + "." + name;
// Get the value from the value provider
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(fullName);
// Add the attempted value to model state, so that we can round-trip their
// value even when it's incorrect and incapable of being held inside the
// model itself (i.e., the user types "abc" for an int).
ModelState modelState = new ModelState { Value = valueProviderResult };
bindingContext.ModelState.Add(fullName, modelState);
// Get the ModelMetadata that represents this property, as we use several of its
// values, and it's necessary for validation
ModelMetadata metadata = bindingContext.PropertyMetadata[name];
// Convert the attempted value to null automatically
string attemptedValue = valueProviderResult.AttemptedValue;
if (metadata.ConvertEmptyStringToNull && String.IsNullOrWhiteSpace(attemptedValue))
attemptedValue = null;
TModel model;
bool invalidValue = false;
try
{
// Attempt to convert the value to the correct type
model = (TModel)valueProviderResult.ConvertTo(typeof(TModel));
metadata.Model = model;
}
catch (Exception)
{
// Conversion failed, so give back the default value for the type
// and set the attempted value into model metadata
model = default(TModel);
metadata.Model = attemptedValue;
invalidValue = true;
}
// Run the validators for the given property
IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(metadata, controllerContext);
foreach (var validator in validators)
foreach (var validatorResult in validator.Validate(bindingContext.Model))
modelState.Errors.Add(validatorResult.Message);
// Only add the "invalid value" message if there were no other errors, because things like
// required validation should trump conversion failures, and null/empty values will often
// fail both required validation and type-conversion validation
if (invalidValue && modelState.Errors.Count == 0)
modelState.Errors.Add(
String.Format(
"The value '{0}' is not a valid value for {1}.",
attemptedValue,
metadata.GetDisplayName()
)
);
return model;
}