ASP.NET Web API中的action參數類型可以分為簡單類型和複雜類型。HttpResponseMessage Put(int id, Product item)id是int類型,是簡單類型,item是Product類型,是複雜類型。簡單類型實參值從哪裡讀取呢?--一般從URI中讀取所謂的
ASP.NET Web API中的action參數類型可以分為簡單類型和複雜類型。
HttpResponseMessage Put(int id, Product item)
id是int類型,是簡單類型,item是Product類型,是複雜類型。
簡單類型實參值從哪裡讀取呢?
--一般從URI中讀取
所謂的簡單類型包括哪些呢?
--int, bool, double, TimeSpan, DateTime, Guid, decimal, string,以及能從字元串轉換而來的類型
複雜類型實參值從哪裡讀取呢?
--一般從請求的body中讀取
複雜類型實參值是否可以從URI中獲取呢?
--可以,按如下
→ 有這樣的一個類
public class Shape { public double Width{get;set;} public double Length{get;set;} }
→ 想從URI中獲取,那就加上[FromUri]
public HttpResponseMessage Get([FromUri] Shape shape)
→ 客戶端就可以放在查詢字元串中傳
...api/products/?Width=88&Length=199
簡單類型可以從請求的body中獲取嗎?
--可以。按如下:
→ action方法
public HttpResponseMessage Post([FromBody] string name){...}
→ 前端請求中
Content-Type:applicaiton/json
"hello world"
API服務端會根據Content-Type的值選擇合適的媒體類型。
複雜類型是否可以從uri中的字元串獲取呢?
--可以
api/products/?shape=188,80
如何把uri中查詢字元串中shape的欄位值,即以逗號分割的字元串轉換成Shape類實例呢?
--使用TypeConverter類
[TypeConverter(typeof(ShapeConverter))] public class Shape { public double Width{get;set;} public double Length{get;set;} public static bool TryParse(string s, out Shampe result) { result = null; var parts = s.Split(','); if(parts.lenth != 2) { return false; } double width, length; if(double.TryParse(parts[0], out width) && double.TryParse(parts[1], out length)) { result = new Shape(){Width = width; Length = length}; return true; } return false; } } public class ShapeConverter: TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourcType) { if(sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo, object value) { if(value is string) { Shape shape; if(Shape.TryParse((string)value, out shape)) { return shape; } } return base.ConvertFrom(context, culture, value); } }
→ 在action不需要[FromUri]
public HttpResponseMessage Get(Shape shape)
→ 客戶端
api/products/?shape=188,80
是否可以通過Model Binder來實現自定義參數綁定過程呢?
--可以,有IModelBinder介面,提供了BindModel方法
→ 自定義一個Model Binder
public class ShapeModelBinder : IModelBinder { private static ConcurrentDictionary<string, Shape> _shapes = new ConcurrentDictionary<string, Shape>(StringComparer.OrdinalIgnoreCase); static ShapeModelBinder() { _shapes["shape1"] = new Shape(){Width= 10, Length = 20}; _shapes["shape2"] = new Shape(){Width=12, Length = 22 }; } public bool BindModel(HttpActionContext actionContext, ModelBindingContect biningContext) { if(bindingContext.ModelType != typeof(Shape)) { return false; } ValueProviderResult val = bindingContext.ValueProvider.GetValue(bidingContext.ModelName); if(val == null) { return false; } string key = val.RawValue as string; if(key == null){ bdingContext.ModelState.AddModelError(bindingContext.ModelName, "值類型錯誤"); return false; } Shape shape; if(_shapes.TryGetValue(key, out shape) || Shape.TryParse(key, shape)) { bindingContext.Model = result; return true; } bindingContext.ModelState.AddModelError(bindingContext.ModelName, "無法把字元串轉換成Shape"); return false; } }
● 從BindingContext中的ValueProvider屬性獲取到ValueProviderResult
● 從前端查詢字元串中傳來的字元串,被放在ValueProviderResult的RawValue屬性中
● 把字元串轉換成Shape實例,最終放在了BindingContext的Model屬性中
→ 使用自定義的Model Binder
可以運用在action中:
public HttpResposneMessage Get([ModelBinder(typeof(ShapeModelBinder))] Shape shape);
可以放在模型上:
[ModelBinder(typeof(Shape))] public class Shape { }
也可以放在全局註冊中:
public static class WebApiConfig { public static void Register(HttpConfiguraiton config) { var provider = new SimpleModelBinderProvider(typeof(Shape), new ShapeModelBinder()); config.Services.Insert(typeof(ModelBinderProvider), 0, provider); } }
註意:即使在全局註冊,也需要在action中按如下寫:
public HttpResponseMessage Get([ModelBinder] Shape shape);
是否可以通過Value Provider來自定義參數綁定過程呢?
--可以。
比如,從前端cookie中獲取值,自定義一個Value Provider.
public class MyCookieValueProvider : IValueProvider { private Dictionary<string, string> _values; public MyCookieValueProvider(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 MyCookieValueProviderFactory : ValueProviderFactory { public override IValueProvider GetValueProvider(HttpActionContext actionContext) { return new MyCookeValueProvider(actionContext); } }
最後註冊到全局中。
public static void Register(HttpConfiguration config) { config.Services.Add(typeof(ValueProviderFactory), new MyCookieValueProviderFactory()); }
還可以把自定義的ValueProvider放在action中。
public HttpResponseMessage Get([ValueProvider(typeof(MyCookieValueProviderFactory))] Shape shape);
是否可以通過HttpParameterBinding實現參數綁定自定義呢?
--可以。
ModelBinderAttribute繼承於ParameterBindingAttribute.
public abstract class ParameterBindingAttribute : Attribute { public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter); }
HttpParameterBinding用來把值綁定到參數上。
假設,需要從前端請求的if-match和if-none-match欄位獲取ETag值。
public class ETag { public string Tag{get;set;} }
可能從if-match獲取,也可能從if-none-match獲取,來個枚舉。
public enum ETagMatch { IfMatch, IfNoneMatch }
自定義HttpParameterBinding。
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 candellationToken) { 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.ActionArguemnts[Descriptor.ParameterName] = etag; var tsc = new TaskCompletionSource<object>(); tsc.SetResult(null); return tsc.Task; } }
可見,所有的action參數放在了ActionContext的ActionArguments中的。
如何使用自定義的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("參數類型不匹配"); } } public class IfMatchAttribute : ETageMatchAttribute { public IfMatchAttribute(): base(ETagMatch.IfMatch) {} } public class IfNoneMatchAttribute: ETagMatchAttribute { public IfNoneMatchAttribute() : base(ETagMatch.IfNoneMatch) {} }
再把定義的有關HttpParameterBinding的特性運用到方法上。
public HttpResponseMessage Get([IfNoneMatch] ETag etag)
還需要在全局註冊:
config.ParameterBindingRules.Add(p => { if(p.ParameterType == typeof(ETag) && p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)) { return new ETagParameterBinding(p, ETagMatch.IfNoneMatch); } else { return null; } })
總結,本篇體驗了簡單類型和複雜類型獲取前端數據的方式。並通過自定義ValueProvider, ModelBinder, HttpParameterBinding來實現對參數綁定過程的控制。