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 = , 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 != )
{
return false;
} double latitude, longitude;
if (double.TryParse(parts[], out latitude) &&
double.TryParse(parts[], 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), , 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/

部分示例自于该网站

转载与引用请注明出处。

时间仓促,水平有限,如有不当之处,欢迎指正。

ASP.NET Web API编程——模型验证与绑定的更多相关文章

  1. ASP.NET Web API编程——路由

    路由过程大致分为三个阶段: 1)请求URI匹配已存在路由模板 2)选择控制器 3)选择操作 1匹配已存在的路由模板 路由模板 在WebApiConfig.Register方法中定义路由,例如模板默认生 ...

  2. ASP.NET Web API 管道模型

    ASP.NET Web API 管道模型 前言 ASP.NET Web API是一个独立的框架,也有着自己的一套消息处理管道,不管是在WebHost宿主环境还是在SelfHost宿主环境请求和响应都是 ...

  3. ASP.NET Web API编程——序列化与内容协商

    1 多媒体格式化器 多媒体类型又叫MIME类型,指示了数据的格式.在HTTP协议中多媒体类型描述了消息体的格式.一个多媒体类型包括两个字符串:类型和子类型. 例如: text/html.image/p ...

  4. ASP.NET Web API编程——构建api帮助文档

    1 概要 创建ASP.NET Web Api 时模板自带Help Pages框架. 2 问题 1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目 ...

  5. Asp.NetCore Web开发之模型验证

    在开发中,验证表单数据是很重要的一环,如果对用户输入的数据不加限制,那么当错误的数据提交到后台后,轻则破坏数据的有效性,重则会导致服务器瘫痪,这是很致命的. 所以进行数据有效性验证是必要的,我们一般通 ...

  6. Web Api 使用模型验证

    public class Person { public int Id { get; set; } [Required(ErrorMessage = "姓名不能为空啊啊啊!")] ...

  7. ASP.NET Web API编程——客户端调用

    可以使用HttpClient这个调用Web API,下面是HttpClient的定义,列举了一些常用的方法,其中还有一些没有列举,包括重载的方法. public class HttpClient : ...

  8. .Net Core Web Api使用模型验证验证参数合法性

    在接口开发过程中免不了要去验证参数的合法性,模型验证就是帮助我们去验证参数的合法性,我们可以在需要验证的model属性上加上Data Annotations特性后就会自动帮我们在action前去验证输 ...

  9. ASP.NET Web API编程——版本控制

    版本控制   版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通过VersionController ...

随机推荐

  1. Unity 网格合并

    从优化角度,Mesh需要合并. 从换装的角度(这里指的是换形状.换组成部件的换装,而不是挂点型的换装),都需要网格合并.材质合并.如果是人物的换装,那么需要合并SkinnedMeshRenderer, ...

  2. springboot 整合 MongoDB 实现登录注册,html 页面获取后台参数的方法

    springboot简介: Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不 ...

  3. Unity3D NGUI事件监听的综合管理

    首先,将Event Listener挂在按钮上 Event Listener的源码很简单 就是利用C#的时间委托机制 注册了UI场景的事件而已 public class UIEventListener ...

  4. Python函数学习——递归

    递归函数 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数. 函数实现过程 def calc(n): v = int(n//2) print(v) if v > ...

  5. C语言第一次博客作业

    一,PTA实验作业 题目1.温度转换 本题要求编写程序,计算华氏温度150°F对应的摄氏温度.计算公式:C=5×(F−32)/9,式中:C表示摄氏温度,F表示华氏温度,输出数据要求为整型. 1.实验代 ...

  6. Spring Boot入门教程1、使用Spring Boot构建第一个Web应用程序

    一.前言 什么是Spring Boot?Spring Boot就是一个让你使用Spring构建应用时减少配置的一个框架.约定优于配置,一定程度上提高了开发效率.https://zhuanlan.zhi ...

  7. STL --> set用法

    set用法 一.set和multiset基础 set和multiset会根据特定的排序准则,自动将元素进行排序.不同的是后者允许元素重复而前者不允许. 需要包含头文件: #include <se ...

  8. java排序算法(四):冒泡排序

    java排序算法(四):冒泡排序 冒泡排序是计算机的一种排序方法,它的时间复杂度是o(n^2),虽然不及堆排序.快速排序o(nlogn,底数为2).但是有两个优点 1.编程复杂度很低.很容易写出代码 ...

  9. Matlab绘图基础——图形修饰处理(入门)

    引入--标题.色条.坐标轴.图例等 例一: set(groot,'defaultAxesLineStyleOrder','remove','defaultAxesColorOrder','remove ...

  10. iptables.sh 初始化防火墙配置

    #!/bin/bash iptables -F iptables -X iptables -Z iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT ...