Binding a Nested Object in ASP.NET MVC - Razor - c#

My ASP.NET MVC viewmodel is designed as below
public class Customer
{
public int CustomerId{ get; set; }
public string CustomerName{ get; set; }
public Address CustomerAddress {get;set;}
}
public class Address
{
public int AddressId{ get; set; }
public string HouseName{ get; set; }
public string Location{get;set;}
}
How can I bind Address object properly in cshtml page so that it should be available after form Submit.
In cshtml page I want bind it the properties as below
#model CustomerManagement.Model.ViewModels.Customers.Customer
#using (Html.BeginForm())
{
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-
control readOnlySchdCode", #readonly = "readonly" } })
#Html.Hidden("AddressId", Model.Address.AddressId)
#Html.Hidden("HouseName", Model.Address.HouseName)
}
In controller form submit will look like as below
public async Task<ActionResult> AddCustomer(Customer model)
{
//How can i access Address properties eg:model.CustomerAddress.AddressId??
}
Can anyone share a sample code on how to bind the Above viewmodel properly in cshtml using razor template and how properties are properly retrieved in Action method while form submit.

You could try this way.
Client side:
#using Newtonsoft.Json.Linq
#using WebAppDemo.Models
#model WebAppDemo.Models.Customer
#{
ViewData["Title"] = "Home Page";
}
#{
ViewBag.Title = "Home Page";
}
<br />
#using (Html.BeginForm("AddCustomer", "Home", FormMethod.Post, new { id = "Form1" }))
{
<div class="row">
<div class="col-lg-2">Cust Id</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerId, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Customer Name</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Address</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerAddress.HouseName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-12"><input type="submit" value="Submit" class="btn btn-primary"></div>
</div>
}
Form Output Should Be Like:
Controller:
[HttpPost] //attribute to get posted values from HTML Form
public ActionResult AddCustomer(Customer model)
{
return Ok();// For testing I just kept it as `Ok` You can return `View()`;
}
Note: Please try to pass value on form as per Model property descriptions. Other than you might
get null value.
Output:
Hope it will help you. Let me know if you have any further concern.

Here is a little example
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
and our controller would be like
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
using view like
#model MvcApplication1.Models.BigViewModel

Related

Dynamically created forms submits null to controller (MVC)

