1.模型驗證git
使用特性約束模型屬性web
可使用System.ComponentModel.DataAnnotations提供的特性來限制模型。json
例如,Required特性表示字段值不能爲空,Range特性限制數值類型的範圍。api
對實體類使用特性後,可使用ModelState.IsValid來判斷驗證是否經過。cookie
例:app
實體:框架
public class DataModel { public int Id { get; set; } public string Field1Name {get;set;} [Required] public string Field2Name { get; set; } }
控制器操做:ide
[HttpPost] public IHttpActionResult ModelValid(DataModel model) { if (!ModelState.IsValid) { throw new HttpResponseException(HttpStatusCode.BadRequest); } return Ok(model); }
客戶端調用:網站
HttpClient client = new HttpClient(); string url = "http://localhost/WebApi_Test/api/account/modelvalid"; using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) { var cont = new { Id = 1, Field1Name = "1name" }; HttpContent content = new StringContent(JsonConvert.SerializeObject(cont)); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); request.Content = content; HttpResponseMessage response = client.SendAsync(request).Result; Console.WriteLine("狀態碼:{0}",(int)response.StatusCode); var task = response.Content.ReadAsStringAsync(); task.Wait(); Console.WriteLine("結果:{0}", task.Result); }
輸出結果:ui
服務端運行截圖:
若客戶端傳值爲:var cont = new { Id = 1, Field1Name = "1name", Field2Name="2name" };
默認賦值
Web API會對客戶端未指定的模型屬性賦初值。對於int,double等數值類型默認的初值爲0,對於字符串或引用類型默認的初值是null。若是未對屬性使用特性加以約束,那麼ModelState.IsValid的值就是true,若對這樣的屬性應用Required特性,那麼當客戶端爲對其賦初值時,驗證將沒法經過,即ModelState.IsValid的值爲false。
例:
上例中不對Id屬性賦值,運行客戶端結果爲:
可見框架自動爲int型的Id賦初值0。
過載
此外當客戶端所用實體屬性多於服務端時,服務端會忽略多出來的屬性,但建議控制器操做(Action)所用參數列表的參數或類屬性與客戶端所傳參數徹底匹配。
例:
若使用上述客戶端,但傳值爲
var cont = new { Field1Name = "1name", Field2Name = "2name",FieldOverLoad ="overload"};
其中DataModel不包含FieldOverLoad 字段。
運行結果以下:
過濾驗證結果
能夠自定義操做過濾器來統一處理模型驗證失敗的情形。自定義操做過濾器派生自ActionFilterAttribute,咱們須要重寫OnActionExecuting方法,以便在操做(Action)調用以前處理。
例:
using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using System.Web.Http.ModelBinding; namespace MyApi.Filters { public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { if (actionContext.ModelState.IsValid == false) { actionContext.Response = actionContext.Request.CreateErrorResponse( HttpStatusCode.BadRequest, actionContext.ModelState); } } } }
在WebApiConfig的Register方法中將上述自定義過濾器添加進來,這樣過濾器對每個操做(Action)都起做用,若是不想使其對每個操做都起做用,而是想應用於個別操做(Action),能夠將此特性應用到目標操做(Action)
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Filters.Add(new ValidateModelAttribute()); // ... } } public class ProductsController : ApiController { [ValidateModel] public HttpResponseMessage Post(Product product) { // ... } }
2模型綁定
默認的綁定規則
1)若是操做(Action)參數是簡單類型,Web API框架會從URI中獲取值。簡單類型是指:.NET 框架定義的原始類型(int, bool, double等)、TimeSpan、DateTime、Guid、decimal、string;另外還有包含類型轉換器的類型,改轉換器可將字符串轉換爲此類型。這裏從URI獲取值具體指:從路由詞典中獲取值或者從URI的查詢字符串中獲取值。具體過程見介紹路由那篇博文。
2)對於複雜類型,Web API會使用多媒體格式化器從消息體中得到值。
類型轉換
默認的模型綁定規則中說起了包含類型轉換器的類型也是簡單類型。類型轉換器可使類也被看作簡單類型。這樣按照默認的規則就能夠從URI中獲取值來構建參數列表了。
例:使用TypeConverter特性指明所使用的類型轉換器。
[TypeConverter(typeof(GeoPointConverter))] public class GeoPoint { public double Latitude { get; set; } public double Longitude { get; set; } public static bool TryParse(string s, out GeoPoint result) { result = null; var parts = s.Split(','); if (parts.Length != 2) { return false; } double latitude, longitude; if (double.TryParse(parts[0], out latitude) && double.TryParse(parts[1], out longitude)) { result = new GeoPoint() { Longitude = longitude, Latitude = latitude }; return true; } return false; } } class GeoPointConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { GeoPoint point; if (GeoPoint.TryParse((string)value, out point)) { return point; } } return base.ConvertFrom(context, culture, value); } }
使用[FromUri]
爲了強制Web API從URI中取值,可使用FromUri特性。這樣即便操做(Action)參數是複雜類型,框架也會中URI中取值來爲參數賦值。
使用[FromBody]
爲了強制Web API從消息體中取值,可使用FromBody特性。這樣即便操做(Action)參數是簡單類型,框架也會從消息體中取值來爲參數賦值。當使用FromBody特性時,Web API使用請求的Content-Type標頭來選擇格式化器。
注意:對多個參數使用FromBody不起做用。
例:
服務端操做爲:
[HttpPost] public IHttpActionResult ModelValid([FromBody]DataModel model) { if (!ModelState.IsValid) { throw new HttpResponseException(HttpStatusCode.BadRequest); } return Ok(model); }
客戶端調用爲:
HttpClient client = new HttpClient(); string url = "http://localhost/WebApi_Test/api/account/modelvalid"; using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) { //var cont = new { Id = 1, Field1Name = "111" }; var cont = new { Field1Name = "1name", Field2Name = "2name"}; HttpContent content = new StringContent(JsonConvert.SerializeObject(cont)); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); request.Content = content; HttpResponseMessage response = client.SendAsync(request).Result; Console.WriteLine("狀態碼:{0}",(int)response.StatusCode); var task = response.Content.ReadAsStringAsync(); task.Wait(); Console.WriteLine("結果:{0}", task.Result); }
運行客戶端能夠正常得到結果,若使用FromUri,沒法經過模型綁定驗證,也沒法得到結果。
改變客戶端傳值的方式:
HttpClient client = new HttpClient(); string url = "http://localhost/WebApi_Test/api/account/modelvalid?Field1Name=1name&Field2Name=2name"; using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) { HttpResponseMessage response = client.SendAsync(request).Result; Console.WriteLine("狀態碼:{0}",(int)response.StatusCode); var task = response.Content.ReadAsStringAsync(); task.Wait(); Console.WriteLine("結果:{0}", task.Result); }
運行結果爲:
自定義模型綁定器
模型綁定器從值提供器(value provider)中得到原始輸入,這種設計拆分出兩個不一樣的功能:
1)值提供器使用HTTP請求而且填充一個詞典。
2)模型綁定器使用這個詞典填充模型。
默認的值提供器從請求URI的查詢字符串和路由詞典中獲取值。要綁定的參數的名稱保存在ModelBindingContext.ModelName屬性中,模型綁定器在詞典中找相應的鍵值對。若是鍵值對存在,而且可以轉換爲待處理模型,模型綁定器分配綁定值給ModelBindingContext.Model屬性。模型綁定器不會限制簡單類型的轉換。自定義模型綁定器須要實現IModelBinder接口。
例:
public class GeoPointModelBinder : IModelBinder { // List of known locations. private static ConcurrentDictionary<string, GeoPoint> _locations = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase); static GeoPointModelBinder() { _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 }; _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 }; _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 }; } public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { if (bindingContext.ModelType != typeof(GeoPoint)) { return false; } ValueProviderResult val = bindingContext.ValueProvider.GetValue( bindingContext.ModelName); if (val == null) { return false; } string key = val.RawValue as string; if (key == null) { bindingContext.ModelState.AddModelError( bindingContext.ModelName, "Wrong value type"); return false; } GeoPoint result; if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result)) { bindingContext.Model = result; return true; } bindingContext.ModelState.AddModelError( bindingContext.ModelName, "Cannot convert value to Location"); return false; } }
使用上述自定義的模型綁定器的方式有多種。
方式1、對於一個操做(Action)。
例:
public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)
方式2、對於一個控制器。
例:
[ModelBinder(typeof(GeoPointModelBinder))] public class GeoPoint { // .... }
方式3、註冊模型綁定器後,依然要使用在操做上使用特性,不過不用指定類型
例:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { var provider = new SimpleModelBinderProvider( typeof(GeoPoint), new GeoPointModelBinder()); config.Services.Insert(typeof(ModelBinderProvider), 0, provider); // ... } } public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }
自定義值提供器
模型綁定器從值提供器中獲取值,自定義值提供器須要實現IValueProvider接口。
例:
public class CookieValueProvider : IValueProvider { private Dictionary<string, string> _values; public CookieValueProvider(HttpActionContext actionContext) { if (actionContext == null) { throw new ArgumentNullException("actionContext"); } _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (var cookie in actionContext.Request.Headers.GetCookies()) { foreach (CookieState state in cookie.Cookies) { _values[state.Name] = state.Value; } } } public bool ContainsPrefix(string prefix) { return _values.Keys.Contains(prefix); } public ValueProviderResult GetValue(string key) { string value; if (_values.TryGetValue(key, out value)) { return new ValueProviderResult(value, value, CultureInfo.InvariantCulture); } return null; } }
建立值提供器工廠,其派生自ValueProviderFactory。
public class CookieValueProviderFactory : ValueProviderFactory { public override IValueProvider GetValueProvider(HttpActionContext actionContext) { return new CookieValueProvider(actionContext); } }
註冊值提供器工廠。
public static void Register(HttpConfiguration config) { config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory()); // ... }
使用值提供器工廠,指定使用CookieValueProvider。
public HttpResponseMessage Get( [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)
自定義HttpParameterBinding
ModelBinderAttribute繼承自ParameterBindingAttribute,ParameterBindingAttribute繼承自Attribute,ParameterBindingAttribute只有一個方法GetBinding,改=該方法返回HttpParameterBinding。HttpParameterBinding表明了參數與值之間的綁定關係。
public class ModelBinderAttribute : ParameterBindingAttribute {......} public abstract class ParameterBindingAttribute : Attribute { protected ParameterBindingAttribute(); // 得到參數綁定 // parameter:參數描述 public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter); }
例:利用請求頭中的if-match或if-none-match得到ETags。
public class ETag { public string Tag { get; set; } } public enum ETagMatch { IfMatch, IfNoneMatch } public class ETagParameterBinding : HttpParameterBinding { ETagMatch _match; public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match) : base(parameter) { _match = match; } public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { EntityTagHeaderValue etagHeader = null; switch (_match) { case ETagMatch.IfNoneMatch: etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault(); break; case ETagMatch.IfMatch: etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault(); break; } ETag etag = null; if (etagHeader != null) { etag = new ETag { Tag = etagHeader.Tag }; } actionContext.ActionArguments[Descriptor.ParameterName] = etag; var tsc = new TaskCompletionSource<object>(); tsc.SetResult(null); return tsc.Task; } }
爲使用自定義的HttpParameterBinding,定義一個派生自ParameterBindingAttribute的類。
public abstract class ETagMatchAttribute : ParameterBindingAttribute { private ETagMatch _match; public ETagMatchAttribute(ETagMatch match) { _match = match; } public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) { if (parameter.ParameterType == typeof(ETag)) { return new ETagParameterBinding(parameter, _match); } return parameter.BindAsError("Wrong parameter type"); } } public class IfMatchAttribute : ETagMatchAttribute { public IfMatchAttribute() : base(ETagMatch.IfMatch) { } } public class IfNoneMatchAttribute : ETagMatchAttribute { public IfNoneMatchAttribute() : base(ETagMatch.IfNoneMatch) { } }
在控制器操做(Action)中使用它。
public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }
另一種使用自定義的HttpParameterBinding的方式是利用HttpConfiguration.ParameterBindingRules這個屬性。
例:
config.ParameterBindingRules.Add(p => { if (p.ParameterType == typeof(ETag) && p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)) { return new ETagParameterBinding(p, ETagMatch.IfNoneMatch); } else { return null; } });
可插拔服務IActionValueBinder
整個模型綁定過程是由IActionValueBinder服務控制器的。其默認實現完成如下工做:
1)在參數中查找ParameterBindingAttribute,包括[FromBody], [FromUri], and [ModelBinder], 或者自定義特性。
2)若是步奏1)中沒有找到,那麼在HttpConfiguration.ParameterBindingRules中尋找一個返回值爲HttpParameterBinding的方法。
3)若是沒有找到就使用默認規則。
若是操做(Action)參數是簡單類型,Web API框架會從URI中獲取值。簡單類型是指:.NET 框架定義的原始類型(int, bool, double等)、TimeSpan、DateTime、Guid、decimal、string;另外還有包含類型轉換器的類型,改轉換器可將字符串轉換爲此類型。這裏從URI獲取值具體指:從路由詞典中獲取值或者從URI的查詢字符串中獲取值。具體過程見介紹路由那篇博文。對於複雜類型,Web API會使用多媒體格式化器從消息體中得到值。
參考:
https://docs.microsoft.com/en-us/aspnet/web-api/
部分示例自於該網站
轉載與引用請註明出處。
時間倉促,水平有限,若有不當之處,歡迎指正。