Как добавить ошибки проверки запроса в ModelStateDictionary в ASP.NET MVC?
Изучение безопасности системы, которую я создаю с помощью ASP.NET MVC 2, привело меня к открытию функции проверки запросов ASP.NET - действительно очень полезной функции. Но, очевидно, я не просто хочу предоставить пользователям "Желтый экран смерти", когда они вводят данные с помощью HTML, поэтому я хочу найти лучшее решение.
Моя идея состоит в том, чтобы найти все поля, которые имеют недопустимые данные и добавить их в ModelStateDictionary
перед вызовом действия, чтобы они автоматически появлялись в пользовательском интерфейсе как сообщения об ошибках. Немного погуглив, кажется, что никто не реализовал это, прежде чем я нахожу загадочным, поскольку это кажется таким очевидным. У кого-нибудь здесь есть предложение, как это сделать? Моя собственная идея состоит в том, чтобы поставить на заказ ControllerActionInvoker
к контроллеру, как описано здесь, который каким-то образом проверяет это и изменяет ModelStateDictionary
но я застрял на том, как сделать это в последнюю очередь.
Просто ловить HttpRequestValidationException
Исключения не кажутся полезным подходом, поскольку на самом деле они не содержат всей необходимой мне информации.
Я сам ответил на вопрос, но мне все равно было бы интересно услышать о любых решениях, которые являются более элегантными / надежными.
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);
}