zoukankan      html  css  js  c++  java
  • 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来实现对参数绑定过程的控制。

  • 相关阅读:
    AcWing 3772. 更新线路(BFS)
    AcWing 3760. 最大剩余油量(树的最长路径)
    AcWing 3771. 选取石子
    洛谷P2014—选课(树形DP)
    吴恩达机器学习ex1
    洛谷P1122—最大子树和(树形DP)
    论文中关于要使用600线的python代码
    mysql 笛卡尔积
    mysql 外键
    写参考文献有感
  • 原文地址:https://www.cnblogs.com/darrenji/p/5223938.html
Copyright © 2011-2022 走看看