ASP.NET Web API 2 之参数验证
Ø 前言
目前 C# 比较流行使用 ASP.NET Web API 来承载 Web 接口,提供与客户端之间的数据交互,现在的版本已经是 2.0 了。既然是接口就少不了对输入参数的验证,所以本文主要探讨下,Web API 中实现接口参数验证的几种方式:
1. 使用 Web API 过滤器验证。
2. 继承验证基类,重写验证方法。
1. 使用 Web API 过滤器验证(Demo 演示)
1) 定义个 DataRecordValidationAttribute 类,用于验证参数传递的 StudentNo 是否在数据库中存在,代码如下:
/// <summary>
/// 数据读取验证特性。
/// </summary>
public class DataRecordValidationAttribute : System.ComponentModel.DataAnnotations.ValidationAttribute
{
/// <summary>
/// 查询的数据表名称。
/// </summary>
public string TableName { get; set; }
/// <summary>
/// TableName 的主键字段名称。
/// </summary>
public string PKName { get; set; }
/// <summary>
/// 初始化 WebAPI2.Filter.WebAPIFilter.CustomValidationAttribute 的实例。
/// </summary>
/// <param name="tableName">查询的数据表名称。</param>
/// <param name="pkName">TableName 的主键字段名称。</param>
public DataRecordValidationAttribute(string tableName, string pkName)
{
this.TableName = tableName;
this.PKName = pkName;
}
/// <summary>
/// 根据当前的验证特性来验证指定的值。
/// </summary>
/// <param name="value">要验证的值。</param>
/// <param name="validationContext">有关验证操作的上下文信息。</param>
/// <returns>System.ComponentModel.DataAnnotations.ValidationResult 类的实例。</returns>
protected override System.ComponentModel.DataAnnotations.ValidationResult IsValid(
object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
return base.IsValid(value, validationContext);
}
/// <summary>
/// 确定对象的指定值是否有效。
/// </summary>
/// <param name="value">要验证的对象的值。</param>
/// <returns>如果指定的值有效,则为 true;否则,为 false。</returns>
public override bool IsValid(object value)
{
bool result = false;
try
{
using (DbContext dbContext = new DbContext("name=MyTestingEntities"))
{
DbCommand dbCommand = dbContext.Database.Connection.CreateCommand();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText =
string.Format("SELECT COUNT(1) FROM {0} WHERE {1}=@p1;", this.TableName, this.PKName);
DbParameter dbParameter = dbCommand.CreateParameter();
dbParameter.ParameterName = "@p1";
dbParameter.Value = value;
dbCommand.Parameters.Add(dbParameter);
dbCommand.Connection.Open();
result = Convert.ToInt16(dbCommand.ExecuteScalar()) > 0;
dbCommand.Connection.Close();
}
}
catch (Exception ex) { }
return result;
}
/// <summary>
/// 基于发生错误的数据字段对错误消息应用格式设置。
/// </summary>
/// <param name="name">要包括在带有格式的消息中的名称。</param>
/// <returns>带有格式的错误消息的实例。</returns>
public override string FormatErrorMessage(string name)
{
return name + " 验证失败";
}
}
2) 还需要定一个 ParameterValidAttribute 类,用于检查验证后的模型状态,代码如下:
/// <summary>
/// 参数验证特性。
/// </summary>
public class ParameterValidAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
/// <summary>
/// 在调用操作方法之前发生。
/// </summary>
/// <param name="actionContext">操作上下文。</param>
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
if (!actionContext.ModelState.IsValid) //未通过验证
{
//获取所有的验证错误信息
List<string> errorList = new List<string>();
System.Web.Http.ModelBinding.ModelStateDictionary dict = actionContext.ModelState;
IEnumerable<System.Web.Http.ModelBinding.ModelErrorCollection> errors = dict.Values.Select(o => o.Errors);
foreach (var items in errors)
{
foreach (var item in items)
errorList.Add(item.ErrorMessage);
}
if (errorList.Count > 0)
{
var httpResponse = new HttpResponseMessage(HttpStatusCode.BadRequest);
httpResponse.Content = new StringContent(string.Join("\n", errorList.ToArray()));
throw new System.Web.Http.HttpResponseException(httpResponse);
}
}
}
}
3) 定义个 StudentModel 模型,使用 DataRecordValidationAttribute 验证主键值是否有效。
/// <summary>
/// 学生模型。
/// </summary>
public class StudentModel
{
[DataRecordValidation("Student", "StudentNo")]
public string StudentNo { get; set; }
[System.ComponentModel.DataAnnotations.StringLength(3, MinimumLength = 2)]
public string Name { get; set; }
}
4) 操作方法上声明 ParameterValidAttribute 特性。
[HttpPost]
[ParameterValid]
public string One(StudentModel model)
{
return string.Format("StudentNo = {0}, Name = {1}", model.StudentNo, model.Name);
}
5) OK,下面是调用示例
1. 调用失败(StudentNo为"S005"的学生不存在;姓名长度不够)
2. 调用成功
2. 继承验证基类,重写验证方法(项目案例)
1) 定义请求模型基类
/// <summary>
/// 请求模型。
/// </summary>
public class RequestModel
{
/// <summary>
/// 检查当前实例是否为该数据类型的默认值。
/// </summary>
public bool IsDefaultValue<T>(T value)
{
T defaultValue = default(T);
return value == null || value.Equals(defaultValue);
}
/// <summary>
/// 检查主键 Key 是否为无效值。
/// </summary>
public bool IsKeyInvalid(long? value)
{
return value.HasValue && value.Value < 1;
}
/// <summary>
/// 参数验证。
/// </summary>
/// <param name="message">验证信息。</param>
/// <returns>true:验证成功;false:验证失败。</returns>
public virtual bool Validate(ref string message)
{
return true;
}
}
2) 定义实际请求模型
/// <summary>
/// 销售机会分页请求模型。
/// </summary>
public class SalesOpportunityPagingReqModel : RequestModel
{
/// <summary>
/// 搜索关键字(客户名/产品名)。
/// </summary>
public string Keywords { get; set; }
/// <summary>
/// 排序Key(0:默认;1:成交时间由近到远;2:成交金额由高到低;3:成交可能性由高到低;4:过期未成交优先)。
/// </summary>
public int OrderBy { get; set; }
public override bool Validate(ref string message)
{
if (base.Validate(ref message))
{
if (OrderBy < 0 || OrderBy > 4)
message = "排序Key 必须介于 0 至 4 之间";
}
return string.IsNullOrEmpty(message);
}
}
3) 定义参数验证过滤器
/// <summary>
/// 参数验证。
/// </summary>
public class ParameterValidateAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
/// <summary>
/// 在调用操作方法之前发生。
/// </summary>
/// <param name="actionContext">操作上下文。</param>
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string validateMessage = this.GetValidateMessage(actionContext);
if (!string.IsNullOrEmpty(validateMessage))
{
//var response = new ResponseModel<object>(ResponseStatusCode.ParameterError, validateMessage, null);
//actionContext.Response = actionContext.Request.CreateResponse<ResponseModel<object>>(
// HttpStatusCode.OK, response);
actionContext.Response = actionContext.Request.CreateResponse<string>(HttpStatusCode.BadRequest, validateMessage);
}
base.OnActionExecuting(actionContext);
}
/// <summary>
/// 根据 System.Web.Http.Controllers.HttpActionContext 对象,获取请求参数 RequestModel 派生对象的验证信息。
/// </summary>
/// <param name="actionContext">System.Net.Http.HttpRequestMessage 对象。</param>
/// <returns>验证信息。返回空表示验证成功,否则验证失败。</returns>
public string GetValidateMessage(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string result = null;
try
{
Type paraBaseType = typeof(RequestModel);
var parameterList = actionContext.ActionArguments.Values.ToList();
if (parameterList == null || parameterList.Count != 2) //参数数量不满足,则退出验证
return result;
var parameter = parameterList[1];
if(parameter == null) //只有一个参数时,例如:(HttpRequestMessage request)
return result;
Type parameterType = parameter.GetType();
if (parameter is RequestModel) //参数的基类型不为 RequestModel,则退出验证
{
//递归验证参数
Func<object, string, string> validate = null;
validate = (obj, mes) =>
{
var requestModel = obj as RequestModel;
if (obj != null)
{
if (requestModel.Validate(ref mes)) //为flase时,立即结束验证
{
var properties = obj.GetType().GetProperties();
foreach (var item in properties)
{
if (item.PropertyType.BaseType == paraBaseType)
{
var propObj = item.GetValue(obj, null);
mes = validate(propObj, mes);
if (!string.IsNullOrEmpty(mes))
break;
}
}
}
}
return mes;
};
result = validate(parameter, string.Empty);
}
}
catch (JsonSerializationException ex)
{
result = "参数序列化异常:{0}{1}".Fmt(
ex.InnerException != null ? ex.InnerException.Message : null, ex.Message);
}
catch (Exception ex)
{
//参数验证本身发生异常
result = "参数验证时发生异常:{0}{1}".Fmt(
ex.InnerException != null ? ex.InnerException.Message : null, ex.Message);
}
return result;
}
}
4) 注册全局验证过滤器,在 WebApiConfig.Register() 方法中加入一行代码即可。
config.Filters.Add(new ParameterValidateAttribute());
5) 添加 Action 操作方法
/// <summary>
/// 获取销售机会分页列表。
/// </summary>
/// <param name="request"></param>
/// <param name="model">分页模型。</param>
/// <returns></returns>
[Route("getSalesOpportunityPagingList")]
[System.Web.Http.HttpPost]
public HttpResponseMessage GetSalesOpportunityPagingList(HttpRequestMessage request, PagingReqModel<SalesOpportunityPagingReqModel> model)
{
return CreateHttpResponse(request, (isPaging) =>
{
return userService.GetSalesOpportunityPagingList(isPaging, base.CurrentUserId, model);
}, (data) =>
{
var response = new ResponseModel<PaginationSet<SalesOpportunityPagingResModel>>(ResponseStatusCode.OK, null, data);
var httpResponse = request.CreateResponse<ResponseModel<PaginationSet<SalesOpportunityPagingResModel>>>(HttpStatusCode.OK, response);
return httpResponse;
});
}
6) OK,下面是调用示例
1. 调用失败(排序Key 不在范围内)
2. 调用成功
Ø 总结
1. 使用 Web API 过滤器验证
1) 优点
1. 属于 Web API 框架的一部分,应该性能较好,是一套标准化的验证机制。
2. 支持多种验证规则,例如:数据类型、邮箱、电话号码、数值范围、字符串长度、正则表达式等验证。
2) 缺点
1. 学习成本较高,比较难以控制全局(除了更进一步的封装)。
2. 有时可能不够灵活。
2. 继承验证基类,重写验证方法
1) 优点
1. 功能强大,简单易用,可自定义任意的验证规则。
2. 具有全局性,可提高一定的开发效率。
2) 缺点
1. 性能方面可能有所降低,因为会不停的反射成员属性,有时还有可能递归调用(这是无法避免的)。
3. 共同点:
1) 都是基于模型验证,且都采用了 System.Web.Http.Filters.ActionFilterAttribute 动作过滤器。
2) 非模型参数时,两者都不能实现验证。
Ø 提示:如果有同学有其他的验证方案,或更高见解,欢迎随时讨论~。