zoukankan      html  css  js  c++  java
  • ASP.NET Web API编程——模型验证与绑定

     1.模型验证

    使用特性约束模型属性

    可以使用System.ComponentModel.DataAnnotations提供的特性来限制模型。

    例如,Required特性表示字段值不能为空,Range特性限制数值类型的范围。

    对实体类使用特性后,可以使用ModelState.IsValid来判断验证是否通过。

    例:

    实体:

    public class DataModel
    {
            public int Id { get; set; }
    
            public string Field1Name {get;set;}
            [Required]
            public string Field2Name { get; set; }
    
    }

    控制器操作:

            [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);
                }

    输出结果:

    服务端运行截图:

    若客户端传值为: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;
        }
    }

    使用上述自定义的模型绑定器的方式有多种。

    方式一、对于一个操作(Action)。

    例:

    public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)

    方式二、对于一个控制器。

    例:

    [ModelBinder(typeof(GeoPointModelBinder))]
    public class GeoPoint
    {
        // ....
    }

    方式三、注册模型绑定器后,依然要使用在操作上使用特性,不过不用指定类型

    例:

    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继承自ParameterBindingAttributeParameterBindingAttribute继承自AttributeParameterBindingAttribute只有一个方法GetBinding,改=该方法返回HttpParameterBindingHttpParameterBinding代表了参数与值之间的绑定关系。

    public class ModelBinderAttribute : ParameterBindingAttribute
    {......}
    public abstract class ParameterBindingAttribute : Attribute
    {
            protected ParameterBindingAttribute();
    
            // 获得参数绑定
            // parameter:参数描述
            public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
    }

    例:利用请求头中的if-matchif-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/

    部分示例自于该网站

    转载与引用请注明出处。
    
    时间仓促,水平有限,如有不当之处,欢迎指正。
  • 相关阅读:
    推介一款小工具——SwitchHosts
    Postman的使用之进行文件上传
    Postman的使用之普通的提交Post和Get请求
    Postman的安装
    Windows配置基础环境(jdk+tomcat)
    yum clean all大坑解决
    通过代理实现访问内网系统
    批量配置免密登录
    设置JRebel热部署【本地模式】
    使用多线程程序模拟实现单生产者/多消费者问题 (Linux 线程锁实践)
  • 原文地址:https://www.cnblogs.com/hdwgxz/p/8759886.html
Copyright © 2011-2022 走看看