【问题标题】:How to add request validation errors to ModelStateDictionary in ASP.NET MVC?如何在 ASP.NET MVC 中向 ModelStateDictionary 添加请求验证错误?
【发布时间】:2011-02-21 08:06:16
【问题描述】:

调查我正在使用 ASP.NET MVC 2 构建的系统的安全性使我发现了 ASP.NET 的请求验证功能 - 确实是一个非常简洁的功能。但很明显,当用户输入带有 HTML 的数据时,我不只是想向用户呈现死机黄屏,所以我要寻找更好的解决方案。

我的想法是在调用操作之前找到所有具有无效数据的字段并将它们添加到ModelStateDictionary,以便它们自动作为错误消息显示在 UI 中。在谷歌上搜索了一下之后,似乎没有人实现过这个,在这之前我觉得很费解,因为它看起来很明显。这里有没有人有关于如何做到这一点的建议?我自己的想法是为控制器提供一个自定义的ControllerActionInvoker,如here 所述,它以某种方式检查并修改ModelStateDictionary,但我一直坚持如何做最后一点。

仅仅捕获HttpRequestValidationException 异常似乎不是一个有用的方法,因为它实际上并不包含我需要的所有信息。

我自己已经回答了这个问题,但我仍然很想知道任何更优雅/更强大的解决方案。

【问题讨论】:

    标签: asp.net-mvc request-validation


    【解决方案1】:

    看了一点 MVC 是如何进行模型绑定的,我自己想出了一个解决方案。我使用自定义实现扩展了Controller 类,该实现覆盖Execute 方法,如下所示:

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

    动作的核心发生在 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);
    }
    

    【讨论】:

      猜你喜欢
      • 2018-04-19
      • 2016-12-06
      • 2010-12-11
      • 2017-03-12
      • 2017-04-04
      • 2014-01-23
      • 1970-01-01
      • 1970-01-01
      • 2020-06-02
      相关资源
      最近更新 更多