私はかつて、カスタムModelBinderを介してこの状況を処理し、base.BindModel呼び出しの周りにtry/catchをスローしました。それは醜いですが、それは仕事を成し遂げます。
繰り返しますが、それは醜いです。
次に例を示します。
public class FooModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Foo model;
try
{
model = (Foo)base.BindModel(controllerContext, bindingContext);
}
catch (HttpRequestValidationException)
{
// handle here
}
}
}
さて、ModelStateにエラーを入力するための勇敢な努力の中で、状態を維持するために最善を尽くすヘルパークラスがあります。その使用(および実装)には多くの要望が残されているため(多くの配管、マジックストリングの使用、タイプ固有、例外メッセージテキストの正規表現など)、提案を歓迎します。これは最も醜い部分、imoです。
使用法:
// from above code snippet
catch (HttpRequestValidationException)
{
// handle any potentially dangerous form values here. Don't want an exception bubbling up to the user
// so handle the HttpRequestValidationException by hand here
// manually populate the model here so that the original values are presented back to the user
model = new Foo()
{
Bar = HandleHttpRequestValidationExceptionHelper.TryAssignment(bindingContext.ModelState, () => bindingContext.ValueProvider.GetValue("Bar").AttemptedValue),
Baz = HandleHttpRequestValidationExceptionHelper.TryAssignment(bindingContext.ModelState, () => bindingContext.ValueProvider.GetValue("Baz").AttemptedValue)
};
}
return model;
ヘルパーは、ユーザーに関連するエラー情報を見つけるために最善を尽くしますが、それは本当にくだらないです。(テーマに気づきましたか?)
実装:
public static class HandleHttpRequestValidationExceptionHelper
{
/// <summary>
/// Use TryAssignment in anticipation of a HttpRequestValidationException; it's used to help return error information to the user
/// </summary>
/// <param name="modelStateDictionary">The ModelStateDictionary to add the errors to</param>
/// <param name="action">The attempted value to assign</param>
/// <returns>Either the proper value or the errored value read from the HttpRequestValidationException Message property</returns>
public static string TryAssignment(ModelStateDictionary modelStateDictionary, Func<string> action)
{
try
{
return action();
}
catch (HttpRequestValidationException ex)
{
// in effort to better inform the user, try to fish out the offending form field
var parenthesesMatch = Regex.Match(ex.Message, @"\(([^)]*)\)");
if (parenthesesMatch.Success)
{
var badFormInput = parenthesesMatch.Groups[1].Value.Split('=');
modelStateDictionary.AddModelError(badFormInput[0], badFormInput[1] + " is not valid.");
return badFormInput[1].TrimStart('"').TrimEnd('"');
}
else
{
// if attempt to find the offending field fails, just give a general error
modelStateDictionary.AddModelError("", "Please enter valid information.");
return string.Empty;
}
}
}
/// <summary>
/// Use TryAssignment in anticipation of a HttpRequestValidationException; it's used to help return error information to the user
/// </summary>
/// <typeparam name="T">Type of the value</typeparam>
/// <param name="modelStateDictionary">The ModelStateDictionary to add the errors to</param>
/// <param name="action">The attempted value to assign</param>
/// <returns>Either the proper value or default(T)</returns>
public static T TryAssignment<T>(ModelStateDictionary modelState, Func<T> action)
{
try
{
return action();
}
catch (HttpRequestValidationException ex)
{
// in effort to better inform the user, try to fish out the offending form field
var parenthesesMatch = Regex.Match(ex.Message, @"\(([^)]*)\)");
if (parenthesesMatch.Success)
{
var badFormInput = parenthesesMatch.Groups[1].Value.Split('=');
modelState.AddModelError(badFormInput[0], badFormInput[1] + " is not valid.");
// can't really cast a string to an unknown type T. safer to just return default(T)
}
else
{
// if attempt to find the offending field fails, just give a general error
modelState.AddModelError("", "Please enter valid information.");
}
return default(T);
}
}
}
基本的に、例外をキャッチしたら、モデルを手動で再バインドして、HttpRequestValidationException
各プロパティの潜在的なエラーをキャッチできるようにします。キャッチされた場合は、それに応じてModelStateDictionaryに、取得できる限り具体的なメッセージを入力します。
フレームワークによって、1)この例外をキャッチし、2)ベッド全体を壊すのではなく、適切に処理できるようになったことを本当に望んでいます。