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.
Related
I am using visual studio 2021 and asp.net core, using controller and client side as cshtml file I have complex model with object as propery and one of them not pass to my controller on using #Html.HiddenFor(m => m.testToCommercialEntity.Test) it is not pass the Test property but,
#Html.HiddenFor(m => m.testToCommercialEntity) it is pass the testToCommercialEntity property
and #Html.EditorFor(m => m.testToCommercialEntity.Test) it is pass the Test property!
I have no Idea what to do.. I dont want to print the Test to the client and #Html.EditorFor print it all to the screen
I put only the relevant code
I have Models of :
public class CompanyTestDetailsModule
{
public Test_To_CommercialEntity testToCommercialEntity { get; set; }
public Test Test { get { return testToCommercialEntity.Test; } }
public bool IsAutoTest
{
get
{
return this.Test.TestType == BL.Enums.TestTypeGroup.Auto || this.Test.TestType == BL.Enums.TestTypeGroup.RelationType;
}
}
}
public class Test_To_CommercialEntity
{
public Test Test { get; set; }
}
public class Test
{
public TestTypeGroup TestType { get; set; }
}
My cshtml file look like that :
#model CompanyTestDetailsModule;
<form class="formEntity" asp-controller="Digital" asp-action="EditAction" autocomplete="off">
#Html.DisplayFor(m => m.testToCommercialEntity.Test.Id);
#Html.DisplayFor(m => m.testToCommercialEntity.Test.TestName);
#Html.HiddenFor(m => m.testToCommercialEntity)
#Html.HiddenFor(m => m.OsekMursheKYCModule)
#Html.HiddenFor(m => m.testToCommercialEntity.Test)
#Html.HiddenFor(m => m.Test)
<div class="form-group">
<input type="submit" value="שמירה" class="btn btn-success" />
</div>
</form>
as you can see you can the page is printing the
#Html.DisplayFor(m => m.testToCommercialEntity.Test.Id);
#Html.DisplayFor(m => m.testToCommercialEntity.Test.TestName);
correctly
When I run the action I got the next Exception :
System.NullReferenceException: 'Object reference not set to an instance of an object.'
CertificateSystem.DB.Models.CompanyTestDetailsModule.Test.get returned null.
for another example, when I change my cshtml file
#Html.DisplayFor(m => m.testToCommercialEntity.Test.Id);
#Html.DisplayFor(m => m.testToCommercialEntity.Test.TestName);
#Html.HiddenFor(m => m.testToCommercialEntity)
#Html.HiddenFor(m => m.OsekMursheKYCModule)
#Html.EditorFor(m => m.testToCommercialEntity.Test)
<div class="form-group">
<input type="submit" value="שמירה" class="btn btn-success" />
</div>
And run the action It pass the Test object correctly
for example :
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 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.
I am having some trouble. I have a view as below
#model IEnumerable<Tipstr.Models.Posts>
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial")
</div>
<div class = "rightContent">
#foreach (var item in Model) {
<h1 class ="ptitle">
#Html.DisplayFor(modelItem => item.title)
</h1>
<p class ="date">
posted #Html.DisplayFor(modelItem => item.date)
</p>
<p class ="post">
#Html.DisplayFor(modelItem => item.body)
</p>
<hr />
}
</div>
</div>
<div class="Cpad">
<br class="clear" /><div class="Cbottom"></div><div class="Cbottomright">
</div>
</div>
And I have a partial, _LeaderboardPartial, as shown
#model IEnumerable<Tipstr.Models.Leaderboard>
<table id="pattern-style-a" summary="Meeting Results">
<thead>
<tr>
<th scope="col">Username</th>
<th scope="col">Tipster Score</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.userName)</td>
<td> #Html.DisplayFor(modelItem => item.score)</td>
</tr>
}
</tbody>
</table>
I can't seem to get it to work I think it has something to do with passing in one model from the layout and then another from the partial. How can I get around this? I get the following error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[Tipstr.Models.Posts]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[Tipstr.Models.Leaderboard]'.
As #David Tansey said, you can have a view model that contains a reference to the two types
public class MyViewModel
{
public MyViewModel(IEnumerable<Posts> posts,
IEnumerable<Leaderboard> leaderboard)
{
//you can add null checks to ensure view model invariants
this.Posts = posts;
this.Leaderboard = leaderboard;
}
public IEnumerable<Posts> Posts { get; private set; }
public IEnumerable<Leaderboard> Leaderboard{ get; private set; }
}
And in your main view you will call Html.Partial like this
#model MyViewModel
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial", this.Model.Leaderboard)
</div>
....
If you do this, you should change the foreach to iterate through Model.Posts instead of Model
#foreach (var item in this.Model.Posts)
This line of code:
#Html.Partial("_LeaderboardPartial") in the first view implies sending along its own model IEnumerable<Tipstr.Models.Posts> to the partial.
The partial expects a reference to type IEnumerable<Tipstr.Models.Leaderboard>.
Perhaps you need a view model that contains a reference to one of each of these types?
I have a SQL Server 2012 in which I have AWARD table with two columns TITLE and MONTH. TITLE is varchar(256) and cannot be NULL. MONTH is int and can be NULL.
With VS2012 Ultimate and EF 5.0.0, the TextBoxFor helper in MVC4 app is not producing validation (data-val="required" and data-val-required="required message") for the TITLE columne above, but in the same View, MONTH is getting the correct validation markup. The .edmx designer does show TITLE is NonNullable, BUTT, the automatically generated AWARD.cs file does not have the [Required]
attribute for the TITLE column.
What can I try?
#model MyProject.Models.AWARD
#{
ViewBag.Title = "Add Award";
Layout = "~/Views/Shared/_EditorLayout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Add Award</legend>
<table>
<tr>
<td>
#Html.LabelFor(model => model.TITLE)
</td>
<td>
#Html.TextAreaFor(model => model.TITLE)
<br />#Html.ValidationMessageFor(model => model.TITLE)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(model => model.MONTH)
</td>
<td>#Html.DropDownListFor(model => model.MONTH, new SelectList(MyProject.Models.Helpers.Months, "key","value"), "[Not Selected]")
<br />#Html.ValidationMessageFor(model => model.MONTH)
</td>
</tr>
<tr>
<td>
<input type="submit" value="Add" />
</td>
<td>
#Html.ActionLink("Cancel", "Index", null, new { #class = "cancel-button" })</td>
</tr>
</table>
</fieldset>
}
You shouldn't really be binding your views directly to your data mapping entities. You should create view model classes to wrap the data you pass to and from your view and then populate your data objects from the controller.
You can then perform the required validation on your view model without affecting your generated mapping classes.
Model
public class AwardViewModel
{
[Required, StringLength(30)]
public string Title { get; set; }
....
}
View
#model AwardViewModel
#using (Html.BeginForm()) {
#Html.EditorFor(m => m.Title)
#Html.ValidationMessageFor(m => m.Title)
...
}
Controller
[HttpPost]
public ActionResult Create (AwardViewModel model)
{
/* Create new AWARD in entity context, populate
with relevant fields from model and commit. */
}