5

Tôi vẫn còn khá mới với ASP.NET và MVC và mặc dù ngày googling và thử nghiệm, tôi đang vẽ một cách trống tốt nhất để giải quyết vấn đề này .ASP.NET MVC - Bảo toàn lựa chọn DateTime không hợp lệ với 3 danh sách thả xuống

Tôi đã viết một BirthdayAttribute mà tôi muốn làm việc tương tự như EmailAddressAttribute. Thuộc tính sinh nhật đặt gợi ý giao diện người dùng để DateTime sinh nhật sẽ được hiển thị bằng mẫu trình chỉnh sửa có 3 danh sách thả xuống. Thuộc tính này cũng có thể được sử dụng để thiết lập một số dữ liệu meta bổ sung cho biết danh sách thả xuống của năm sẽ hiển thị bao nhiêu năm.

Tôi biết tôi có thể sử dụng bộ chọn ngày của jQuery, nhưng trong trường hợp sinh nhật tôi thấy 3 trình đơn thả xuống dễ sử dụng hơn nhiều.

@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>") 

Tôi cũng có ModelBinder để xây dựng lại ngày của mình sau đó. Tôi đã loại bỏ nội dung của các hàm trợ giúp của tôi cho ngắn gọn, nhưng tất cả mọi thứ hoạt động tốt đến thời điểm này. Ngày bình thường, hợp lệ, hoạt động tốt để tạo hoặc chỉnh sửa thành viên của tôi.

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

Bây giờ tôi đang xem nó, có lẽ tôi nên sử dụng DateTime không có giá trị, nhưng đó không phải ở đây cũng không có ở đó.

Sự cố tôi gặp phải là xác thực rất cơ bản các ngày không hợp lệ như ngày 30 tháng 2 hoặc ngày 31 tháng 9. Việc xác nhận chính nó hoạt động tốt, nhưng ngày không hợp lệ không bao giờ được lưu và tồn tại khi biểu mẫu được tải lại.

Điều tôi muốn là nhớ ngày không hợp lệ của ngày 30 tháng 2 và hiển thị lại nó bằng thông báo xác thực thay vì đặt lại danh sách thả xuống về giá trị mặc định của họ. Các trường khác, như địa chỉ email (được trang trí với thuộc tính EmailAddressAttribute) bảo vệ các mục không hợp lệ tốt khỏi hộp.

Hiện tại, tôi chỉ đang cố gắng để xác thực phía máy chủ hoạt động. Thành thật mà nói, tôi thậm chí không bắt đầu suy nghĩ về việc xác nhận phía khách hàng được nêu ra.

Tôi biết có rất nhiều điều tôi có thể làm với javascript và ajax để làm cho vấn đề này trở thành điểm tranh luận, nhưng tôi vẫn muốn có xác thực hợp lệ phía máy chủ để quay trở lại.

+0

nếu bạn thêm sự kiện thay đổi vào menu thả xuống, bạn có thể tạo ngày đầy đủ trong trường bị ẩn và xác thực điều này, sau đó có thể chọn lại giá trị trong menu thả xuống từ giá trị hộp văn bản ẩn. mã phía máy khách – davethecoder

Trả lời

2

Cuối cùng tôi đã giải quyết được vấn đề của mình, vì vậy tôi muốn chia sẻ giải pháp của mình.

KHUYẾN CÁO: Mặc dù trước đây tôi đã trở nên tuyệt vời với .NET 2.0, tôi chỉ cập nhật các kỹ năng của mình lên các phiên bản mới nhất của C#, ASP.NET, MVC và Entity Framework. Nếu có những cách tốt hơn để làm bất cứ điều gì tôi đã làm dưới đây xin vui lòng tôi luôn luôn mở để phản hồi.

TODO:

  • Thực hiện xác nhận phía khách hàng cho những ngày không hợp lệ như ngày 30 tháng 2. Khách hàng xác nhận phía cho [Bắt buộc] thuộc tính đã được xây dựng trong.

  • Thêm hỗ trợ cho các nền văn hóa do đó ngày xuất hiện trong định dạng mong muốn

