2011-12-09 53 views
2

嗯,我最近遇到了一個我似乎無法解決的有趣問題。MVC3 - 當模型需要TimeSpan時傳遞null item

該錯誤消息我得到的是:

{"The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.TimeSpan'."} 

這當我嘗試提交新的條目添加到數據庫中都會出現。所以,提交內容的細節。

模型類:

public class EventModel 
     { 
      [Key] 
      public int EventID { get; set; } 

      [DisplayName("Booking title")] 
      [Required(ErrorMessage="Please provide a title for the booking")] 
      public string Title { get; set; } 

      [DataType(DataType.Date)] 
      [DisplayName("Start date")] 
      [DisplayFormat(DataFormatString="{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)] 
      public DateTime StartDateTime { get; set; } 

      [DisplayName("End date")] 
      [DataType(DataType.Date)] 
      [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)] 
      [IsDateAfter("StartDateTime", true, ErrorMessage="End date must be on or after the start date")] 
      public DateTime EndDateTime { get; set; } 

      public long StartTicks { get; set; } 
      public long EndTicks { get; set; } 

      [NotMapped] 
      [DisplayName("Start Time")] 
      public TimeSpan StartTime 
      { 
       get { return TimeSpan.FromTicks(StartTicks); } 
       set { StartTicks = value.Ticks; } 
      } 

      [NotMapped] 
      [DisplayName("End Time")] 
      public TimeSpan EndTime 
      { 
       get { return TimeSpan.FromTicks(EndTicks); } 
       set { EndTicks = value.Ticks; } 
      } 

      [DefaultValue(2)] 
      [DisplayName("Booking is")] 
      public int BookingStatus { get; set; } 

      [DisplayName("Set recurrence")] 
      [DefaultValue(false)] 
      public bool DoesRecur { get; set; } 

      [DisplayName("Set recurrence type")] 
      public string Pattern { get; set; } 

      [DisplayName("Set the day this happens on ")] 
      public int DayIndex { get; set; } 

      [DisplayName("Choose the day instance this recurs on")] 
      public int DayCount { get; set; } 

      [DisplayName("Day ")] 
      [NotMapped] 
      public string Day { get; set; } 

      [DisplayName("Instance")] 
      [NotMapped] 
      public string Instance { get; set; } 

      // links resource to a user/member 
      [DisplayName("Booked by")] 
      [NotMapped] 
      public string BookerName { get; set; } 

      public Guid MemberID { get; set; } 

      // links resource to a resource type 
      [DisplayName("Resource required:")] 
      public int ResourceID { get; set; } 
     } 

的操作方法在控制類:

[HttpGet] 
     public ActionResult Create(DateTime eventDate) 
     { 
      var days = from DayOfWeek d in Enum.GetValues(typeof(DayOfWeek)) 
         select new { ID = (int) d, Name = (DayOfWeek)d }; 

      var instance = from DayInstance i in Enum.GetValues(typeof(DayInstance)) 
          select new { ID = (int) i, Name = (DayInstance)i }; 

      MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName); 
      CreateEventViewModel model = new CreateEventViewModel() 
      { 
       Event = new EventModel() 
       { 
        StartDateTime = eventDate, 
        EndDateTime = eventDate, 
        MemberID = (Guid)mu.ProviderUserKey 
       }, 
       Resources = DBContext.Resources.ToList(), 
       Patterns = DBContext.Patterns.ToList(), 
       ResourceTypes = DBContext.ResourceTypes.ToList() 
      }; 

      ViewData["dayOfWeek"] = new SelectList(days, "ID", "Name", DayOfWeek.Monday); 
      ViewData["dayInstance"] = new SelectList(instance, "ID", "Name", DayInstance.First); 

      return View(model); 
     } 

     [HttpPost] 
     public ActionResult Create(CreateEventViewModel em) 
     { 
      if (ModelState.IsValid) 
      { 
       // get the resource turn aournd time 
       double turnAround = rc.GetResourceTurnAround(em.Event.ResourceID); 

       MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName); 
       em.Event.MemberID = (Guid) mu.ProviderUserKey; 
       em.Event.BookingStatus = 2; 

       // need to get the time added to the date. 
       DateTime actualStartPoint = new DateTime(em.Event.StartDateTime.Ticks + em.Event.StartTicks); 
       DateTime actualEndPoint = new DateTime(em.Event.EndDateTime.Ticks + em.Event.EndTicks); 

       em.Event.StartDateTime = actualStartPoint; 
       em.Event.EndDateTime = actualEndPoint; 

       // add turn around time to the end of the event 
       em.Event.EndDateTime = em.Event.EndDateTime.AddMinutes(turnAround); 

       // needed becase these are handled slighty differently to the rest of the model 
       em.Event.DayIndex = int.Parse(Request.Form.GetValues("dayOfWeek").GetValue(0).ToString()); 
       em.Event.DayCount = int.Parse(Request.Form.GetValues("dayInstance").GetValue(0).ToString()); 

       DBContext.Events.Add(em.Event); 
       DBContext.SaveChanges(); 

       // get the resource owner 
       MembershipUser resourceOwner = Membership.GetUser(rc.GetResourceOwnerByID(em.Event.ResourceID)); 

       // email the admin team and the user the details of this booking 
       // get the email address of the user making the booking 

       StringBuilder message = new StringBuilder(); 
       message.AppendFormat("Thank you for your booking, this is now being reviewed by the team.\nThe details of your booking are included for confirmation.\n"); 
       message.AppendFormat("Booking Title: {0}\nResource: {1}\n Date: {2} {3} (this includes our turn around time added on)\n", em.Event.Title, rc.GetResourceNameByID(em.Event.ResourceID), actualStartPoint, actualEndPoint); 
       message.AppendFormat("You can log in at any time to review your bookings.\nYou will receive an email when the team have reviewed this request\nMany thanks\n"); 
       EmailHandler eh = new EmailHandler(); 
       eh.SetRecipient(Membership.GetUser().Email); 
       eh.AddAdminEmail(); 
       eh.AddBcc(resourceOwner.Email); 
       eh.SetSubject("Booking Requested"); 
       eh.SetBody(message.ToString()); 
       eh.sendMessage(); 

       return RedirectToAction("Index"); 
      } 
      else 
      { 
       return View(); 
      } 
     } 

