Impossible to display editor template for nested IEnumerable - c#

I want to display an Article with several fields in my view but it doesn't work right: the fields doesn't show (only the header (fields' names) does).
Article class:
public class Article : IComparable
{
public IEnumerable<ArticleField> Fields
{
get;
set;
}
[More properties...]
}
ArticleField class:
public class ArticleField
{
// !! I want this display in a table Header !!
[Display(Name = "Name", ResourceType = typeof(Resources.Classes.ArticleField))]
[Required(ErrorMessageResourceName="required", ErrorMessageResourceType=typeof(Resources.Common.Validation))]
public string Name
{
get;
set;
}
[More properties...]
}
Index.cshtml (appear fine to rendered html)
#model MygLogWeb.Classes.Article.Article
#using (Html.BeginForm("Index", "Article", FormMethod.Post))
{
#Html.AntiForgeryToken()
<table class="table">
<tbody>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<td>
#if (Model.IsSystem)
{
#Html.HiddenFor(model => model.Name)
#Model.Name
}
else
{
#Html.EditorFor(model => model.Name)
}
</td>
</tr>
<tr>
<th>
#Html.DisplayNameFor(model => model.IsSystem)
</th>
<td>
#Html.HiddenFor(model => model.IsSystem)
#Model.IsSystem
</td>
</tr>
</tbody>
</table>
<br style="margin: 0.5em 0em;"/>
#* POINT OF INTEREST *#
#Html.EditorFor(model => model.Fields, "ArticleFields")
<button type="submit" name="button" value="save">#Resources.Common.Buttons.save</button>
}
EditorTemplates/ArticleFields.cshtml (appear fine to rendered html)
#model IEnumerable<MygLogWeb.Classes.Article.ArticleField>
#using MygLogWeb.Classes.Article
#{
var uid = Guid.NewGuid().ToString();
var GlobalViewBag = ViewContext.Controller.ViewBag;
GlobalViewBag.ItemCount = Model.Count();
}
<table class="table none" id="#uid">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.IsSystem)
</th>
<th>
#Html.DisplayNameFor(model => model.IsVirtual)
</th>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Formula)
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>
#* POINT OF INTEREST *#
#Html.EditorFor(model => model)
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<input type="button" value="#Resources.Common.Buttons.add" data-row-add="ArticleField" data-row-addto="##uid > TBODY" />
</td>
</tr>
</tfoot>
</table>
<script type="text/javascript">
new TableEdit("#uid");
</script>
EditorTemplates/ArticleFields.cshtml (doesn't appear at all to rendered html)
#model MygLogWeb.Classes.Article.ArticleField
xxx
I though I could make public IEnumerable<ArticleField> Fields a List<T> but then I'll get an issue with the headers. Code such as #Html.DisplayNameFor(model => model.Formula) wouldn't work.

Instead of
#Html.EditorFor(model => model)
You must use an element of the collection
#Html.EditorFor(model => model[0])
or for loop over elements
#foreach (var item in Model)
{
#Html.EditorFor(model => item)
}
If you are planning to post back with the same model, then you'll want to use a List rather than an IEnumerable in order for the default model binder to create your objects in the post

Convert your model to a list, iterate it and use Editor instead of EditorFor. Something along this line:
foreach(var entry in Model.ToList())
{
Editor(entry, "ArticleFieldsItem");
}
And then create another template:
#model MygLogWeb.Classes.Article.ArticleField
<td>#Html.DisplayFor(model => model.IsSystem)</td>
<td>#Html.DisplayFor(model => model.IsVirtual)</td>
<td>#Html.DisplayFor(model => model.Name)</td>
<td>#Html.DisplayFor(model => model.Formula)</td>

I eventually did what I didn't want to, I completely stopped having IEnumerable properties in my models and use Arrays or Lists instead.
Then I of course could use for loops (not foreach ones) with [index].

Related

Why parameter of the action method is null when view is posted?

This is an ASP.NET MVC project. I have a view that displays a collection of BookRentViewModel items.
When I click on the button in the view, the view is posted to an action method called rent. rent action method has a parameter called items of type IEnumerable<BookRentViewModel>.
Here is the code of the view:
#model IEnumerable<DaramSerl.Models.ViewModels.BookRentViewModel>
#using (Html.BeginForm("rent", "RentBook", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.bookName)
</th>
<th>
#Html.DisplayNameFor(model => model.isRented)
</th>
<th>
#Html.DisplayNameFor(model => model.startDate)
</th>
<th>
#Html.DisplayNameFor(model => model.endDate)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.EditorFor(modelItem => item.bookName)
</td>
<td>
#Html.EditorFor(modelItem => item.isRented)
</td>
<td>
#Html.EditorFor(modelItem => item.startDate)
</td>
<td>
#Html.EditorFor(modelItem => item.endDate)
</td>
</tr>
}
</table>
<input type="submit" value="Create" class="btn btn-primary" />
}
And here is the action method:
[HttpPost]
public async Task<ActionResult> rent(IEnumerable<BookRentViewModel> items)
{
if (ModelState.IsValid)
{
return RedirectToAction("/Index");
}
return View();
}
And here is the BookRentViewModel:
public class BookRentViewModel
{
[Key]
public int bookId{ get; set; }
public string bookName { get; set; }
public bool isRented { get; set; }
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
}
The problem is that items parameter is always null when rent action is triggered.
Any idea why items is null and does not get the collection from the view?
UPDATE
I used fiddler to see if the collection is posted to the server but it seems it cannot be parsed into BookRentViewModel items.
UPDATE3
Here is screenshot from fiddler with the collection sent from the view:
Doesnt look like you're setting a value for public int bookId - if thats the case this will fail validation as its a non-nullable type.
Either way, set a breakpoint in your controller method and check the errors on ModelState - see Get error message if ModelState.IsValid fails?
Edit
To include properties but not show them on your view, use the hidden field razor tag helper:
#Html.HiddenFor(x => x.bookId)
I tried reproduce source code.
You can change model mapping in cshtml file as below
#for (int i = 0; i < Model.Count();i++ )
{
<tr>
<td>#Html.TextBox("items[" + #i + "].bookName",
Model.ElementAt(i).bookName
)</td>
<td>#Html.CheckBox("items[" + #i + "].isRented",
Model.ElementAt(i).isRented
)</td>
<td>#Html.TextBox("items[" + #i + "].startDate",
Model.ElementAt(i).startDate
)</td>
<td>#Html.TextBox("items[" + #i + "].endDate",
Model.ElementAt(i).endDate
)</td>
</tr>
}
rent.cshtml file
#model IEnumerable<WebApplication2.Controllers.BookRentViewModel>
#using (Html.BeginForm("rent", "Rent", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.bookName)
</th>
<th>
#Html.DisplayNameFor(model => model.isRented)
</th>
<th>
#Html.DisplayNameFor(model => model.startDate)
</th>
<th>
#Html.DisplayNameFor(model => model.endDate)
</th>
</tr>
#*#foreach (var item in Model)
{
<tr>
<td>
#Html.EditorFor(modelItem => item.bookName)
</td>
<td>
#Html.EditorFor(modelItem => item.isRented)
</td>
<td>
#Html.EditorFor(modelItem => item.startDate)
</td>
<td>
#Html.EditorFor(modelItem => item.endDate)
</td>
</tr>
}*#
#for (int i = 0; i < Model.Count();i++ )
{
<tr>
<td>#Html.TextBox("items[" + #i + "].bookName",
Model.ElementAt(i).bookName
)</td>
<td>#Html.CheckBox("items[" + #i + "].isRented",
Model.ElementAt(i).isRented
)</td>
<td>#Html.TextBox("items[" + #i + "].startDate",
Model.ElementAt(i).startDate
)</td>
<td>#Html.TextBox("items[" + #i + "].endDate",
Model.ElementAt(i).endDate
)</td>
</tr>
}
</table>
<input type="submit" value="Create" class="btn btn-primary" />
}