I'm creating multiple forms in a for in my view, the problem comes when I send to the controller on submit and it comes null.
here an example.
#model List<Project.ViewModels.ValidForm>
#if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<div>
#using (Html.BeginForm("Method", "Controller", FormMethod.Post, new { id = "valForm" + #Model[i].Id }))
{
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].Data1, new { #class = "form-control" })
</div>
<div class="col-md-4">
<label>Data 2 options</label><br />
#Html.TextBoxFor(m => m[i].Data2.Option1, new { #class = "form-control" })
</div>
<div>
<button type="submit" class="btn btn-success">Save this form</button>
</div>
}
</div>
}
}
And there are the viewmodels.
public class ValidForm{
public int data1 { get; set; }
public Data2 data2 {get;set;}
}
public class Data2{
public int option1 {get;set;}
public int option2 {get;set;}
}
and the controller.
[HttpPost]
public ActionResult validaciones(validform vm){
//do something.
return view();
}
The reason you get null is that your model is an array of ValidForm and not just one.
Change your controller to:
[HttpPost]
public ActionResult validaciones(ValidForm[] vms){
ValidForm vm = ValidForm[0];
//do something with vm.
return view();
}
If you want only one form at a time you can do this: (I've added Name = "PropName", mind the capital N in Name)
Then you're post action should expect a single VM
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].Data1, new { #class = "form-control", Name="Data1" })
</div>
For data2 the name needs to be Data2.option1, ect...
Fully working example:
HomeController:
public class HomeController : Controller
{
public class ValidForm
{
public string Id { get; set; }
public int data1 { get; set; }
public Data2 data2 { get; set; }
}
public class Data2
{
public int option1 { get; set; }
public int option2 { get; set; }
}
public ActionResult Index()
{
var model = new ValidForm[2] { new ValidForm { }, new ValidForm {data2 = new Data2{}} };
return PartialView(model);
}
[HttpPost]
public ActionResult Tester(ValidForm model)
{
return View("Index", new ValidForm[1] { model });
}
}
View:
#model MvcApplication1.Controllers.HomeController.ValidForm[]
#if (Model != null)
{
for (int i = 0; i < Model.Count(); i++)
{
<div>
#using (Html.BeginForm("Tester", "Home", FormMethod.Post, new { id = "valForm" + #Model[i].Id }))
{
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].data1, new { #class = "form-control", Name = "data1"})
</div>
<div class="col-md-4">
<label>Data 2 options</label><br />
#Html.TextBoxFor(m => m[i].data2.option1, new { #class = "form-control", Name = "data2.option1"})
</div>
<div>
<button type="submit" class="btn btn-success">Save this form</button>
</div>
}
</div>
}
}
Also note that the input boxes can only be numbers because you have all ints.

Implementing bound dropdown list in MVC5 - Post action returns null values

I appear to be having some problems with dropdown list populating and binding in MVC. The simple example I have has a List of Movies with a Genre item that is populated with a drop down.
I pass across a Select List with the items to populate the drop down but appear to be running into problems when the post action is happening.
The problems appear to be :
The ViewModel being returned appears to return the GenreList as null on the Post action.
The Genre does not appear to be set so that after the edit -the dropdown list is populated correctly.
I cannot seem to find a good answer for this and have been trying quite a few examples but seem to be going round in circles. Would like to try and get this most basic of dropdown list edit example working so I can see how this should be implemented.
Model Classes
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
namespace Test.Models
{
public class Genre
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
}
public class Movie
{
[Key]
public int MovieID { get; set; }
public string Name { get; set; }
public Genre MovieGenre { get; set; }
}
public class MovieViewModel
{
public Movie MovieItem { get; set; }
public SelectList GenreList{ get; set; }
}
}
Controller Code
namespace Test.Controllers
{
public class MoviesController : Controller
{
private DataContext _dc = new DataContext();
// GET: Movies
public ActionResult Index()
{
var x = from m in _dc.Movies
select m;
return View(x.ToList());
}
// GET: Movies/Edit/5
public ActionResult Edit(int id)
{
var x = from m in _dc.Movies
where m.MovieID == id
select m;
var l = from m in _dc.Genres
select m;
var y = new MovieViewModel
{
GenreList = new SelectList(l.ToList(), "ID", "Description"),
MovieItem = x.FirstOrDefault()
};
return View(y);
}
// POST: Movies/Edit/5
[HttpPost]
public ActionResult Edit(int id, MovieViewModel m)
{
// PROBLEM: GenreList in model is now not populate for return
if (ModelState.IsValid)
{
var movie = _dc.Movies.Find(id);
movie.Name = m.MovieItem.Name;
movie.MovieGenre = m.MovieItem.MovieGenre;
// PROBLEM: The MovieGenre does not appear to be saved correctly
// when you make the edit and go back to that record after saving
// the dropdown is not populated.
_dc.SaveChanges();
return RedirectToAction("Index", "Movies");
}
return View(m);
}
}
}
Razor View Code
#model Test.Models.MovieViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.MovieItem.MovieID)
<div class="form-group">
#Html.LabelFor(model => model.MovieItem.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MovieItem.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.MovieItem.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div>
#Html.LabelFor(m => m.GenreList, "Genre:")
#Html.DropDownListFor(m => m.MovieItem.MovieGenre.Id, (IEnumerable<SelectListItem>) Model.GenreList)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>

Multiple forms issue