現在的視圖中的項目 - 主要觀點:

@model AssetManager.Models.CreateEventViewModel 
@{ 
    ViewBag.Title = "Create"; 
    Layout = "~/Views/Shared/_Layout.cshtml"; 
} 
@using (Html.BeginForm()) 
{ 
    @Html.ValidationSummary(true) 
    <fieldset> 
     <legend id="bookingLegend">Place Booking</legend> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.Title) 
      </div> 
      <div class="editor-field"> 
       @Html.EditorFor(model => model.Event.Title) 
       @Html.ValidationMessageFor(model => model.Event.Title) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.StartDateTime) 
      </div> 
      <div class="editor-field"> 
       @Html.EditorFor(model => model.Event.StartDateTime, new { @class = "date" }) 
       @Html.ValidationMessageFor(model => model.Event.StartDateTime) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label timeSelector"> 
       @Html.LabelFor(model => model.Event.StartTime) 
      </div> 
      <div class="editor-field timeSelector"> 
       @Html.EditorFor(model => model.Event.StartTime) 
       @Html.ValidationMessageFor(model => model.Event.StartTime) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.EndDateTime) 
      </div> 
      <div class="editor-field"> 
       @Html.EditorFor(model => model.Event.EndDateTime, new { @class = "date" }) 
       @Html.ValidationMessageFor(model => model.Event.EndDateTime) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label timeSelector"> 
       @Html.LabelFor(model => model.Event.EndTime) 
      </div> 
      <div class="editor-field timeSelector"> 
       @Html.EditorFor(model => model.Event.EndTime) 
       @Html.ValidationMessageFor(model => model.Event.EndTime) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.Label("Select Resource Type") 
      </div> 
      <div class="editor-field"> 
       @Html.DropDownList("ResourceTypes", new SelectList(Model.ResourceTypes, "ResourceTypeID", "Title"), "-- Select Resource Type --", new { @id = "ddlResourceTypes" }) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.ResourceID) 
      </div> 
      <div class="editor-field"> 
       @Html.DropDownListFor(model => model.Event.ResourceID, new SelectList(Enumerable.Empty<SelectListItem>(), "ResourceType", "Name"), "-- Select Resource --", new { @id = "ddlResources" }) 
       @Html.ValidationMessageFor(model => model.Event.ResourceID) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.DoesRecur) 
      </div> 
      <div class="editor-field"> 
       @Html.EditorFor(model => model.Event.DoesRecur) 
       @Html.ValidationMessageFor(model => model.Event.DoesRecur) 
      </div> 
     </div> 
     <div id="recurType" class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.Pattern) 
      </div> 
      <div class="editor-field"> 
       @Html.DropDownListFor(model => model.Event.Pattern, new SelectList(Model.Patterns, "PatternCode", "Pattern"), "-- Select Recurrence Pattern --") 
       @Html.ValidationMessageFor(model => model.Event.Pattern) 
      </div> 
     </div> 
     <div id="recurDayHappens" class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.DayIndex) 
      </div> 
      <div class="editor-field"> 
       @Html.DropDownList("dayOfWeek") 
       @Html.ValidationMessageFor(model => model.Event.DayIndex) 
      </div> 
     </div> 
     <div id="recurInstance" class="controlcontainer"> 
      <div class="editor-label"> 
       @Html.LabelFor(model => model.Event.DayCount) 
      </div> 
      <div class="editor-field"> 
       @Html.DropDownList("dayInstance") 
       @Html.ValidationMessageFor(model => model.Event.DayCount) 
      </div> 
     </div> 
     <div class="controlcontainer"> 
      <p> 
       <input class="subButton" type="submit" value="Create" /> 
       <input id="cancelBtn" class="cancelButton" type="button" value="Cancel" onclick="location.href='@Url.Action("Index", "Calendar")'" /> 
      </p> 
     </div> 
    </fieldset> 
} 

再有就是TimeSpan項目的編輯模板:

@model TimeSpan 

@Html.DropDownList("Hours", Enumerable.Range(0, 24) 
    .Select(i => new SelectListItem { Value = i.ToString(), 
    Text = i.ToString(), Selected = Model.Hours == i }))&nbsp;: 

@Html.DropDownList("Minutes", Enumerable.Range(0, 60) 
    .Select(i => new SelectListItem { Value = i.ToString(), 
    Text = i.ToString(), Selected = Model.Minutes == i })) 

最後一個TimeBinder類:

public class TimeBinder : IModelBinder 
    { 
     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
     { 
      // Ensure there's incomming data 
      var key_hours = bindingContext.ModelName + ".Hours"; 
      var valueProviderResult_hours = bindingContext.ValueProvider 
       .GetValue(key_hours); 

      var key_minutes = bindingContext.ModelName + ".Minutes"; 
      var valueProviderResult_minutes = bindingContext.ValueProvider 
       .GetValue(key_minutes); 

      if (valueProviderResult_hours == null || string.IsNullOrEmpty(valueProviderResult_hours.AttemptedValue) 
       || valueProviderResult_minutes == null || string.IsNullOrEmpty(valueProviderResult_minutes.AttemptedValue)) 
      { 
       return null; 
      } 

      // Preserve it in case we need to redisplay the form 
      bindingContext.ModelState.SetModelValue(key_hours, valueProviderResult_hours); 
      bindingContext.ModelState.SetModelValue(key_minutes, valueProviderResult_minutes); 

      // Parse 
      var hours = ((string[])valueProviderResult_hours.RawValue)[0]; 
      var minutes = ((string[])valueProviderResult_minutes.RawValue)[0]; 

      // A TimeSpan represents the time elapsed since midnight 
      var time = new TimeSpan(Convert.ToInt32(hours), Convert.ToInt32(minutes), 0); 

      return time; 
     } 
    } 

