3

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

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

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

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

4

1 回答 1

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);
}
于 2010-06-02T06:59:26.573 回答