ModelStateについて
MVCでは、ModelStateを使用してフォームの検証を行うことが一般的です。しかし、Web APIではビューが存在しないため、検証エラーをどのように処理するかが課題となります。ここでは、ModelStateを使用してAPIのパラメータを検証し、エラーメッセージをクライアントに返す方法を紹介します。
ModelStateの構造
ModelStateはDictionary型で、キーはモデルのプロパティ名、値はそのプロパティに関連するエラー情報を持っています。具体的には、ModelStateDictionaryとModelStateクラスが関与しています。
public class ModelState
{
public ModelErrorCollection Errors { get; }
public ValueProviderResult Value { get; set; }
}
public class ModelError
{
public string ErrorMessage { get; private set; }
public Exception Exception { get; private set; }
}
コード実装
ログインシナリオを例にとって、ログイン用のモデルを作成し、検証ルールを定義します。
public class LoginModel
{
[Required(ErrorMessage = "電話番号を入力してください")]
[RegularExpression(@"^1[3|4|5|7|8][0-9]\d{8}$", ErrorMessage = "電話番号の形式が正しくありません")]
public string PhoneNumber { get; set; }
[Required(ErrorMessage = "認証キーが無効です")]
public string AuthKey { get; set; }
[Required(ErrorMessage = "認証コードを入力してください")]
public string AuthCode { get; set; }
}
次に、APIアクション内でModelStateの検証を行います。
if (!ModelState.IsValid)
{
string error = string.Empty;
foreach (var key in ModelState.Keys)
{
var state = ModelState[key];
if (state.Errors.Any())
{
error = state.Errors.First().ErrorMessage;
break;
}
}
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(JsonConvert.SerializeObject(new { Status = "Failed", Message = error }), Encoding.UTF8, "application/json")
};
}
フィルターを使用したコードの再利用
各APIアクションで同じコードを書くのは非効率的です。そこで、MVCのFilter機能を利用します。ActionFilterAttributeを継承したカスタム属性を作成し、OnActionExecutingメソッドをオーバーライドします。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class ModelValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
string error = string.Empty;
foreach (var key in modelState.Keys)
{
var state = modelState[key];
if (state.Errors.Any())
{
error = state.Errors.First().ErrorMessage;
break;
}
}
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(JsonConvert.SerializeObject(new { Status = "Failed", Message = error }), Encoding.UTF8, "application/json")
};
actionContext.Response = response;
}
}
}
この属性をAPIコントローラーまたは個別のアクションに適用することで、モデルの検証とエラーレスポンスの生成を自動化できます。