就是這樣,這是所有參與的代碼。我完全困惑於爲何發生此錯誤。任何想法或建議的原因和解決方案都非常感激。

非常感謝 nathj07

編輯 PK,所以我試着用個時間跨度編輯模板一些不同的充:

@model TimeSpan? 
@Html.DropDownList("Hours", Enumerable.Range(0, 24) 
    .Select(i => new SelectListItem 
    { 
     Value = i.ToString(), 
     Text = i.ToString(), 
     Selected = Model.HasValue ? Model.Value.Hours == i : false 
    }))&nbsp;: 
@Html.DropDownList("Minutes", Enumerable.Range(0, 60) 
    .Select(i => new SelectListItem 
    { 
     Value = i.ToString(), 
     Text = i.ToString(), 
     Selected = Model.HasValue ? Model.Value.Minutes == i : false 
    })) 

這似乎已經克服了這個錯誤,但現在我得到的一個問題一個進一步下降。在視圖中有一個DropDownList(「ResourceTypes」....)這本質上是一個下拉列表,用於控制出現在DropDownListFor(model => model.Event.ResourceID .....)中有一個簡單的一段JavaScript代碼:

$(document).ready(function() { 
    $("#ddlResourceTypes").change(function() { 
     var idResourceType = $('#ddlResourceTypes').val(); 
     $.getJSON("/Resource/LoadResourcesByType", { id: idResourceType }, 
        function (resourceData) { 
         var select = $("#ddlResources"); 
         select.empty(); 
         select.append($('<option/>', { 
          value: 0, 
          text: "-- Select Resource --" 
         })); 
         $.each(resourceData, function (index, itemData) { 
          select.append($('<option/>', { 
           value: itemData.Value, 
           text: itemData.Text 
          })); 
         }); 
        }); 
    }); 
}); 

現在我得到的問題是:

對象引用不設置到對象

在DropDownList中的一個實例( 「ResourceTypes」 .....)

對此有何意見?

回答

1

當您發佈無效表單時,以代碼return View()結束。

因此,您顯示相同的視圖,但不傳遞模型,模型將爲空。您的代碼首次真正需要一個值是在TimeSpan的編輯器中。現在這個值是可空的,但是你從不測試它爲空的情況。

改變返回到:

return View(em); 

傳遞模式,或使用代碼從一開始採取行動,重建並通過模型:評論

return Create(/* Your create date */); 

編輯 ModelBinder中的錯誤可能是由以下幾行引起的:

 var hours = ((string[])valueProviderResult_hours.RawValue)[0]; 
     var minutes = ((string[])valueProviderResult_minutes.RawValue)[0]; 

將陣列轉換爲string[]。我會做轉換爲字符串儘可能晚,並使其更錯誤的證明:

 var hours = Convert.ToString(((object[])valueProviderResult_hours.RawValue).FirstOrDefault()) ?? "00"; 

這將只是轉換爲對象的數組,所以有較少的失敗改變。取第一個元素,或返回null,並使用Convert將其轉換爲字符串,如果結果仍爲null,則返回「00」。

+0

那麼,這一切都是有道理的,所以我嘗試了一下創建操作方法,可以看到確實有一個錯誤。麻煩實際上是ResourceTypes字段轉換:無法將System.String轉換爲AssetManager.Models.ResourceType我不希望在此模型上進行轉換(Event)我不想存儲ResourceType,它只是在那裏啓用動態總體的資源dropdownlistfor控制。任何關於下一步該怎麼辦的建議? – nathj07

+0

我添加了一些代碼。 – GvS

+0

感謝代碼,這是有道理的,我已經實現了。至於ResourceType的問題 - 這是什麼導致IsValid等於false - 你有什麼想法在這裏。我敢肯定這件事很簡單,我錯過了這個。 – nathj07

0

在部分視圖中打斷點並檢查模型對象,在某處您會發現部分視圖有錯誤的對象。那是造成此錯誤的原因

相關問題