2010-05-19 62 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); 
}