4

私はまだ ASP.NET と MVC にかなり慣れていないため、何日にもわたるグーグル検索と実験にもかかわらず、この問題を解決する最善の方法について空白を描いています。

EmailAddressAttribute と同様に機能するように、BirthdayAttribute を作成しました。birthday 属性は、3 つのドロップダウン リストを持つエディター テンプレートを使用して誕生日 DateTime がレンダリングされるように UI ヒントを設定します。この属性を使用して、年のドロップダウンに表示する年数を示す追加のメタ データを設定することもできます。

jQuery の日付ピッカーを使用できることはわかっていますが、誕生日の場合、3 つのドロップダウンの方がはるかに使いやすいことがわかりました。

@model DateTime
@using System;
@using System.Web.Mvc;
@{
    UInt16 numberOfVisibleYears = 100;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
    {
        numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
    }
    var now = DateTime.Now;
    var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
    var months = Enumerable.Range(1, 12).Select(x => new SelectListItem{ Text = new DateTime( now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
    var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
}

@Html.DropDownList("Year", years, "<Year>") /
@Html.DropDownList("Month", months, "<Month>") /
@Html.DropDownList("Day", days, "<Day>")

後で日付を再構築するための ModelBinder もあります。簡潔にするためにヘルパー関数の内容を削除しましたが、ここまではすべてうまく機能しています。通常の有効な日付は、メンバーの作成または編集に問題なく機能します。

public class DateSelector_DropdownListBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
        if (bindingContext == null)
            throw new ArgumentNullException("bindingContext");

        if (IsDropdownListBound(bindingContext))
        {
            int year    = GetData(bindingContext, "Year");
            int month   = GetData(bindingContext, "Month");
            int day     = GetData(bindingContext, "Day");

            DateTime result;
            if (!DateTime.TryParse(string.Format("{0}/{1}/{2}", year, month, day), out result))
            {
                //TODO: SOMETHING MORE USEFUL???
                bindingContext.ModelState.AddModelError("", string.Format("Not a valid date."));
            }

            return result;
        }
        else
        {
            return base.BindModel(controllerContext, bindingContext);
        }

    }

    private int GetData(ModelBindingContext bindingContext, string propertyName)
    {
        // parse the int using the correct value provider
    }

    private bool IsDropdownListBound(ModelBindingContext bindingContext)
    {
        //check model meta data UI hint for above editor template
    }
}

今見てみると、おそらくnull可能なDateTimeを使用しているはずですが、それはここにもありません。

私が抱えている問題は、2 月 30 日や 9 月 31 日などの無効な日付の非常に基本的な検証にあります。検証自体はうまく機能しますが、無効な日付は保存されず、フォームがリロードされたときに保持されません。

私が望むのは、ドロップダウンをデフォルト値にリセットするのではなく、2 月 30 日の無効な日付を記憶し、検証メッセージとともに再表示することです。電子メール アドレス (EmailAddressAttribute で装飾されている) などのその他のフィールドは、無効なエントリを問題なく保持します。

現時点では、サーバー側の検証を機能させようとしています。正直なところ、クライアント側の検証についてはまだ考え始めていません。

この問題を解決するために javascript と ajax でできることがたくさんあることはわかっていますが、代わりに適切なサーバー側の検証を行うことをお勧めします。

4

1 に答える 1

2

ようやく問題を解決できたので、解決策を共有したいと思いました。

免責事項: 私は以前は .NET 2.0 に長けていましたが、C#、ASP.NET、MVC、および Entity Framework の最新バージョンにスキルを更新しているだけです。以下で行ったことを行うためのより良い方法がある場合は、いつでもフィードバックをお待ちしております.

TODO:

  • 2 月 30 日などの無効な日付に対するクライアント側の検証を実装します。[Required] 属性のクライアント側の検証は既に組み込まれています。

  • カルチャのサポートを追加して、日付が目的の形式で表示されるようにします

私が抱えていた問題は、DateTime が 2 月 30 日などの無効な日付で構築されることを許可しないことであることに気付いたときに、解決策が思い浮かびました。単に例外をスローします。日付が構築されない場合、無効なデータをバインダーを介して ViewModel に戻す方法がわかりませんでした。

この問題を解決するには、ビュー モデルの DateTime を廃止し、独自のカスタム Date クラスに置き換える必要がありました。以下のソリューションは、Javascript が無効になっている場合に、完全に機能するサーバー側の検証を提供します。検証エラーが発生した場合、検証メッセージが表示された後も無効な選択が保持されるため、ユーザーは間違いを簡単に修正できます。

このビューっぽい Date クラスを日付モデルの DateTime にマップするのは簡単です。

日付.cs

public class Date
{
    public Date() : this( System.DateTime.MinValue ) {}
    public Date(DateTime date)
    {
        Year = date.Year;
        Month = date.Month;
        Day = date.Day;
    }

    [Required]
    public int Year  { get; set; }

    [Required, Range(1, 12)]
    public int Month { get; set; }

    [Required, Range(1, 31)]
    public int Day   { get; set; }

    public DateTime? DateTime
    {
        get
        {
            DateTime date;
            if (!System.DateTime.TryParseExact(string.Format("{0}/{1}/{2}", Year, Month, Day), "yyyy/M/d", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                return null;
            else
                return date;
        }
    }
}

これは、DateTime から作成できる基本的な日付クラスです。このクラスには、Year、Month、および Day のプロパティと、有効な日付があると仮定して DateTime クラスを取得しようとする DateTime ゲッターがあります。それ以外の場合は null を返します。

組み込みの DefaultModelBinder がフォームをこの Date オブジェクトにマッピングし直すと、必須および範囲の検証が処理されます。ただし、2 月 30 日などの無効な日付が許可されないようにするために、新しい ValidationAtribute が必要になります。

DateValidationAttribute.cs

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class DateValidationAttribute : ValidationAttribute
{
    public DateValidationAttribute(string classKey, string resourceKey) :
        base(HttpContext.GetGlobalResourceObject(classKey, resourceKey).ToString()) { }

    public override bool IsValid(object value)
    {
        bool result = false;
        if (value == null)
            throw new ArgumentNullException("value");

        Date toValidate = value as Date;

        if (toValidate == null)
            throw new ArgumentException("value is an invalid or is an unexpected type");

        //DateTime returns null when date cannot be constructed
        if (toValidate.DateTime != null)
        {
            result = (toValidate.DateTime != DateTime.MinValue) && (toValidate.DateTime != DateTime.MaxValue);
        }

        return result;
    }
}

これは、Date フィールドとプロパティに設定できる ValidationAttribute です。リソース ファイル クラスとリソース キーを渡すと、"App_GlobalResources" フォルダー内の対応するリソース ファイルでエラー メッセージが検索されます。

IsValid メソッド内で、Date を検証していることを確認したら、DateTime プロパティをチェックして、それが有効であることを確認するために null でないかどうかを確認します。適切な測定のために、DateTime.MinValue と MaxValue をチェックします。

それで、それは本当にそれについてです。この Date クラスを使用して、カスタム ModelBinder を完全になくすことができました。このソリューションは、DefaultModelBinder に完全に依存しているため、すべての検証がすぐに機能します。どうやら新しい DateValidationAttribute もチェックしているようで、とても興奮していました。カスタムバインダーでバリデーターをいじる必要があるかもしれないとずっと思っていました。これでだいぶすっきりした感じ。

私が使用している部分ビューの完全なコードを次に示します。

DateSelector_DropdownList.cshtml

@model Date
@{
    UInt16 numberOfVisibleYears = 100;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
    {
        numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
    }
    var now = DateTime.Now;
    var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
    var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Text = new DateTime(now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
    var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString(), Text = x.ToString() });
}

@Html.DropDownList("Year", years, "<Year>") /
@Html.DropDownList("Month", months, "<Month>") /
@Html.DropDownList("Day", days, "<Day>")

また、テンプレート ヒントと表示する年数を設定するために使用する属性も含めます。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class DateSelector_DropdownListAttribute : DataTypeAttribute, IMetadataAware
{
    public DateSelector_DropdownListAttribute() : base(DataType.Date) { }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues.Add("NumberOfVisibleYears", NumberOfVisibleYears);
        metadata.TemplateHint = TemplateHint;
    }

    public string TemplateHint { get; set; }
    public int NumberOfVisibleYears { get; set; }
}

ソリューションは、予想よりもずっときれいになったと思います。それは私が望んでいた正確な方法で私のすべての問題を解決します. どうにかして DateTime を保持できたらいいのにと思いますが、サーバー側のコードのみを使用して無効な選択を維持する方法を理解できる唯一の方法です。

改善点はありますか?

于 2013-04-19T02:26:38.600 に答える