TS1005 and TS1002 in first string

I read about these warnings but I did not manage to solve the problem.
VS have last update.
TS1002 (JS) ',' expected.
TS1005 (JS) Unterminated string literal.
TS1002 (JS) ',' expected.
TS1005 (JS) Unterminated string literal.
these warnings were written 2x twice as I wrote (I don't understand what it means)
file type - ViewAll.cshtml
#model IEnumerable<WebSQLserver.Models.Employee>
#{
ViewBag.Title = "ViewAll";
Layout = null;
}
<table class="table table-striped table-condensed" id="employeeTable">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Position)
</th>
<th>
#Html.DisplayNameFor(model => model.Office)
</th>
<th>
#Html.DisplayNameFor(model => model.Salary)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Position)
</td>
<td>
#Html.DisplayFor(modelItem => item.Office)
</td>
<td>
#Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
<a class="btn btn-primary btn-sm" onclick="Edit('#Url.Action("AddOrEdit", "Employee", new { id=#item.EmployeeID})')"><i class="fa fa-pencil fa-lg"></i></a>
<a class="btn btn-danger btn-sm" onclick="Delete('#Url.Action("Delete", "Employee", new { id=#item.EmployeeID})')"><i class="fa fa-trash fa-lg"></i></a>
</td>
</tr>
}
</tbody>
</table>
file type Controller.cs
namespace WebSQLserver.Controllers
{
public class EmployeeController : Controller
{
// GET: Employee
public ActionResult Index()
{
return View();
}
public ActionResult ViewAll()
{
return View(GetAllEmployee());
}
IEnumerable<Employee> GetAllEmployee()
{
using(DBModel db = new DBModel())
{
return db.Employees.ToList<Employee>();
}
}

Update mutiple records in database MVC 5 EF

Good evening,
Now I'll describe my situation:
Here is my Model:
[Table("Items")]
public class Item
{
[Key]
public string Id { get; set; }
public DateTime Generated { get; set; }
public string Content { get; set; }
public string UrlSeo { get; set; }
public bool IsActive { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
I;m implementing CRUD operations to DB records, but I need implement one more Action, I need to Update for example UrlSeo property in selected records in one action.
So here is my view:
#model IEnumerable<CheckBoxes.Models.Item>
#{
ViewBag.Title = "Items";
}
<dv class="page-header">
<h3>Items</h3>
</dv>
<p>
#Html.ActionLink("Create New", "Create")
</p>
#using (Html.BeginForm("ChangeUrl", "Items", FormMethod.Post))
{
<div class="row">
<div class="form-group-sm">
<div class="col-md-4">
#Html.TextBox("urlSeo", null, new { #class = "form-control" })
</div>
<div class="col-md-offset-2 col-md-4">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
<p>
</p>
</div>
<table class="table">
<tr>
<th>
<input type="checkbox" id="checkAll" />
</th>
<th>
#Html.DisplayNameFor(model => model.Generated)
</th>
<th>
#Html.DisplayNameFor(model => model.Content)
</th>
<th>
#Html.DisplayNameFor(model => model.UrlSeo)
</th>
<th>
#Html.DisplayNameFor(model => model.IsActive)
</th>
<th>
#Html.DisplayNameFor(model => model.Quantity)
</th>
<th>
#Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<th>
<input type="checkbox" class="checkBox" value="#item.Id" />
</th>
<td>
#Html.DisplayFor(modelItem => item.Generated)
</td>
<td>
#Html.DisplayFor(modelItem => item.Content)
</td>
<td>
#Html.DisplayFor(modelItem => item.UrlSeo)
</td>
<td>
#Html.DisplayFor(modelItem => item.IsActive)
</td>
<td>
#Html.DisplayFor(modelItem => item.Quantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
}
#section Scripts{
#Scripts.Render("~/bundles/toastr")
<!--მასიური წაშლა-->
<script>
$(document).ready(function () {
$("#checkAll").click(function () {
$(".checkBox").prop('checked',
$(this).prop('checked'));
});
});
</script>
}
And Finally My Controller:
[HttpPost]
public async Task<ActionResult> ChangeUrl(string urlSeo, string[] id)
{
foreach (var itemId in id)
{
Item item = await db.Items.FindAsync(itemId);
item.UrlSeo = urlSeo;
db.Entry(item).State = EntityState.Modified;
db.Entry(item).Property(x => x.Generated).IsModified = false;
db.Entry(item).Property(x => x.Content).IsModified = false;
db.Entry(item).Property(x => x.IsActive).IsModified = false;
db.Entry(item).Property(x => x.Price).IsModified = false;
db.Entry(item).Property(x => x.Quantity).IsModified = false;
}
await db.SaveChangesAsync();
return Json(new { success = true });
}
Now Question
How to pass all checked items to controller and input from field.
I home someone will help me.
Where was problem in view here is necessary give name for checkbox, witch equals part of controller.
#foreach (var item in Model)
{
<tr>
<th>
<input type="checkbox" class="checkBox" value="#item.Id" name="id" />
</th>
Everything worked fine, yes one more thing, better to use:
return RedirectToAction("Index");
So this is working solution.

submit only checked items

I'm trying to submit only these rows which is checked. I'm using this jquery plungin and my table looks same as is in link
#model IEnumerable<BillBox.ACD_UNI_STUDENTS>
<table class="table tblSelect">
<tr>
<th>
<input type="checkbox" id="checkall" title="Select all" />
</th>
<th>
#Html.DisplayNameFor(model => model.FIRST_NAME)
</th>
<th>
#Html.DisplayNameFor(model => model.LAST_NAME)
</th>
<th>
#Html.DisplayNameFor(model => model.PERSONAL_NUMBER)
</th>
<th>
#Html.DisplayNameFor(model => model.ACD_UNI_DEGREES.DEGREE)
</th>
<th>
#Html.DisplayNameFor(model => model.ACD_UNI_FACULTIES.FACULTY)
</th>
<th>
#Html.DisplayNameFor(model => model.ACD_UNI_SEMESTERS.SEMESTER)
</th>
<th>
#Html.DisplayNameFor(model => model.ACD_UNI_SPECIALIZATIONS.SPECIALIZATION)
</th>
<th>
#Html.DisplayNameFor(model => model.COR_PAYER_STATUS.NAME)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
<input type="checkbox"/>
</td>
<td>
#Html.DisplayFor(modelItem => item.FIRST_NAME)
</td>
<td>
#Html.DisplayFor(modelItem => item.LAST_NAME)
</td>
<td>
#Html.DisplayFor(modelItem => item.PERSONAL_NUMBER)
</td>
<td>
#Html.DisplayFor(modelItem => item.ACD_UNI_FACULTIES.FACULTY)
</td>
<td>
#Html.DisplayFor(modelItem => item.ACD_UNI_SEMESTERS.SEMESTER)
</td>
<td>
#Html.DisplayFor(modelItem => item.ACD_UNI_SEMESTERS.SEMESTER)
</td>
<td>
#Html.DisplayFor(modelItem => item.ACD_UNI_SPECIALIZATIONS.SPECIALIZATION)
</td>
<td>
#Html.DisplayFor(modelItem => item.COR_PAYER_STATUS.NAME)
</td>
</tr>
}
</table>
Now I have a question. How can I retrieve only these rows which checkbox is checked?
that's my controller
public PartialViewResult AllStudent()
{
var students = (from q in db.ACD_UNI_STUDENTS
select q).ToList();
return PartialView(students);
}
there are many rows (from db) so I can't do it with formcollection. I can't retrieve their names
You may submit the selected ids as IEnumerable and then filter them in your controller! Here is an example
<form action="url" method="post">
...
#foreach (var item in Model)
{
<tr>
<td>
<input type="checkbox" name="Objs[]" id="Objs[]" value="#item.UNIQUE_ID"/>
</td>
<td>
#Html.DisplayFor(modelItem => item.FIRST_NAME)
</td>
</tr>
}...
</form>
and your controller
[HttpPost]
public PartialViewResult AllStudent(IEnumerable<long> Objs)
{
var students = (from q in db.ACD_UNI_STUDENTS
where Objs.Contains(q.UNIQUE_ID)
select q).ToList();
return PartialView(students);
}
...
I'd suggest not to use the Database Model for the View directly. You should have a Model to bind to the Rows, which might look like:
public class StudentRowViewModel
{
public bool IsChecked { get; set; }
public int StudentId { get; set; }
// ... More columns, whatever you want to display
public string Name { get; set; }
}
When populating the View, you select StudentRowViewModels like this:
db.ACD_UNI_STUDENTS.Select(x => new StudentRowViewModel { StudentId = x.UNIQUE_ID, Name = ... });
Or whatever applies to your DataBase Model.
In your View, your Checkbox will also Bind to the Model:
#Html.CheckboxFor(x => x.IsChecked)
Finally, when the Form is submitted, you can select only the checked Items:
public ActionResult AllStudents(IEnumerable<StudentRowViewModel> model)
{
var checked = model.Where(x => x.IsChecked).Select(x => x.StudentId).ToList();
var items = db.ACD_UNI_STUDENTS.Where(x => checked.Contains(x.UNIQUE_ID));
}
You get the Idea?
First put a class and value attribute in your checkbox :
<input type="checkbox" class="myCheckBox" value="#item.PERSONAL_NUMBER" />
Then i advise you to use a JQuery script to detect which checkbox are checked :
I suppose that you have a button to do another action :
$(document).ready(function () {
$('#myButton').click(function() {
var id = '';
$('.myCheckBox:checked').each(function (e) {
id += $(this).val() + ';'
}
var url = '/ControllerName/ActionResultName';
$.ajax({
url:url,
cache: false,
type: 'POST',
data: {
Id: id
},
succes: function(data){
$('.myCheckBox').attr('checked',false);
location.reload();
}
})
}
});
After that you can get in your controller all the personnal numbers in your controller :
put a string parameter 'id' to your actionresult.
id.TrimEnd(';')

Populate form from table select with asp.net mvc

I have a table with data, how can I populate a form on the same page with the data when the edit button is clicked. Basically is should be the same as this example but without using knockoutjs
http://jsfiddle.net/jiggle/2cr2f/
#model IEnumerable<GenomindApp2.Areas.RulesEngine.ViewModels.GeneViewModel>
#{
ViewBag.Title = "Index2";
}
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.GeneValue)
</th>
<th>
#Html.DisplayNameFor(model => model.GeneCode)
</th>
<th>
#Html.DisplayNameFor(model => model.GeneName)
</th>
<th>
#Html.DisplayNameFor(model => model.GeneComments)
</th>
<th>
#Html.DisplayNameFor(model => model.WildType)
</th>
<th>
#Html.DisplayNameFor(model => model.WildTypeAllele)
</th>
<th>
#Html.DisplayNameFor(model => model.AtRiskAllele)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.GeneCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.GeneName)
</td>
<td>
#Html.DisplayFor(modelItem => item.GeneComments)
</td>
<td>
#Html.DisplayFor(modelItem => item.WildType)
</td>
<td>
#Html.DisplayFor(modelItem => item.WildTypeAllele)
</td>
<td>
#Html.DisplayFor(modelItem => item.AtRiskAllele)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { }) |
#Html.ActionLink("Details", "Details", new { }) |
#Html.ActionLink("Delete", "Delete", new { })
</td>
</tr>
}
</table>
You should do it like that:
Model:
public class ModelB
{
public int Age { get; set; }
public string Name { get; set; }
}
Controller:
public ActionResult MyAction()
{
var model = new List<ModelB>
{
new ModelB{Age = 2, Name = "Bob"},
new ModelB{Age = 7, Name = "Sam"},
};
return View(model);
}
[HttpPost]
public ActionResult MyAction(List<ModelB> model)
{
//whatever
}
View:
#model List<TestWebApplication.Models.ModelB>
...
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
Age: #Html.EditorFor(modelItem => Model[i].Age);
Name: #Html.EditorFor(modelItem => Model[i].Name);
<br />
}
<input type="submit"/>
}
Please note that I used for instead of foreach. When you populate a form you shouldn't use foreach - it will not render well.

Categories

Resources