Asp.net mvc 3 MVC3-当模型需要时间跨度时传递空项

Asp.net mvc 3 MVC3-当模型需要时间跨度时传递空项,asp.net-mvc-3,dictionary,model,drop-down-menu,Asp.net Mvc 3,Dictionary,Model,Drop Down Menu,我最近遇到了一个有趣的问题,我似乎想不出来 我收到的错误消息是: {"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 {

我最近遇到了一个有趣的问题,我似乎想不出来

我收到的错误消息是:

{"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>
}
最后是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
    }))
这似乎已经克服了这个错误,但现在我得到了一个问题进一步。在视图中有一个DropDownlowstresourceTypes。。。。这本质上是一个dropdownlist,用于控制DropDownListFormodel=>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
                            }));
                        });
                    });
    });
});
现在我遇到的问题是:

{"The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.TimeSpan'."}
对象引用未设置为对象的实例

在DropDownlowstresourceTypes上


对此有什么想法吗?

在局部视图中点击断点并检查模型对象,在某个地方您会发现局部视图的对象错误。这就是导致此错误的原因

当您发布无效表单时,将以代码返回视图结束

因此,如果显示相同的视图,而不传递模型,则该模型将为空。您的代码第一次真正需要值是在TimeSpan编辑器中。该值现在是可以为null的,但是您永远不会测试它是否为null

将返回更改为:

return View(em);
要传递模型,或使用GET操作中的代码重新生成并传递模型,请执行以下操作:

return Create(/* Your create date */);
评论后编辑 ModelBinder中的错误可能由以下行引起:

        var hours = ((string[])valueProviderResult_hours.RawValue)[0];
        var minutes = ((string[])valueProviderResult_minutes.RawValue)[0];
将数组转换为字符串[]。我会尽可能晚地将其转换为字符串,并通过以下方式使其更加防错:

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

这将只强制转换到一个对象数组,因此失败的更改更少。取第一个元素,或return null,并使用convert将其转换为字符串,如果结果仍然为null,则返回00。

好吧,这一切都是有意义的,因此我尝试单步执行Create Action方法,可以看出其中确实存在一个错误。问题实际上是ResourceTypes字段的转换:无法将System.String转换为AssetManager.Models.ResourceType我不想在此模型事件上进行转换我不想存储ResourceType它只是用于启用控制的资源DropDownList的动态填充。关于下一步在哪里使用它有什么建议吗?谢谢你的代码,这很有意义,我已经实现了。至于ResourceType的问题——这是导致IsValid等于false的原因——你有什么想法吗。我确信这是我在这一点上遗漏的一些简单的东西。好的,下面是答案。由于GvS解决了timspan周围的问题,我设法解决了ResourceTypes DropDownList周围的问题,方法是将resourceTypeID实际列为模型上的未映射属性,然后将视图更改为使用DropDownList for。模型状态现在有效,一切正常。再次感谢StackOverflow,特别是GvS提高了我的理解并帮助我解决了这个问题。谢谢nathj07