View Model virtual properties and drop down lists

asp.net-mvc c# entity-framework

Question

I'm having trouble grasping the proper way to create view models and save that info back to the database using Entity Framework, and I can't seem to find the info I'm looking for, so please forgive me if I have overlooked it.

I came across this post here and he seems to be asking the same question but doesn't get an answer.

My main questions are,

For editing purposes, If I have a ProductModel model that has a Warranty model relationship, should I be using virtual property Warranty in the view model or should I be using int WarrantyId?

If I should be using a virtual property, why doesn't this code save the Warranty properly?

Do I need to explicitly flag or populate the Warranty for update?

Please not this does populate my edit view and select lists as intended.

My (simplified) code is setup as follows:

Model:

    public int ModelId{ get; set; }

    public int ModelNumber { get; set; }

    public virtual Warranty Warranty { get; set;}

View Model:

    public int ModelId { get; set; }

    [Required(ErrorMessage = "Model Number required")]
    [StringLength(25, ErrorMessage = "Must be under 25 characters")]
    [Display(Name="Model Number")]
    public string ModelNumber { get; set; }

    //related objects and necesary properties
    public virtual Warranty Warranty { get; set; }

    public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }

Controller (GET):

public ActionResult Edit(int? id)
    {
        //check the id
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        //get the model and make sure the object is populated
        var model = _modelService.GetModel(id.Value);
        if (model == null)
        {
            return HttpNotFound();
        }

        //pass our entity (db) model to our view model
        var editModelModel = new EditModelModel();
        editModelModel.InjectFrom(model);

        //warranty select list
        editModelModel.WarrantySelectListItems = WarrantySelectList(editModelModel.Warranty.WarrantyId);

        //option multi select list
        editModelModel.OptionSelectListItems = OptionSelectList();

        return View(editModelModel);
    }

Controller (POST) (work in progress):

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(EditModelModel editModelModel)
    {
        if (!ModelState.IsValid)
        {
            return View(editModelModel);
        }

        var modelEntity = new Model();
        modelEntity.InjectFrom(editModelModel);

        _modelService.Update(modelEntity);
        _unitOfWork.Save();

        return RedirectToAction("Index");
    }

View (simplified):

<div class="form-group">
        @Html.Label("Warranty", new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(x => x.Warranty.WarrantyId, Model.WarrantySelectListItems, "--Select--")
            @Html.ValidationMessageFor(model => model.Warranty.WarrantyId)
        </div>
    </div>

Again, I just want to know the proper/best way to set up these viewmodels and models so the EF is doing as much of the work as possible. I feel like if I have to create a WarrantyId field, I'm doing something wrong, but maybe that isn't the case.

Thanks in advance. Any insight/help is greatly appreciated.

1
1
5/23/2017 11:52:58 AM

Accepted Answer

For editing purposes, If I have a ProductModel model that has a Warranty model relationship, should I be using virtual property Warranty in the view model or should I be using int WarrantyId?

You don't use virtual keyword for the property of your ViewModel, because the ViewModel has nothing to do with Entity Framework. The reason to use the virtual keyword is to allow lazy loading in Entity Framework. In your case, if you add the virtual keyword for the Warranty navigation property in the Product POCO class, you can access the Warranty property like below:

Model.Warranty.WarrantyId

And the reason it didn't save the Warranty information into your Database is because you need to define a Warranty foreign key property in the Product class.

In your case, if you're using code first approach and Product is your POCO class, just keep it simply like below:

    public class Product
    {
        public int ModelId { get; set; }
        public int ModelNumber { get; set; }
        public int WarrantyId {get;set;}

        [ForeignKey("WarrantyId ")]
        public virtual Warranty Warranty { get; set; }
    }

Then your ViewModel :

    public class MyViewModel 
    {
        public Product Product { get; set; }
        public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }
    }

Finally your view

  @model MyViewModel

  @Html.DropDownList("Product.Warranty.WarrantyId", Model.WarrantySelectListItems, "--Select--")
  @Html.ValidationMessageFor("Product.Warranty.WarrantyId")

Of course, you need to change your action methods to meet the ViewModel.

6
12/7/2016 2:53:58 PM

Popular Answer

For editing purposes, If I have a ProductModel model that has a Warranty model relationship, should I be using virtual property Warranty in the view model or should I be using int WarrantyId?

You shouldn't be using virtual properties in your view models. A view model simply represents the slice of data that is necessary to display a view. As you are mapping to that view model from your entities, you don't need to mark anything as virtual. See this answer, if you want to know what virtual is doing with regards to the Entity Framework.

Also, you should only be including the information necessary to render that view. So if you just need the WarrantyId in the view, then only include that.

As you're also model-binding back to the same view model in your POST action, you should be very specific about what you want your view model to represent, otherwise you leave yourself open to an over-posting attack.

I feel like if I have to create a WarrantyId field, I'm doing something wrong, but maybe that isn't the case.

It isn't the case. Each of your views should be self-contained. When you first starting using view models, one-per-view, your initial reaction is that of violating DRY. However, each view has different requirements. In terms of view models themselves, the most obvious distinction is validation. If you use entities in your views, all of those views are tied to the validation rules you've applied to your entities. (You'd also be vulnerable to over-posting if you don't want the user to be able to edit the entire entity.)

However, by having separate view models for your views, and applying the validation rules on the view models themselves, you can now have different validation requirements in your views. For example:

public class ViewAViewModel
{
    [Required]
    public int WarrantyId { get; set; }
}

public class ViewBViewModel
{
    // No longer required.
    public int WarrantyId { get; set; }
}

If you'd have included Warranty directly in both of these views, you'd have been stuck with one set of validation rules.

That aside, I'm wondering why you have this on your model (which I assume is an entity):

public IEnumerable<SelectListItem> WarrantySelectListItems { get; set; }

That doesn't belong here. This is a presentation detail, and it should not exist in your business objects. It should exist on your view model.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow