天天看点

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

    /// TableName 的主键字段名称。

    public string PKName { get; set; }

    /// 初始化 WebAPI2.Filter.WebAPIFilter.CustomValidationAttribute 的实例。

    /// <param name="tableName">查询的数据表名称。</param>

    /// <param name="pkName">TableName 的主键字段名称。</param>

    public DataRecordValidationAttribute(string tableName, string pkName)

    {

        this.TableName = tableName;

        this.PKName = pkName;

    }

    /// 根据当前的验证特性来验证指定的值。

    /// <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);

    /// 确定对象的指定值是否有效。

    /// <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;

    /// 基于发生错误的数据字段对错误消息应用格式设置。

    /// <param name="name">要包括在带有格式的消息中的名称。</param>

    /// <returns>带有格式的错误消息的实例。</returns>

    public override string FormatErrorMessage(string name)

        return name + " 验证失败";

}

2)   还需要定一个 ParameterValidAttribute 类,用于检查验证后的模型状态,代码如下:

/// 参数验证特性。

public class ParameterValidAttribute : System.Web.Http.Filters.ActionFilterAttribute

    /// 在调用操作方法之前发生。

    /// <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 验证主键值是否有效。

/// 学生模型。

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"的学生不存在;姓名长度不够)

ASP.NET Web API 2 之参数验证

2.   调用成功

ASP.NET Web API 2 之参数验证

2.   继承验证基类,重写验证方法(项目案例)

1)   定义请求模型基类

/// 请求模型。

public class RequestModel

    /// 检查当前实例是否为该数据类型的默认值。

    public bool IsDefaultValue<T>(T value)

        T defaultValue = default(T);

        return value == null || value.Equals(defaultValue);

    /// 检查主键 Key 是否为无效值。

    public bool IsKeyInvalid(long? value)

        return value.HasValue && value.Value < 1;

    /// 参数验证。

    /// <param name="message">验证信息。</param>

    /// <returns>true:验证成功;false:验证失败。</returns>

    public virtual bool Validate(ref string message)

        return true;

2)   定义实际请求模型

/// 销售机会分页请求模型。

public class SalesOpportunityPagingReqModel : RequestModel

    /// 搜索关键字(客户名/产品名)。

    public string Keywords { get; set; }

    /// 排序Key(0:默认;1:成交时间由近到远;2:成交金额由高到低;3:成交可能性由高到低;4:过期未成交优先)。

    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)   定义参数验证过滤器

/// 参数验证。

public class ParameterValidateAttribute : System.Web.Http.Filters.ActionFilterAttribute

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

    /// 根据 System.Web.Http.Controllers.HttpActionContext 对象,获取请求参数 RequestModel 派生对象的验证信息。

    /// <param name="actionContext">System.Net.Http.HttpRequestMessage 对象。</param>

    /// <returns>验证信息。返回空表示验证成功,否则验证失败。</returns>

    public string GetValidateMessage(System.Web.Http.Controllers.HttpActionContext actionContext)

        string result = null;

        {

            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)

            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(

4)   注册全局验证过滤器,在 WebApiConfig.Register() 方法中加入一行代码即可。

config.Filters.Add(new ParameterValidateAttribute());

5)   添加 Action 操作方法

/// 获取销售机会分页列表。

/// <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 不在范围内)

ASP.NET Web API 2 之参数验证
ASP.NET Web API 2 之参数验证

Ø  总结

1.   使用 Web API 过滤器验证

1)   优点

1.   属于 Web API 框架的一部分,应该性能较好,是一套标准化的验证机制。

2.   支持多种验证规则,例如:数据类型、邮箱、电话号码、数值范围、字符串长度、正则表达式等验证。

2)   缺点

1.   学习成本较高,比较难以控制全局(除了更进一步的封装)。

2.   有时可能不够灵活。

2.   继承验证基类,重写验证方法

1.   功能强大,简单易用,可自定义任意的验证规则。

2.   具有全局性,可提高一定的开发效率。

1.   性能方面可能有所降低,因为会不停的反射成员属性,有时还有可能递归调用(这是无法避免的)。

3.   共同点:

1)   都是基于模型验证,且都采用了 System.Web.Http.Filters.ActionFilterAttribute 动作过滤器。

2)   非模型参数时,两者都不能实现验证。

Ø  提示:如果有同学有其他的验证方案,或更高见解,欢迎随时讨论~。