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 に答える