私は大きなMVC3Webアプリケーションに取り組んでおり、ModelState.IsValid
メソッドに関して煩わしい思いをしています。
ModelStateは、投稿されているデータを検証するために、ほぼすべてのコントローラーで使用されています。ビューはすべて、異なるクラスを含むViewModelに基づいており、これらのクラスには明らかに[Required]
としてマークできるプロパティが含まれています。
私が抱えている問題は、必要なプロパティが必要ない場合があり、ModelState.Remove
メソッドを使用してModelState.IsValid
をtrueにする必要があることです。
私の質問はModelState.Remove
を使用することですが、これは物事を行う正しい方法ですか、それともより効率的なアプローチがありますか。
2つの異なるコンテキストで[Required]
プロパティを使用して同じビューモデルを使用している場合(1つはプロパティが必要な場合、もう1つは必要ない場合)、必要に応じてModelState
を手動で変更する必要があります。やっています。
別の方法は、別のビューモデルを使用することです。おそらく、問題の必須プロパティを除くすべてのプロパティを持つ基本クラスがあります。次に、そこから2つのビューモデルを導出します。1つは必要なプロパティを持ち、もう1つは必要でないプロパティを持ちます(重複です、私は知っています)。それらを完全に分離して、継承を使用しないことを決定できます。
これが私の解決策です-MVCHTMLヘルパーをモデルにしたModelState
のRemoveFor()
拡張メソッド:
public static void RemoveFor<TModel>(this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression)
{
string expressionText = ExpressionHelper.GetExpressionText(expression);
foreach (var ms in modelState.ToArray())
{
if (ms.Key.StartsWith(expressionText + ".") || ms.Key == expressionText)
{
modelState.Remove(ms);
}
}
}
使用方法は次のとおりです。
if (model.CheckoutModel.ShipToBillingAddress == true)
{
// REUSE BILLING ADDRESS FOR SHIPPING ADDRESS
ShoppingCart.ShippingAddress = ShoppingCart.BillingAddress;
// REMOVE MODELSTATE ERRORS FOR SHIPPING ADDRESS
ModelState.RemoveFor<SinglePageStoreModel>(x => model.CheckoutModel.ShippingAddress);
}
ですから、あなたの質問に答えて、これが正しい方法であるユースケースは間違いなくあると思います。このような強く型付けされたヘルパーは、見やすくなり、多くのことを心配している場合は正当化するのが簡単になります。マジックストリングの。
基本的に、あなたの問題は、クラスが[必須]で装飾されている一方で、それが常に正しいとは限らないということです。 trueでないコンテキストで操作している場合は、プロパティを[必須]として定義していないクラスを実際に使用する必要があります。
特定の使用法に対して正しく定義されているViewModelを実際に使用する必要があります。これは、一部のクラスを複製することを意味する場合があります。 ViewModelはUIの実装に関連付けられており、ドメインモデルのクラスを使用する場合がありますが、常に正しいとは限りません。
それができない場合、オプションはModelState.IsValidを使用しないか、ModelState.Removeを引き続き使用することです。
しかし論理的には、ViewModelが「検証可能」であり、特定の検証エラーを無視する必要がないことは理にかなっています。
私は完全にスティーブ・モーガン氏と一緒です
したがって、ViewModelが常にRequired
であるためにいくつかのプロパティを必要としない場合は、それを必須として装飾するべきではありません。
なぜこの問題が必要なのかわかりませんが、PropertyOne
に値がある場合は、Required
をPropertyTwo
にする必要がある場合があると思います。この場合、これら2つのプロパティを確認するためにCustomValidationAttribute
を作成する必要がある場合があります。
私はこのようなものを使用しています:
_[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class PropertyNeededAttribute : ValidationAttribute
{
private const string defaultErrorMessage = "'{0}' needs '{1}' to be valid.";
public PropertyNeededAttribute(string originalProperty, string neededProperty)
: base(defaultErrorMessage)
{
NeededProperty = neededProperty;
OriginalProperty = originalProperty;
}
public string NeededProperty { get; private set; }
public string OriginalProperty { get; private set; }
public override object TypeId
{
get { return new object(); }
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, NeededProperty);
}
public override bool IsValid(object value)
{
object neededValue = Statics.GetPropertyValue(value, NeededProperty);
object originalValue = Statics.GetPropertyValue(value, OriginalProperty);
if (originalValue != null && neededValue == null)
return false;
return true;
}
}
_
注:Statics.GetPropertyValue(...)
は、プロパティから値を取得して比較する以外に何もしません。
これがお役に立てば幸いです:)
プロパティが常に必要なわけではない場合は、[Required]
で装飾しないでください。
検証を行うための良い代替手段は、インターフェースを実装することです IValidatableObject 。
たとえば、国がUnited States
の場合にのみ、フィールドState
を必須にしたいとします。あなたはそのようにそれを行うことができます:
public class AddressModel : IValidatableObject
{
[Required]
public string Country { get; set; }
public string State { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(Country == "United States" && String.IsNullOrEmpty(State))
{
yield return new ValidationResult("State is required for United States", new [] { nameof(State) });
}
}
}
注:この種の検証は、サーバー側でのみ機能します。
他の選択肢?
他の回答で述べたように、ビューと検証が大きく異なる場合は、2つ以上のモデルを作成することをお勧めします。