I have a requirement to be able to dynamically add/remove rows to a Tabel in an MVC 5 Application I am working on. I have also included knockout in my project as I use it to post back to preform calculation on my viewModel.
What I have done so far is created a List on my User Model to hold the details of the AdditionalUsers:
public List<AdditionalUser> AdditionalUsers { get; set; }
Additional User class defined as:
public class AdditionalUser
{
public string FirstName { get; set; }
public string Surname { get; set; }
public double Cost { get; set; }
}
I then created a EditorTemplates folder and created a partial view _AdditionalUser.cshtml as below:
#model MyProject.Models.AdditionalUser
<td>
#Html.TextBoxFor(model => model.FirstName)
</td>
<td>
#Html.TextBoxFor(model => model.Surname)
</td>
<td>
#Html.TextBoxFor(model => model.Cost)
</td>
Where I need this rendered on my User View I have done the following:
<tr>
#Html.EditorFor(model => model.AdditionalUsers)
</tr>
Each other on the view has 3 . Doing this then in my controller where I new my User model I did:
model.AdditionalUsers = new List<AdditionalUser>(2);
I would have thought that would have created two blank rows in the tr where I called EditorFor but nothing is rendered? This was just for my first test to get the EditorTemplate working. I want to then wire this up to knockout to Add and remove the rows dynamically but first I am wondering why the EditorTemplate is not rendering as expected?
Your editor template is named incorrectly. There are two ways for MVC to pick it up:
By Name
Name the template with the exact same name as the data type:
DateTime.cshtml
String.cshtml
AdditionalUser.cshtml
Explicitly
In the property of your model, use the UIHint attribute:
public class MyModel
{
public SomeObject TheObject { get; set; }
[UIHint("SomeObject")]
public SomeObject AnotherObject { get; set; }
}
Additionally your code is not quite correct to get the rows rendered. You should first add the tr tag to the view:
#model MyProject.Models.AdditionalUser
<tr>
<td>
#Html.TextBoxFor(model => model.FirstName)
</td>
<td>
#Html.TextBoxFor(model => model.Surname)
</td>
<td>
#Html.TextBoxFor(model => model.Cost)
</td>
</tr>
Next change your parent view to something like this (note I've added table header row for clarity):
<table>
<tr>
<th>#Html.DisplayNameFor(m => m.FirstName)</th>
<th>#Html.DisplayNameFor(m => m.Surname)</th>
<th>#Html.DisplayNameFor(m => m.Cost)</th>
</tr>
#Html.EditorFor(model => model.AdditionalUsers)
</table>
You can drop the "_" character from the name, or there is an overload, that takes a template name as the second argument.
#Html.EditorFor(model => model.AdditionalUsers, "_AdditionalUsers")
In your GET action method, you need to return some item in the AdditionalUsers collection. Try this.
var yourViewModel=new YourViewModel();
var userList = new List<AdditionalUser>();
userList.Add(new AdditionalUser { FirstName ="A"} );
userList.Add(new AdditionalUser{ FirstName ="B"});
yourViewModel.AdditionalUsers =userList();
return view(yourViewModel);
Also your editor template name should be same as the class name which is strongly typed to the editor template razor view, which is AdditionalUser.cshtml
here is the rules
1- the name of the template must be same. in your case it must be AdditionalUser.cshtml
2- MVC first looks if a folder EditorTemplates exists in the parent folder of the view (which has controller name) then looks at Shared folder.
But in your case #Html.EditorFor(model => model.AdditionalUsers) is would not work as the type is List and as far as I know there is no way to do that so use a foreach loop;
#foreach (var u in model.AdditionalUsers) {
#Html.EditorFor(model => u)
}
should do the trick.
Related
I have a main viewmodel that contains a list of child viewmodels to be edited:
public class MyObjectGridViewModel
{
public string Comment { get; set; }
public List<MyObject> Objects { get; set; } = new List<MyObject>();
}
The structure of MyObject is really not relevant at all. Then I have an editor template for the Objects collection, like this:
#model MyObject
<tr>
<td class="hidden">
#Html.HiddenFor(m => m.Id)
</td>
<td>
#Html.DisplayFor(m => m.Name)
</td>
<td>
#Html.CheckBoxFor(m => m.IsBanned, new { #class = "checkbox" })
</td>
</tr>
And then a form view to edit individual MyObject models inline, i.e. without a separate Edit view:
#model MyObjectGridViewModel
#using (Html.BeginForm("Ban", "Access", FormMethod.Post, new { role = "form" }))
{
#Html.TextBoxFor(m => m.Comment)
<table class="table bans">
#Html.EditorFor(m => m.Objects)
</table>
<input type="submit" value="Save" />
}
And a post action on the controller:
[HttpPost]
public ActionResult Ban(MyObjectGridViewModel model)
{
var items = model.Objects.Count(); // items == 0
}
Yet when I click the submit button and invoke this action, the model.Objects list has zero items. The editor template displays an expected 5 items, I check 1 or 2 checkboxes, submit, and lose all the items in the Objects property.
What is wrong here?
I found some old code, and it looks like the solution is to have an editor template for the main view model, and inside that, use another one for the collection item. I now have:
MyObjectGridViewModel.cshtml:
#model MyObjectGridViewModel
#Html.EditorFor(model => Model.Objects)
MyObject.cshtml:
#model MyObject
<tr>
<td class="hidden">
#Html.HiddenFor(m => m.Id)
</td>
<td>
#Html.DisplayFor(m => m.Name)
</td>
<td>
#Html.CheckBoxFor(m => m.IsBanned, new { #class = "checkbox" })
</td>
</tr>
And in the form, instead of
#Html.EditorFor(m => Model.Objects)
I now have
#Html.EditorFor(m => Model)
And all seems to work fine.
I have a problem and I can't find solution.
I'm using Razor and it is my VieModel class.
public class GroupToExport
{
public GroupToExport()
{
ToExport = false;
}
[DisplayName("Export")]
public bool ToExport { get; set; }
public Group Group { get; set; }
}
public class GroupsToExport
{
public GroupsToExport()
{
//refill list
}
public List<GroupToExport> ExportingGroups { get; set; }
}
View:
#using (Html.BeginForm("Export", "ElmahGroup", FormMethod.Post, new { id = "toExportForm" }))
{
//some divs
<input type="submit" id="js-export-submit" value="Export" />
#foreach (var item in Model.ExportingGroups)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => item.ToExport)
</td>
</tr>
}
//some divs
}
Controller:
public ActionResult Export(GroupsToExport model)
{
var groupsToExport = model.ExportingGroups.Where(x => x.ToExport).Select(x => x);
throw new System.NotImplementedException();
}
After submit "ToExport", in Controller, every group always has value 'false'. Even if all groups are checked.
Can somebody help me? What I'm doing wrong?
You cannot use a foreach loop to generate controls for a collection. The html you're generating for each checkbox (and for the associated hidden input) is <input type="checkbox" name="item.ToExport" .../>. Your model does not contain a property which is named item.
Use a for loop
#for(int i = 0; i < Model.ExportingGroups.Count; i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ExportingGroups[i].ToExport)
</td>
</tr>
}
Now your HTML will be
<input name="ExportingGroups[0].ToExport" .../>
<input name="ExportingGroups[1].ToExport" .../>
etc. which will correctly bind to your model
Edit
Alternatively you can use a custom EditorTemplate for typeof GroupToExport. Create a partial view /Views/Shared/EditorTemplates/GroupToExport.cshtml
#model yourAssembly.GroupToExport
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ToExport)
</td>
</tr>
And then in the main view
#Html.EditorFor(m => m.ExportingGroups)
The EditorFor() method will generate the correct html for each item in your collection based on the template.
You are using Incorrect syntax to Map the values back when they are posted, since the checked value of a checkbox is initialised to false by default, that is the reason why it is always false,use sysntax
#for(int i = 0; i < Model.ExportingGroups.Count(); i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => Model.ExportingGroups[i].ToExport)
</td>
</tr>
}
//some divs
This should map back all values you are looking for.
I found this works much better: Leave the foreach loop as is (do not user a counter)
#foreach (var item in Model.GroupToExport)
{
Then use this Razor format to display a checkbox
#Html.CheckBox("Active", #item.ToExport)
Simple to use and does not make you change the typical foreach loop.
I am learning how to apply ViewModels to ASP.NET MVC. I'm unsure how to be able to access "specific" properties of the ViewModel from within the View. I've got a ViewModel containing a whole bunch of properties like this:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<ProductCategory> ProductCategories { get; set; }
public IEnumerable<ProductDescription> ProductDescriptions { get; set; }
}
Now, previously when I had a "listing" type page I could simply do this in the View (as can be seen, I'm accessing two different properties here, Products and ProductDescriptions):
#for (int i = 0; i < Model.Products.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(m => Model.Products.ElementAt(i).Name)
</td>
<td>
#Html.DisplayFor(m => Model.Products.ElementAt(i).ProductID)
</td>
<td>
#Html.DisplayFor(m => Model.ProductDescriptions.ElementAt(i).Description)
</td>
}
However I'm now totally stuck on implementing a "details" like page where I only need to access specific records For example:
#Html.DisplayFor(m => m.Products.ProductID)
Does not compile because ""Model.Products" does not contain a definition for "ProductID"". However, the Products model does have a ProductID or else the "listing" page code would not compile.
How do I fix this?
Erik Funkenbusch answered your problem, but an easy way to iterate a Collection is like this:
#foreach (var product in Model.Products)
{
<tr>
<td>
#Html.DisplayFor(m => product.ProductID)
</td>
</tr>
}
I am using MVC4 with SimpleMembership. MVC has setup my UserProfile table and I've modified the Registration View to accomodate my layout. Everything worked great until I determined that I wanted to include some additional information from my user that best fit within the context of another already existing Model.
Typically my approach to passing more than one Model from my View to the Controller has been to employ Tuples. Historically this has worked out great for me and I've never had any real issues until now.
My Registration Form resembles this:
#model Tuple<MyNamespace.Models.RegisterModel,MyNamespace.Models.MembershipDetail>
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.AntiForgeryToken()
<table style="border-collapse: collapse; border-spacing:0px; border-width:0px; margin: 0px; width:100px; padding: 0 0 0 0px; background-color:#2e2e2e;">
<tr>
<td>
Profile Name
</td>
<td>
#Html.TextBoxFor(m => m.Item2.ProfileName)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Item1.UserName)
</td>
<td>
#Html.TextBoxFor(m => m.Item1.UserName)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Item1.Password)
</td>
<td>
#Html.PasswordFor(m => m.Item1.Password)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Item1.ConfirmPassword)
</td>
<td>
#Html.PasswordFor(m => m.Item1.ConfirmPassword)
</td>
</tr>
<tr>
<td>
<button type="submit" id="btnSubmitForm" value="Register">Register</button>
</td>
</tr>
</table>
#Html.Partial("_ValidationSummary",ViewData.ModelState)
}
The Register Method of my Controller is similar to this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model, MembershipDetail member)
{
if (ModelState.IsValid)
{
try
{
// Do something with the data
}
catch(MembershipCreateUserException e)
{
ModelState.AddModelError("",ErrorCodeToString(e.StatusCode));
}
}
return View(model);
}
Whenever I debug this code after the click of the Submit button, I note that both Models returned to the controller are empty. The contained fields are either null, 0 or Empty Strings and I cannot figure out why.
To add to this mystery, if I remove the tuple from the top of the view and re-assign it as a single Model as such:
#model MyNamespace.Models.RegisterModel
And replace the tuple references from the code (e.g. m.Item1.Property, m.Item2.Property) with the 1 Model inferred reference (m.Property and etc); then the data passed in from the TextBox helpers is appropriately assigned to the model and the model is populated when I debug the code.
Now, I know I could simply add a few other fields to the UserProfile table and use them in my Model to alleviate the Tuple altogether but then I am duplicating data in my schema which is not an ideal solution. And keeping in mind that though my sample code provided here only contains 1 element of the 2nd Model, it will actually be more than 1.
So why don't the Models get populated when using the Tuple and is there a more appropriate way to go about solving this problem? Or can I not mix Model data on a single form submission???
First of all , it is not a good practice to use Tuple as model, because it is hard to read the code. You should write page's own viewmodels for that.
For this example like :
public class SomeViewModel{
public RegisterModel RegisterModel { get; set;}
public MembershipDetail MembershipDetail {get; set;}
}
it is easy to implement and will work as you want.
However if you want to use Tuples on the view as model.
You should get Your post action like
public ActionResult Register(Tuple<RegisterModel,MembershipDetail> model)
{
.... // do something..
}
Because the form html will be created like :
<input name="Item1.ProfileName" />
You can see , it will try to send Item1.Profile name which is not supported by default model binder to convert it to your own classes.. it will expect a Tuple or a class like :.
public class TheClass {
public RegisterModel Item1 {get; set;}
public MemberhipDetail Item2 {get; set;}
}
which basically just like the viewmodel
I use asp net MVC 3 one of my project.I use partial view for my coding. I want to list all customers in a list and submit their information as a list. When I try to submit my list in post back, it sends my list is null. You can find my code as in the below:
My controller method is:
[HttpPost]
public ActionResult ConfirmUsers(ICollection<Career.DomainModel.UserApprovalDto> collection)
{
string bas = "";
//if (collection != null)
if (ModelState.IsValid)
{
bas = "bas";
}
return RedirectToAction("Index");
}
My partial view is:
#model List<Career.DomainModel.UserApprovalDto>
#using (Html.BeginForm("ConfirmUsers", "ManageUsers", new { area = "" }, FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Is Reported
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].FirstName)
</td>
<td>
#Html.CheckBox("IsReported", Model[i].IsReported.HasValue ? Model[i].IsReported.Value : false)
#*#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);*# #* #if (Model[i].IsReported != null)
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}
else
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}*#
</td>
<td>
</td>
</tr>
}
</table>
<div>
<input name="submitUsers" type="submit" value="Save" />
</div>
}
Thanks in advance.
Kerem
I would use Editor template to handle this. Have your View Model like this to represent the CheckBox item.
public class ReportedUserViewModel
{
public string FirstName { set;get;}
public int Id { set;get;}
public bool IsSelected { set;get;}
}
Now in yout main view model, add a property which is a collection of the above class
public class ConfirmUserViewModel
{
public List<ReportedUserViewModel> ReportedUsers{ get; set; }
//Other Properties also here
public ConfirmUserViewModel()
{
ReportedUsers=new List<ReportedUserViewModel>();
}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult ConfirmUser()
{
var vm = new ConfirmUserViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.ReportedUsers.Add(new ReportedUserViewModel { Name = "Test1" , Id=1});
vm.ReportedUsers.Add(new ReportedUserViewModel { Name = "Test2", Id=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called EditorTemplate and Create a new View there with the same name as of the Property Name(ReportedUsers.cshtml)
Add this code to the newly created editor template.
#model ReportedUserViewModel
<p>
<b>#Model.FirstName </b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model ConfirmUserViewModel
#using (Html.BeginForm())
{
<div>
#Html.EditorFor(m=>m.ReportedUsers)
</div>
<input type="submit" value="Submit" />
}
Now when You Post the Form, Your Model will have the ReportedUsers Collection where the Selected Check boxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult AddAlert(ConfirmUserViewModel model)
{
if(ModelState.IsValid)
{
//Check for model.ReportedUsers collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}
With the code you wrote, MVC model binder mechanism does not know how to map those inputs into List of object.
Do this little trick instead:
#Html.CheckBox("[" + i + "]." + "IsReported", Model[i].IsReported.Value);
This will result the name of input field as [0].IsReported for the first item in the list, [1].IsReported for next item.
That should work.
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
I'm a little late to the party, but it is easy enough to just refer to the list if you embed it in the model--
#Html.CheckBoxFor(modelItem => Model.List[i].Selected)
That will post back for each item in the iterator.
#Html.CheckBox("[" + i + "]." + "IsReported", Model[i].IsReported.Value);
worked perfectly for me.
Make sure your post method contains parameter for the list.
e.g public ActionResult Index(ConfigViewModel model, List configurationList)
Here in my case I have one view model (model) which contains list object.
If you specify view model in your post method as it is then you will get null value for the list (here model object is null). But if you add specific list parameter (configurationList) in the action then you can get all of the list values in the controller.
i run into same problem last week.
I realize that checkbox has three value(true/false/null) when i get it from database because i let the checkbox value nullable when i desingned database. i redesinged db and the problem was solved.
you didnt post models so i dont realy sure if this is the case. just look at the model, if it's writing Nullable above your Ischeck property, go to database and redesign. remember isCheck, isSelected properties have to have just two values(true/false).