1

I am using Entity Framework to create my models and I have some fields which are foreign keys but also nullable. For instance, the Status of a Device may not be known, so if that is the case, I have it set to NULL. If the Device does have a status, then the value is the Dev_Status_ID which references the Device_Status table so the Dev_Status_Name can be displayed on the website.

I have been testing to ensure that empty fields during create and edit are being handled properly, but they are not. When I leave the Device Status drop down list on the default String.Empty value, the website highlights the Device Status drop down list when I click the Submit button.

Device Create View:

@model ITInventory.Models.Device
@{
    ViewBag.Title = "Create";
}
<h2>Create</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
<div class="form-horizontal">

    <h4>Device</h4>

    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            @* Asset Tag *@
            <div class="form-group">
                @Html.LabelFor(model => model.dev_asset_tag, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.dev_asset_tag, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.dev_asset_tag, "", new { @class = "text-danger" })
                </div>
            </div>

            @*......Leaving out a bunch of other fields for readability*@

            @*Status*@
            <div class="form-group">
                @Html.LabelFor(model => model.Device_Status.dev_status_name, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownListFor(m => m.Device_Status.dev_status_id, (SelectList)ViewBag.dev_status_id, String.Empty, new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.dev_status_id, "", new { @class = "text-danger" })
                </div>
            </div>

            @*.......Leaving out a bunch of other fields for readability*@

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />&nbsp;&nbsp;&nbsp;&nbsp;
                    <input type="reset" value="Cancel" class="btn btn-default" />
                </div>

            </div>
        </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Device Model:

namespace ITInventory.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;

    public partial class Device
    {
    
        [DisplayName("Device ID")]
        public int dev_id { get; set; }

        [DisplayName("Asset Tag")]
        public string dev_asset_tag { get; set; }

        @*.......Leaving out a bunch of other fields for readability*@

        [DisplayName("Status ID")]
        public Nullable<int> dev_status_id { get; set; }

        @*.......Leaving out a bunch of other fields for readability*@
       
        public virtual Device_Status Device_Status { get; set; }
        @*.......Leaving out the other public virtual bits for readability*@*@
    }
}

Status Model:

namespace ITInventory.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;

    public partial class Device_Status
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Device_Status()
        {
            this.Devices = new HashSet<Device>();
        }

        [DisplayName("Status ID")]
        public int dev_status_id { get; set; }

        [DisplayName("Status")]
        public string dev_status_name { get; set; }
    
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Device> Devices { get; set; }
    }
}

Device Controller:

// GET: Devices/Create
        public ActionResult Create()
        {
            @*.......Leaving out a bunch of other fields for readability*@
            
            ViewBag.dev_status_id = new SelectList(db.Device_Status.OrderBy(x => x.dev_status_name), 
            "dev_status_id", "dev_status_name");
            
            @*.......Leaving out a bunch of other fields for readability*@
            return View();
        }

        // POST: Devices/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see https://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Device device)
        {
            if (ModelState.IsValid)
            {
                db.Devices.Add(device);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            @*.......Leaving out a bunch of other fields for readability*@
            
            ViewBag.dev_status_id = new SelectList(db.Device_Status, "dev_status_id", "dev_status_name", null);
            
            @*.......Leaving out a bunch of other fields for readability*@
            
            return View(device);
            }

I'm new to MVC, so I feel I must be missing something obvious, but all my searches haven't helped. My field is set to Nullable, I don't have the field Required, and when I try to do an Insert on the database manually, it doesn't complain about a NULL dev_status_id. Am I trying to do something that isn't allowed?

Community
  • 1
  • 1
  • For a start you have a label for the `Device_Status.dev_status_name` property (and no associated control so its not a label) Then you bind the dropdownlist to property `Device_Status.dev_status_id,` but have associated validation for `dev_status_id` (they are not the same). Delete all this awful code. You are editing data so always use a view model - [What is ViewModel in MVC?](https://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc). And you view model will contain properties `int? SelectedStatus` and `IEnumerable StatusList`. –  Mar 24 '18 at 00:36
  • Then refer [this Q/A](https://stackoverflow.com/questions/34366305/the-viewdata-item-that-has-the-key-xxx-is-of-type-system-int32-but-must-be-o) for how to code it correctly –  Mar 24 '18 at 00:39
  • And the reason the dropdownlist is highlighted is that your binding to an `int` (your `Device_Status.dev_status_id` property) and an `int` must have a value - it cannot be `null` which is the value of the first option. But you never see the associated error message because you create the validation message for a completely different property. –  Mar 24 '18 at 00:43

2 Answers2

0

May be when your default value is selected, on server side it might be empty string please check it in post action. if its empty string assign DBNull.Value to dev_status_id

Jigar Sangoi
  • 159
  • 6
0

In this case you should have 2 models.

One is service model, where you use Nullable, maybe store some data for later decision making. Use it with all your logic.

The other one is ViewModel (e.g. DeviceViewModel), where you put only valid data that will be displayed. This model supposed to be lightweight and 100% valid for display rules.

So when you are done with all the logic in your controller, you compose your ViewModel from your service model an display it.

Ivan Maslov
  • 168
  • 2
  • 13