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. AngularJS中实现服务端分页

    这个教程将介绍在AngularJS应用中的服务端分页处理.在任何涉及到列表或表格数据的应用中都可能会用到分页. 概念 当我们处理异步分页时,每次只从服务器上获取一页数据.也就是说当用户点击第二页,就只 ...

  2. myeclipse设置环境(最实用的教程)

    1. General --> Workspace --> UTF-82. General --> Editors --> Associations --> JSP --& ...

  3. handsontable 方法汇总

    核心方法 1.为handsontable添加钩子方法 addHook(key,callback):key为钩子方法名 例如:hot.addHook('beforeInit', myCallback); ...

  4. SqlServer之like、charindex、patindex 在有无索引的情况下分析

    1.环境介绍 测试环境 SQL2005 测试数据 200W条 2.环境准备 2.1建表 CREATE TABLE [dbo].[Depratments](         [Dep_id] [int] ...

  5. 本站已稳定运行了XX天,网页时间显示功能实现方法

    这个是我昨晚上添加的效果, 分为三个部分: 1.本站已稳定运行了多少天的实现: 直接把下面代码复制到后台你想添加的位置即可. 1 2 3 4 5 6 7 8 9 本站已稳定运行了 <strong ...

  6. 关于现在IT行业从业者一些建议

    本人从事IT行业,确切的说应该是软件开发行业已经9个年头了,从刚开始小白也慢慢的已经有了自己独有的开发习惯. 近些年进入行业的人越来越多,有可能确实看到了这行业就业及薪资待遇,更多的也是随着互联网及移 ...

  7. 基于Three.js的360度全景--photo-sphere-viewer--简介

    这个是基于three.js的全景插件  photo-sphere-viewer.js  ---------------------------------------- 1.能添加热点: 2.能调用陀 ...

  8. java编程基础知识及常见例题

    ⒈标识符: 只能包含数字.字母.下划线.$,并且不能以数字开头.语义直观规范 驼峰法则: 如:方法名.变量名采用驼峰法则 帕斯卡命名法: 如: 类.接口.枚举采用帕斯卡命名法包名:网址倒写,com.网 ...

  9. C语言描述链表的实现及操作

    一.链表的创建操作 // 操作系统 win 8.1 // 编译环境 Visual Stuido 2017 #include<stdio.h> #include<malloc.h> ...

  10. vue/axios请求拦截

    import axios from 'axios';import { Message } from 'element-ui';import Cookies from 'js-cookie';impor ...