透過Custom ValueProvider可以額外的處理一些資料格式
ASP.NET MVC Model Binding 提供了方常方便的機制,而且一般來說,能使用Model Binding 就應該盡量優先於其他方法(譬如FormCollection或是多個Parameter)
而 Default Model Binding 使用了 ValueProvider 來設定一個class 相對應的 property,例如 Product class,傳入一個controller 的Index method
public class Product {
public String ProductID { get; set; }
public String ProductName { get; set; }
public Decimal Price { get; set; }
}public class ProductController : Controller {
[HttpPost]
public ActionResult Index(Product p) {
this.ViewData.Model = p;
return View();
}
}line 4,Index method 內的參數 p(Product) 在建立時,其內的property (ProductID, ProductName, Price)的值依據 ValueProvider 來取得,而ValueProvider 由ValueProviderFactory 內的 GetValueProvider 取得,預設的ValueProviderFactory共有4個,存在ValueProviderFactories內,依序為
- FormValueProviderFactory
- RouteDataValueProviderFactory
- QueryStringValueProviderFactory
- HttpFileCollectionValueProviderFactory
為何說”依序”,因為在Binding 的機制,依據property name(例如ProductID),從這些ValueProviderFactory(4個)中以找到第1筆符合的資料為優先,詳細的內容不是這篇文章的主題,因此就先不談,不過看名稱應該也猜的出來這4個ValueProviderFactory 是負責處理哪些資料
再回歸主題,一個簡單的Product Edit 功能
Product class 做一些修改,增加Validation Attribute
public class Product {
[Required]
public String ProductID { get; set; }
[Required]
public String ProductName { get; set; }
[Required]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N0}")]
public Decimal Price { get; set; }
}Controller : ProductController
public class ProductController : Controller {
public ActionResult Index() {
ViewData.Model = new Product();
return View();
}
[HttpPost]
public ActionResult Index(Product p) {
this.ViewData.Model = p;
return View();
}
}
View : Index.aspx,使用client validation
<% Html.EnableClientValidation(); %>
<% using(Html.BeginForm()){ %>
<%: Html.EditorForModel() %>
<input type="submit" value="Submit" />
<%} %>這時候會發生一個問題,就是Price如果有含千分位,例如1,234,在client validation是通過的,但在Model Binding 會發生問題
原因是Price 是decimal type, 當傳入1,234 會出錯,這種問題有不同的解法,而此處利用Custom Value Provider來處理Price Property
Create 一個 PriceValueProviderFactory class, 繼承ValueProviderFactory,實作一個回傳IValueProvider 的 GetValueProvider method
public class PriceValueProviderFactory : ValueProviderFactory{
public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
return new PriceValueProvider(controllerContext.HttpContext);
}
class PriceValueProvider : IValueProvider {
private HttpContextBase httpContext;
public PriceValueProvider(HttpContextBase httpContext) {
this.httpContext = httpContext;
}
#region IValueProvider 成員
public bool ContainsPrefix(string prefix) {
return prefix.Contains("Price");
}
public ValueProviderResult GetValue(string key) {
if(!ContainsPrefix(key)) return null;
string price = httpContext.Request.Form[key];
return new ValueProviderResult(decimal.Parse(price), price, CultureInfo.CurrentCulture);
}
#endregion
}
}
再Create PriceValueProvider class 實作 IValueProvider interface,而PriceValueProvider 處理了”Price”的資料 (在此就不作嚴謹的Validation 了)
最後再將PriceValueProviderFactory 加入到ValueProviderFactories,但前面有提到有ValueProviderFactory 有序順的因素,因此將他加入到第一個,確保優先被Select 到
在global.asax的 Application_Start method 加入一行即可
ValueProviderFactories.Factories.Insert(0, new PriceValueProviderFactory());
既然是增加到ValueProviderFactories,同樣對 TryUpdateModel 一樣有效
當然若只是單一上述的 Price 狀況,用Custom Value Provider ,可能過於overhead,但若是整個系統的Price 都有此問題考量,就可以考慮實作出特定的ValueProvider
例如系統UI 有關日期的input 必須以民國年輸入條件,但資料背後都是以西元年處理,這時在Custom ValueProvider 就處理掉民國年 <-> 西元年的工作