Các giải pháp đến với tôi khi tôi nhận ra rằng vấn đề tôi đã có được rằng DateTime sẽ không cho phép chính nó được xây dựng với một ngày không hợp lệ như ngày 30 tháng 2. Nó chỉ đơn giản là ném một ngoại lệ. Nếu ngày của tôi không được xây dựng, tôi biết không có cách nào để truyền dữ liệu không hợp lệ của mình trở lại thông qua trình kết nối tới ViewModel.

Để giải quyết vấn đề này, tôi phải loại bỏ DateTime trong mô hình khung nhìn của mình và thay thế bằng lớp Ngày tùy chỉnh của riêng tôi. Giải pháp bên dưới sẽ cung cấp chức năng xác thực phía máy chủ đầy đủ trong trường hợp Javascript bị tắt. Trong trường hợp lỗi xác thực, các lựa chọn không hợp lệ sẽ vẫn tồn tại sau khi thông báo xác thực được hiển thị cho phép người dùng dễ dàng sửa lỗi của họ.

Sẽ đủ dễ dàng để ánh xạ chế độ xem này-ish Lớp ngày tới DateTime trong mô hình ngày của bạn.

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

Đây chỉ là một lớp học ngày cơ bản mà bạn có thể xây dựng từ một DateTime. Lớp này có các thuộc tính cho Year, Month và Day cũng như getter DateTime có thể cố gắng truy xuất bạn một lớp DateTime giả sử bạn có một ngày hợp lệ. Nếu không thì nó trả về null.

Khi được xây dựng trong DefaultModelBinder ánh xạ biểu mẫu của bạn trở lại đối tượng Ngày tháng này, nó sẽ xử lý xác nhận Phạm vi bắt buộc và Phạm vi cho bạn. Tuy nhiên, chúng tôi sẽ cần một ValidationAtribute mới để đảm bảo rằng các ngày không hợp lệ như ngày 30 tháng 2 không được phép.

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

Đây là một ValidationAttribute mà bạn có thể đặt trên các lĩnh vực ngày và tài sản của bạn. Nếu bạn vượt qua trong lớp tệp tài nguyên và khóa tài nguyên, nó sẽ tìm kiếm tệp tài nguyên tương ứng trong thư mục "App_GlobalResources" của bạn cho thông báo lỗi.

Bên trong phương pháp IsValid, khi chúng tôi chắc chắn rằng chúng tôi đang xác thực ngày, chúng tôi kiểm tra thuộc tính DateTime của nó để xem nếu nó không phải là null để xác nhận rằng nó hợp lệ. Tôi ném vào một kiểm tra cho DateTime.MinValue và MaxValue cho biện pháp tốt.

Vì vậy, đó là về nó thực sự. Với lớp Date này, tôi đã xoay xở hoàn toàn với ModelBinder tùy chỉnh. Giải pháp này dựa hoàn toàn vào DefaultModelBinder, có nghĩa là tất cả các xác nhận hợp lệ đều hoạt động ngay trên hộp. Nó dường như thậm chí kiểm tra DateValidationAttribute mới của tôi, mà tôi đã rất vui mừng về. Tôi nhấn mạnh mãi mãi nghĩ rằng tôi có thể phải muck với validators trong một chất kết dính tùy chỉnh. Điều này cảm thấy sạch hơn rất nhiều.

Đây là mã hoàn chỉnh cho chế độ xem một phần tôi đang sử dụng.

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>") 

Tôi cũng sẽ bao gồm các thuộc tính tôi sử dụng thiết lập các mẫu gợi ý và số năm có thể nhìn thấy hiển thị.

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

Tôi nghĩ giải pháp này trở nên sạch hơn rất nhiều so với dự kiến. Nó giải quyết tất cả các vấn đề của tôi theo cách chính xác mà tôi đã hy vọng. Tôi ước rằng tôi bằng cách nào đó có thể giữ DateTime, nhưng đây là cách duy nhất tôi có thể tìm ra cách duy trì một lựa chọn không hợp lệ chỉ sử dụng mã phía máy chủ.

Bạn có thực hiện bất kỳ cải tiến nào không?

Các vấn đề liên quan