I have an object passed to an "Edit" controller. The object is a "Component" which can also have "Component Properties" added to it: Manufacturer, Size, etc. I want to be able to add those attributes on the same page as the object's Edit view.
I have 2 forms. The first one functions correctly to edit the model.
The second one posts to the "Create" method of the ComponentPropertyValueController, but the only value that passes to the object parameter is the value entered (such as the manufacturer). The Parent object attributes needed to be associated with the added "Component Properties" are 0's and null.
What I have done is tried to instantiate a new ComponentPropertyValue object and set the id to the parent id. I also tried setting the parent object to the current view model.
I cannot figure out how to make the new ComponentPropertyValue object passed to the Create controller belong to the parent Component when it posts.
Here is a picture of the form. The form looks fine. It has the proper drop downs and seems to function correctly.
Screenshot
Picture of Entity Model
The model is purposely Generic. Allowing to add an type of thing and add attributes to them.
"Component" Controller Code:
//
// GET: /Components/Edit/5
public ActionResult Edit(int id = 0)
{
Component component = db.Components.Find(id);
if (component == null)
{
return HttpNotFound();
}
ViewBag.ComponentPropertyId = new SelectList(db.ComponentProperties, "Id", "Property");
ViewBag.ComponentTypeId = new SelectList(db.ComponentTypes, "Id", "Type", component.ComponentTypeId);
return View(component);
}
Razor View:
#model removed.com.Models.Component
#using removed.com.Models
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Component</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.ComponentTypeId, "ComponentType")
</div>
<div class="editor-field">
#Html.DropDownList("ComponentTypeId", String.Empty)
#Html.ValidationMessageFor(model => model.ComponentTypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<h2>Add Property</h2>
#{
ComponentPropertyValue componentPropertyValue = new ComponentPropertyValue() { Component = Model, ComponentId = Model.Id} ;
}
#using (Html.BeginForm("Create", "ComponentPropertyValue", FormMethod.Post, componentPropertyValue))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>ComponentPropertyValue</legend>
<div class="editor-label">
#Html.LabelFor(x => componentPropertyValue.ComponentPropertyId, "ComponentProperty")
</div>
<div class="editor-field">
#Html.DropDownList("ComponentPropertyId", String.Empty)
#Html.ValidationMessageFor(x => componentPropertyValue.ComponentPropertyId)
</div>
<div class="editor-label">
#Html.LabelFor(x => componentPropertyValue.Value)
</div>
<div class="editor-field">
#Html.EditorFor(x => componentPropertyValue.Value)
#Html.ValidationMessageFor(x => componentPropertyValue.Value)
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I have also tried to add hidden inputs, and here is the html for the form:
<form Component="" ComponentId="" ComponentProperty="" ComponentPropertyId="0" Id="0" Value="" action="/ComponentPropertyValue/Create" method="post"> <fieldset>
<legend>ComponentPropertyValue</legend>
<input id="ComponentId" name="ComponentId" type="hidden" value="36" />
36 is the correct id for the record. but it is never passed to the controller.
If after instantiating, I set the ComponentId:
#{
ComponentPropertyValue componentPropertyValue = new ComponentPropertyValue() { ComponentId = Model.Id };
}
#using (Html.BeginForm("Create", "ComponentPropertyValue", FormMethod.Post, componentPropertyValue))
The form is generated with the value set. Yet it still never reaches the controller.
<form Component="" ComponentId="36" ComponentProperty="" ComponentPropertyId="0" Id="0" Value="" action="/ComponentPropertyValue/Create" method="post">
My view model for the two interacting classes:
public partial class Component
{
public Component()
{
this.ComponentPropertyValues = new HashSet<ComponentPropertyValue>();
this.ComponentVideos = new HashSet<ComponentVideo>();
}
public int Id { get; set; }
public int ComponentTypeId { get; set; }
public string Name { get; set; }
public virtual ICollection<ComponentPropertyValue> ComponentPropertyValues { get; set; }
public virtual ComponentType ComponentType { get; set; }
public virtual ICollection<ComponentVideo> ComponentVideos { get; set; }
}
public partial class ComponentPropertyValue
{
public int Id { get; set; }
public int ComponentPropertyId { get; set; }
public int ComponentId { get; set; }
public string Value { get; set; }
public virtual ComponentProperty ComponentProperty { get; set; }
public virtual Component Component { get; set; }
}

HtmlPrefix for Partialview, remove the dot

