2010-05-19 36 views
3

調查我用ASP.NET MVC 2構建的系統的安全性,讓我發現了ASP.NET的請求驗證功能 - 的確是一個非常整潔的功能。但顯然,我不只是想在用HTML輸入數據時向用戶展示死亡黃頁,所以我出去尋找更好的解決方案。如何向ASP.NET MVC中的ModelStateDictionary添加請求驗證錯誤?

我的想法是在調用動作之前找到所有包含無效數據的字段並將它們添加到ModelStateDictionary,以便它們自動出現在用戶界面中作爲錯誤消息。谷歌搜索了一下後,似乎沒有人實現過,因爲它看起來很明顯,所以我覺得很困惑。有沒有人在這裏有如何做到這一點的建議?我自己的想法是提供一個自定義ControllerActionInvoker到控制器,如here所述,以某種方式檢查這個並修改ModelStateDictionary,但我堅持如何做到這一點。

只是捕獲HttpRequestValidationException異常似乎並不是一種有用的方法,因爲它實際上並不包含我需要的所有信息。

我已經自己回答了這個問題,但我仍然非常有興趣聽到任何更優雅/健壯的解決方案。

回答

1

看了一下MVC如何做模型綁定,我自己想出了一個解決方案。

public abstract class ExtendedController : Controller 
{  
    protected override void Execute(RequestContext requestContext) 
    { 
     ActionInvoker = new ExtendedActionInvoker(ModelState); 
     ValidateRequest = false; 
     base.Execute(requestContext); 
    } 
} 

對我來說,當發生請求驗證我已經添加到控制以下的web.config

<httpRuntime requestValidationMode="2.0"/> 

的我與覆蓋像Execute方法,這樣一個自定義實現擴展Controller類動作的肉發生在ControllerActionInvoker類的自定義執行中:

public class ExtendedActionInvoker : ControllerActionInvoker 
{ 
    private ModelStateDictionary _modelState; 
    private const string _requestValidationErrorKey = "RequestValidationError"; 

    public ExtendedActionInvoker(ModelStateDictionary modelState) 
    { 
     _modelState = modelState; 
    } 

    protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) 
    { 
     var action = base.FindAction(controllerContext, controllerDescriptor, actionName); 
     controllerContext.RequestContext.HttpContext.Request.ValidateInput(); 

     return action; 
    } 

    protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) 
    { 
     try 
     { 
      return base.GetParameterValue(controllerContext, parameterDescriptor); 
     } 
     catch (HttpRequestValidationException) 
     { 
      var fieldName = parameterDescriptor.ParameterName; 
      _modelState.AddModelError(fieldName, ModelRes.Shared.ValidationRequestErrorMessage); 
      _modelState.AddModelError(_requestValidationErrorKey, ModelRes.Shared.ValidationRequestErrorMessage); 

      var parameterType = parameterDescriptor.ParameterType; 

      if (parameterType.IsPrimitive || parameterType == typeof(string)) 
      { 
       return GetValueFromInput(parameterDescriptor.ParameterName, parameterType, controllerContext); 
      } 

      var complexActionParameter = Activator.CreateInstance(parameterType); 
      foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(complexActionParameter)) 
      { 
       object propertyValue = GetValueFromInput(descriptor.Name, descriptor.PropertyType, controllerContext); 
       if (propertyValue != null) 
       { 
        descriptor.SetValue(complexActionParameter, propertyValue); 
       } 
      } 
      return complexActionParameter; 
     } 
    } 

    private object GetValueFromInput(string parameterName, Type parameterType, ControllerContext controllerContext) 
    { 
     object propertyValue; 
     controllerContext.RouteData.Values.TryGetValue(parameterName, out propertyValue); 
     if (propertyValue == null) 
     { 
      propertyValue = controllerContext.HttpContext.Request.Params[parameterName]; 
     } 

     if (propertyValue == null) 
      return null; 
     else 
      return TypeDescriptor.GetConverter(parameterType).ConvertFrom(propertyValue); 
    } 
} 

這是什麼確實是在找到操作後執行請求驗證。如果請求無效,這將不會立即導致錯誤,但當調用GetParameterValue時,它將引發異常。爲了避免這種情況,我重寫了這個方法,並將基本調用包裝在try-catch中。如果發現異常,我基本上重新實現模型綁定(我對此代碼的質量沒有任何承諾),並向該值添加一個錯誤到ModelStateDictionary對象。

作爲獎勵,因爲我想以ajax方法的標準格式返回錯誤,我還添加了InvokeActionMethod的自定義實現。

protected override ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) 
{ 
    if (_modelState.ContainsKey(_requestValidationErrorKey)) 
    { 
     var errorResult = new ErrorResult(_modelState[_requestValidationErrorKey].Errors[0].ErrorMessage, _modelState); 

     var type = controllerContext.Controller.GetType(); 
     var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 
     if (methods.Where(m => m.Name == actionDescriptor.ActionName).First().ReturnType == typeof(JsonResult)) 
      return (controllerContext.Controller as ExtendedControllerBase).GetJson(errorResult); 
    } 

    return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters); 
} 
相關問題