5

Я до сих пор довольно новичок в ASP.NET и MVC, и, несмотря на дни поиска и экспериментирования, я рисую пробел, чтобы наилучшим образом решить эту проблему ,ASP.NET MVC - сохранение недопустимого выбора DateTime с 3 раскрывающимися списками

Я написал функцию BirthdayAttribute, что хочу работать аналогично EmailAddressAttribute. Атрибут дня рождения устанавливает подсказку UI, чтобы день рождения DateTime отображался с использованием шаблона редактора, в котором есть три раскрывающихся списка. Атрибут также может быть использован для установки некоторых дополнительных метаданных, которые сообщают о выпадающем году, сколько лет оно должно отображаться.

Я знаю, что я мог бы использовать 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 
    } 
} 

Теперь, когда я смотрю на него, я, вероятно, должен использовать нулевую дату DateTime, но это ни здесь, ни там.

Проблема, с которой я столкнулся, заключается в очень простой проверке недействительных дат, таких как 30 февраля или 31 сентября. Сама проверка достоверно работает, но недействительные даты никогда не сохраняются и сохраняются при перезагрузке формы.

Я хотел бы запомнить недействительную дату 30 февраля и повторно отобразить ее с помощью сообщения о проверке вместо сброса выпадающих списков до их значения по умолчанию. Другие поля, такие как адрес электронной почты (украшенный атрибутом EmailAddressAttribute), сохраняют недействительные записи только в порядке.

На данный момент я просто пытаюсь получить валидацию на стороне сервера. Честно говоря, я еще не начал думать о проверке на стороне клиента.

Я знаю, что есть много я мог бы сделать с Javascript и AJAX, чтобы сделать эту проблему спорным вопросом, но я бы все-таки предпочел бы надлежащее стороне сервера проверка на месте, чтобы возвратиться.

+0

Если вы добавляете изменения в раскрывающееся меню, вы можете создать полную дату в скрытом поле и подтвердить это, вы можете повторно выбрать значения в раскрывающемся списке из значения скрытого текстового поля, однако это так и будет код на стороне клиента – davethecoder

ответ

2

Мне наконец-то удалось решить мою проблему, поэтому я хотел поделиться своим решением.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Несмотря на то, что когда-либо был хорошим с .NET 2.0, я только обновляю свои навыки до последних версий C#, ASP.NET, MVC и Entity Framework. Если есть лучшие способы сделать что-либо, что я сделал ниже, я всегда открыт для обратной связи.

TODO:

  • Осуществить проверку на стороне клиента для недействительных даты, например, 30 февраля. сторона проверки клиента для [Требуется] атрибут уже встроены.

  • Добавить поддержку культур, так что дата отображается в нужном формате

решение пришло ко мне, когда я понял, что проблема, которую я заключалось в том, что DateTime не позволит себе строить с недопустимой датой, такой как 30 февраля. Это просто исключает. Если моя дата не будет построена, я не знаю, как передать мои недействительные данные обратно через связующее средство в ViewModel.

Чтобы решить эту проблему, мне пришлось покончить с DateTime в моей модели просмотра и заменить ее собственным пользовательским классом Date. Нижеприведенное решение обеспечит полностью работоспособную проверку на стороне сервера в случае, если Javascript отключен. В случае ошибки проверки недопустимые выборы сохраняются после отображения сообщения проверки, позволяющего пользователю легко исправить свою ошибку.

Должно быть достаточно легко сопоставить этот класс даты вида с 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. Класс имеет свойства для Year, Month и Day, а также GetTime getter, которые могут попытаться получить вам класс DateTime, если у вас есть действительная дата. В противном случае он возвращает null.

Когда встроенный DefaultModelBinder сопоставляет вашу форму с этим объектом Date, он позаботится о вас для проверки требуемого и диапазона. Однако нам понадобится новый ValidationAtribute, чтобы убедиться, что недопустимые даты, такие как 30 февраля, не разрешены.

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, как только мы уверены, что мы проверяем дату, мы проверяем его свойство 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, но это единственный способ выяснить, как поддерживать недопустимый выбор, используя только код на стороне сервера.

Есть ли улучшения, которые вы сделали бы?

 Смежные вопросы

  • Нет связанных вопросов^_^