I created a partial view that should display a list of user with a check box , so i can reuse this partial view in various pages.
The problem is that, i'm not able to have the correct htmlprefix the input generated
(I would like to remove the . of the prefix )
Model:
public class CircleEditViewModel
{
[Key]
public int CircleId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public bool IsSystem { get; set; }
public List<SimpleUserListViewModel> Users { get; set; }
public CircleEditViewModel()
{
Users = new List<SimpleUserListViewModel>();
}
}
public class SimpleUserListViewModel
{
public SimpleUserListViewModel()
{
}
public SimpleUserListViewModel(User user)
{
this.UserId = user.UserId;
FullName = user.FullName;
}
public int UserId { get; set; }
public byte[] Picture { get; set; }
public string FullName { get; set; }
public bool IsCheckedForAction { get; set; }
}
'Main view':
#model Wims.Website.ViewModels.CircleEditViewModel
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse('form');
});
</script>
#using (Ajax.BeginForm(Html.ViewContext.RouteData.Values["Action"].ToString(), null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "SaveDone(data)" }, new { id = "editform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Circle</legend>
#Html.Label(DateTime.Now.ToString());
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
#Html.Partial("~/Views/Shared/_UserList.cshtml", Model.Users,
new ViewDataDictionary(Html.ViewDataContainer.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Users" }
})
#Html.GenerateSecureDataControls(model => model.CircleId)
<input type="submit" value="Save" />
}
Partial view:
#model List<Wims.Website.ViewModels.Shared.SimpleUserListViewModel>
#{
if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<div class="userDetail">
<div>
<div>
#Html.CheckBoxFor(model => model[i].IsCheckedForAction)
</div>
<div class="iconDiv">
#Html.Image("~/Content/Images/defaultUser.jpg", Model[i].FullName, null)
</div>
<div>
#Html.TextBoxFor(model => model[i].FullName)
#Html.HiddenFor(model => model[i].UserId)
</div>
</div>
</div>
<div style="clear: both"></div>
}
}
}
I am almost there, the input generated id's are
id="Users.[0].FullName
Is there any way i can remove the first dot?
I've found some solution yesterday on a blog (which i can't find anymore...) but it was for MVC3 and I couldn't make it work anyway...
Thanks for the help!
EDIT:
Maybe I should use EditorFor instead of partial view:
.NET MVC 4 Strongly typed ViewModel containing Strongly typed Model with EditorFor and EditorTemplate partial view not binding
Will check tonight
Alrighty, The EditorFor worked perfectly..
I need to read more about this.

How to have List Objects retained in the model on HttpPost?

I have a Model that contains a List of a custom type.
I want the data from this type to be passed back in when a model is submitted as a HttpPost call the the controller.
However, it does not seem to do what I want. I've got where I am so far by following Passing IEnumerable or list Model to Controller using HttpPost but I'm having a problem.
My controller method:
[HttpPost]
public ActionResult UpdateStock(int id, ProductModel model)
{
return View("UpdateStock", model);
}
Now, the View is like this (trimmed):
#using (Html.BeginForm())
{
<div>
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#for ( int i = 0; i < Model.Stock.Count; i++ )
{
var item = Model.Stock[i];
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Key"
value="#item.Key" />
</div>
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Value"
value="#item.Value" />
</div>
}
}
My problem is, that it seems the #Html.EditorFor() and <input type=.../> tags don't seem to play well with each other. If I have it like above, then the ProductNo and other properties using #Html methods won't be passed through to the model.
Any advice much appreciated.
I would simply use editor templates:
Model:
public class ProductModel
{
public string ProductNo { get; set; }
public string ProductName { get; set; }
public IEnumerable<Stock> Stocks { get; set; }
}
public class Stock
{
public string Key { get; set; }
public string Value { get; set; }
}
Controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new ProductModel
{
ProductNo = "123",
ProductName = "p name",
Stocks = new[]
{
new Stock { Key = "key1", Value = "value1" },
new Stock { Key = "key2", Value = "value2" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(ProductModel model)
{
...
}
}
View:
#model ProductModel
#using (Html.BeginForm())
{
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#Html.EditorFor(x => x.Stocks)
}
and then you define a custom editor template for the Stock type (~/Views/Shared/EditorTemplates/Stock.cshtml):
#model Stock
<div class="editor-field">
#Html.EditorFor(x => x.Key)
</div>
<div class="editor-field">
#Html.EditorFor(x => x.Value)
</div>

Categories