你的位置:首页 > ASP.net教程

[ASP.net教程]ASP.NET Web API中的参数绑定总结


 

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来实现对参数绑定过程的控制。