[WEB API]使用ActionFilter來統一處理ModelState的驗證

[WEB API]使用ActionFilter來統一處理ModelState的驗證

前言

當我們使用MVC或Web api的時候,其實我們常常都會在controller寫判斷驗證我們Model所定義的驗證,但是其實我們可以使用更好的方式來包裝,在此我們使用ActionFilter的方式,其實這個就是微軟幫我們的WebApi包裝好的AOP概念,接下來就說明一下實際情況

情境模擬

正常來說我們一旦使用了Web Api,我們的驗證就都會定義在Model裡面,這樣子如果我們只要有任何驗證相關的,只要去Model裡面看,示例如下

EmployeeModel

public class EmployeeModel                          
{                                                   
    public int Id { get; set; }                     
                                                    
    [Required]                                      
    [MaxLength(5,ErrorMessage ="{0}不可超過5個字")]  
    public string Name { get; set; }                
                                                    
    [Required]                                      
    public string Address { get; set; }             
}                                                   

Controller

public IHttpActionResult Post(EmployeeModel employee) 
{                                                     
    if (!ModelState.IsValid)                          
    {                                                 
        return BadRequest(ModelState);                
    }                                                 
    return Ok();                                      
}                                                     

結果

但是正常來說我們很多Controller的Action都需要驗證Model,那如果我們能把這段重覆的程式碼,放在一個地方,以後有什麼相關的需要修改,就盡量改這個地方就好了,接下來就新增一支名為ValidateModelAttribute.cs

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.SelectMany(x => x.Value.Errors).Any(x => !string.IsNullOrEmpty(x.ErrorMessage)))
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }

            base.OnActionExecuting(actionContext);
        } 
    }

做完了之後我們可以取決於要把這個attribute掛在哪邊,可以掛在method或class的上面

掛在Method上面

    public class ValuesController : ApiController
    {
        [ValidateModel]
        public IHttpActionResult Post(EmployeeModel employee)
        {         
            return Ok();
        }
    }

掛在Class上面

    [ValidateModel]
    public class ValuesController : ApiController
    {       
        public IHttpActionResult Post(EmployeeModel employee)
        {         
            return Ok();
        }
    }

最方便的方式就是直接掛全局的,這時候我們可以在Global.asax動點手腳,在適當的生命週期插進去我們想要處理的邏輯

Global.asax

 public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            RegisterWebApiFilters(GlobalConfiguration.Configuration.Filters); //我們新加入的Filter
        }

        public static void RegisterWebApiFilters(System.Web.Http.Filters.HttpFilterCollection filters)
        {
            filters.Add(new ValidateModelAttribute()); //加進剛剛寫的那支類別
        }
    }

至此我們就註冊了全局都要去使用這個ModelState.IsValid,就不用在類別或方法裡面在掛attribute了,不過如果我們想要忽略掉某個action不要去驗證Model的狀態呢?我們再新增一個IgnoreValidateModelAttribute的類別來濾掉吧

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ActionDescriptor.GetCustomAttributes<IgnoreValidateModelAttribute>(false).Any()) //有掛上這個Attribute的,直接就return掉
            {
                return;
            }

            if (actionContext.ModelState.SelectMany(x => x.Value.Errors).Any(x => !string.IsNullOrEmpty(x.ErrorMessage)))
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }

            base.OnActionExecuting(actionContext);
        } 
    }

    public class IgnoreValidateModelAttribute : Attribute
    {
    }

接著再來剛剛那個類別,掛上Ignore來試試看,是不是就不會做驗證了

    public class ValuesController : ApiController            
    {                                                        
        [IgnoreValidateModel]                                
        public IHttpActionResult Post(EmployeeModel employee)
        {                                                    
            return Ok("忽略掉這個方法");                      
        }                                                    
    }                                                        

測試結果

結論

簡單介紹一下ActionFilter的用法,其實還有很多情境可以應用,請自行發揮囉。