5

나는 ASP.NET과 MVC에 상당히 익숙하며 인터넷 검색 및 실험에도 불구하고이 문제를 해결하는 가장 좋은 방법은 비어 있습니다. .ASP.NET MVC - 3 개의 드롭 다운 목록이있는 유효하지 않은 DateTime 선택 유지

EmailAddressAttribute와 비슷한 방식으로 작업하려는 BirthdayAttribute를 작성했습니다. birthday 속성은 UI 힌트를 설정하여 생일 DateTime이 3 개의 드롭 다운 목록이있는 편집기 템플릿을 사용하여 렌더링되도록합니다. 이 속성은 연도 드롭 다운에 몇 년 동안 표시해야 하는지를 알려주는 몇 가지 추가 메타 데이터를 설정하는 데 사용할 수도 있습니다.

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 
    } 
} 

이제 살펴볼 것이므로 nullable DateTime을 사용해야 할 것입니다.하지만 여기에는 물론 여기에도 없습니다.

내가 겪고있는 문제는 2 월 30 일 또는 9 월 31 일과 같은 유효하지 않은 날짜의 매우 기본적인 유효성 검사와 관련되어 있습니다. 유효성 검사 자체는 훌륭하게 작동하지만 유효하지 않은 날짜는 양식이 다시로드 될 때 저장되고 지속되지 않습니다.

내가 원하는 것은 2 월 30 일의 잘못된 날짜를 기억하고 드롭 다운을 기본값으로 다시 설정하는 대신 유효성 검사 메시지로 다시 표시하는 것입니다. 전자 메일 주소 (EmailAddressAttribute로 장식 됨)와 같은 다른 필드는 유효하지 않은 항목을 그대로 유지합니다.

현재 서버 측 유효성 검사를 받으려고하고 있습니다. 솔직히 클라이언트 측 유효성 검사에 대해서 아직 생각조차하지 못했습니다.

나는이 문제를 논점으로 만들기 위해 자바 스크립트와 아약스로 할 수있는 일이 많이 있다는 것을 알고있다. 그러나 나는 여전히 올바른 서버 측 유효성 검사를 사용하여 다시 실패 할 것이다.

+0

변경 이벤트를 드롭 다운에 추가하고 숨김 필드에 전체 날짜를 생성하고 유효성을 검사하면 숨겨진 텍스트 상자 값의 드롭 다운에서 값을 다시 선택할 수 있습니다. 클라이언트 측 코드 – davethecoder

답변

2

마침내 문제를 해결할 수있어서 솔루션을 공유하려고했습니다.

면책 조항 : 이전에는 .NET 2.0을 사용했지만 이전 버전에서는 C#, ASP.NET, MVC 및 Entity Framework의 최신 버전으로 업데이트했습니다. 내가 한 일을 더 잘 수행 할 수있는 방법이 있다면 아래 의견을 항상 전합니다.

TODO :

  • 는 월 30 잘못된 날짜에 대한 클라이언트 측 유효성 검사를 구현합니다. [필수] 속성이 이미 구축에 대한 클라이언트 측 유효성 검사. 날짜가

이 솔루션은 나에게 와서 원하는 형식으로 표시되도록 내가 깨달았을 때

  • 는 문화에 대한 지원을 추가하는 문제 I DateTime이 2 월 30 일과 같은 잘못된 날짜로 생성되는 것을 허용하지 않는다는 것입니다. 그것은 단순히 예외를 throw합니다. 내 데이트가 작성되지 않으면 바인더를 통해 잘못된 데이터를 다시 ViewModel로 전달할 수있는 방법이 없다는 것을 알고있었습니다.

    이 문제를 해결하기 위해 필자는 내 뷰 모델에서 DateTime을 제거하고 내 자신의 사용자 지정 Date 클래스로 바꾸어야했습니다. 아래의 솔루션은 Javascript가 비활성화 된 경우 서버 측 유효성 검사를 완벽하게 수행합니다. 유효성 검사 오류가 발생하는 경우 유효성 검사 메시지가 표시된 후에 유효하지 않은 선택 사항이 유지되어 사용자가 실수를 쉽게 수정할 수 있습니다.

    이 view-ish Date 클래스를 날짜 모델의 DateTime에 매핑하기는 쉽습니다.

    Date.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 클래스를 검색 할 수있는 DateTime 게터가 있습니다. 그렇지 않은 경우는 null을 리턴합니다.

    기본 제공되는 DefaultModelBinder가 양식을이 Date 개체에 다시 매핑하면 Required 및 Range 유효성 검사가 처리됩니다. 그러나 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; 
        } 
    } 
    

    이것은 당신이 당신의 날짜 필드와 속성에 넣을 수 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을 유지할 수 있었으면 좋겠지 만 서버 측 코드 만 사용하여 유효하지 않은 선택을 유지하는 방법을 알아낼 수있는 유일한 방법입니다.

    개선 할 점이 있습니까?