私は大きなモデルを持っています(大きなとは、モデルクラスに多くのフィールド/プロパティが含まれ、それぞれに少なくとも1つの検証属性(Required
、MaxLength
、MinLength
など)があることを意味します))。ユーザーがモデルにデータを入力するための多くの入力を含む1つのビューを作成する代わりに、ユーザーがモデルフィールドの一部を入力して次のステップに進む、いくつかのビューを作成したいと思います(ある種の 「ウィザード」)。ステップ間でリダイレクトしている間、いっぱいになっていないモデルオブジェクトをSession
に保存します。以下のようなもの:
モデル:
public class ModelClass
{
[MaxLength(100)] ...
public string Prop1{get;set;}
[MaxLength(100)] ...
public string Prop2{get;set;}
...
[Required][MaxLength(100)] ...
public string Prop20{get;set;}
}
コントローラ:
[HttpPost]
public ActionResult Step1(ModelClass postedModel)
{
// user posts only for example Prop1 and Prop2
// so while submit I have completly emty model object
// but with filled Prop1 and Prop2
// I pass those two values to Session["model"]
var originalModel = Session["model"] as ModelClass ?? new ModelClass();
originalModel.Prop1 = postedModel.Prop1;
originalModel.Prop2 = postedModel.Prop2;
Session["model"] = originalModel;
// and return next step view
return View("Step2");
}
[HttpPost]
public ActionResult Step2(ModelClass postedModel)
{
// Analogically the same
// I have posted only Prop3 and Prop4
var originalModel = Session["model"] as ModelClass;
if (originalModel!=null)
{
originalModel.Prop3 = postedModel.Prop3;
originalModel.Prop4 = postedModel.Prop4;
Session["model"] = originalModel;
// return next step view
return View("Step3");
}
return View("SomeErrorViewIfSessionBrokesSomeHow")
}
Step1
ビューにはProp1
とProp2
の入力のみが含まれ、Step2ビューにはProp3
とProp4
の入力が含まれます。
しかしここにIS THE THING
たとえば、ユーザーがオンの場合、ステップ1で、Prop1に100文字を超える値を入力すると、クライアント側の検証が正常に機能します。しかし、もちろん、私はこの値を検証し、コントローラーのサーバー側で検証する必要があります。フルモデルがあれば、次のようにします。
if(!ModelState.IsValid) return View("the same view with the same model object");
そのため、ユーザーはフォームにもう一度入力して修正する必要があります。 [〜#〜] but [〜#〜]ステップ1で、ユーザーは20の2つのプロパティのみを入力したので、それらを検証する必要があります。モデルの状態が無効になるため、ModelState.IsValid
を使用できません。ご覧のとおり、Prop20
は[Required]
属性でマークされていますが、ユーザーがProp1
とProp2
を送信すると、Prop20
はnullになり、それがModelState
の理由です。無効です。もちろん、ユーザーがステップ2に進み、すべてのステップを入力し、最後のステップでのみモデルの状態を検証できるようにすることはできますが、ステップ1を間違って入力した場合、ユーザーがステップ2に進むことを許可したくありません。そして、私はコントローラーでこの検証が必要です。 質問は次のとおりです:モデルの一部のみを検証するにはどうすればよいですか?モデルのプロパティの一部のみがそれらに一致することを確認するにはどうすればよいですか?検証属性?
1つの可能な解決策:
ModelState.IsValidField(string key);を使用します。
if (ModelState.IsValidField("Name") && ModelState.IsValidField("Address"))
{ ... }
その後、すべてが完了したら、次を使用します。
if(ModelState.IsValid) { .. }
最もエレガントな方法は、次のようにすることだと思います。
List<string> PropertyNames = new List<string>()
{
"Prop1",
"Prop2"
};
if (PropertyNames.Any(p => !ModelState.IsValidField(p)))
{
// Error
}
else
{
// Everything is okay
}
または:
List<string> PropertyNames = new List<string>()
{
"Prop1",
"Prop2"
};
if (PropertyNames.All(p => ModelState.IsValidField(p)))
{
// Everything is okay
}
else
{
// Error
}
これに対する既存の答えに追加するだけです。プロパティ名をハードコーディングするのではなく、次の行に沿って残りの検証属性とともに追加する属性を使用します。
_public class ValidationStageAttribute : Attribute
{
public int StageNumber { get; private set; }
public ValidationStageAttribute(int stageNumber)
{
StageNumber = stageNumber;
}
}
_
モデル自体の知識がなくてもプロパティ名を取得できるようになったので、部分的な検証をメソッドに取り込むことができます(これを頻繁に使用する場合は、ベースコントローラーが適しています)。
_protected bool ValidateStage(object viewModel, int stageToValidate)
{
var propertiesForStage = viewModel.GetType()
.GetProperties()
.Where(prop => prop.GetCustomAttributes(false).OfType<ValidationStageAttribute>().Any(attr => attr.StageNumber == stageToValidate))
.Select(prop => prop.Name);
return propertiesForStage.All(p => ModelState.IsValidField(p));
}
_
これで、ポストアクションで行う必要があるのは、ValidateStage(viewModel, 1)
を呼び出すことだけです。
MVCコアでは、これは次と同等になります。
if (ModelState.GetFieldValidationState("Name") == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
{
// do something
}
ただし、この場合は単純に別のビューモデルを作成するをお勧めします。
部分ビューモデルは、より大きなビューモデルに継承される可能性があるため、コード(DRYプリンシパル)で繰り返す必要はありません。
プロパティ名をハードコーディングしないことをお勧めします。