KendoUI listview passing IEnumerable<model> instead of model from editor template in MVC - asp.net-mvc

I've been puzzling over this for a few days now. Basically, I have a view model that contains three IEnumerables of other view models to be displayed in three separate Kendo controls - one as a ListView, and two as GridViews. Each view model has a separate editor template that is used by the corresponding control. The GridViews are working 100%, and the Kendo ListView is working properly on the page (the ListView refreshes with the updated data).
My problem is that an IEnumerable of the view model is being passed to the ActionResult (with a null value) instead of just a single instance of the view model, and the DataSourceRequest is empty.
Here is the (redacted) code.
The view models (just the wrapper and view model for the ListView in question):
public class MainPersonViewModel
{
public MainPersonViewModel(){}
public int PersonId { get; set; }
public IEnumerable<DetailsViewModel> PersonDetails { get; set; }
public IEnumerable<AddressViewModel> Addresses { get; set; }
public IEnumerable<PersonGroupingViewModel> MemberOf { get; set; }
}
public class DetailsViewModel
{
public DetailsViewModel(){}
public int PersonId { get; set; }
public string Name { get; set; }
public string WorkingTitle { get; set; }
}
The Kendo ListView template:
#model Staff.ViewModels.MainPersonViewModel
<script type="text/x-kendo-tmpl" id="personDetailsTemplate">
<table>
<tr>
<td>Name: </td><td>${Name}</td>
</tr>
<tr>
<td>Working Title: </td><td>${Title}</td>
</tr>
<tr>
<td colspan="2">
<div class="edit-buttons">
<a class="k=button k-button-icontext k-edit-button" href="\\#"><span class="k-icon k-edit"></span>Edit</a>
</div>
</td>
</tr>
</table>
</script>
And the Kendo control itself:
#(Html.Kendo().ListView<Staff.ViewModels.DetailsViewModel>(Model.PersonDetails)
.Name("personDetailsList")
.TagName("div")
.ClientTemplateId("personDetailsTemplate")
.Editable()
.DataSource(ds => ds
.Model(m =>
{
m.Id(f => f.PersonId);
m.Field(f => f.Name);
m.Field(f => f.WorkingTitle);
}
)
.Read(read => read.Action("ViewPersonDetails", "Staff", new {id = Model.PersonId}))
.Update(update => update.Action("UpdatePersonDetails", "Staff"))
)
)
The ActionResult called by the Listview in the controller is as follows:
public class StaffController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdatePersonDetails([DataSourceRequest] DataSourceRequest request, IEnumerable<DetailsViewModel> toUpdate)
{
//update code here
return Json(ModelState.ToDataSourceResult());
}
}
Finally the editor template (located in Views\Staff\EditorTemplates):
#model Staff.ViewModels.DetailsViewModel
<div>
<table>
<tr>
<td>#Html.LabelFor(m => m.NameFirst)</td>
<td>#Html.EditorFor(m => m.NameFirst)</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.NameLast)</td>
<td>#Html.EditorFor(m => m.NameLast)</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.Title)</td>
<td>#Html.EditorFor(m => m.Title)</td>
</tr>
<tr>
<td colspan="2">
<div class="edit-buttons">
<a class="k-button k-button-icontext k-update-button" href="\\#"><span class="k-icon k-update"></span>Save</a>
<a class="k-button k-button-icontext k-cancel-button" href="\\#"><span class="k-icon k-cancel"></span>Cancel</a>
</div>
</td>
</tr>
</table>
</div>
Since the ListView seems to be refreshing I don't think it's necessarily a problem with it or the DataSource on the page, so I'm leaning towards some detail (i.e. limitation) with the editor template that I'm missing. Any thoughts?
Sorry for the long post, and any help would be greatly appreciated!

Related

Input Tag Helper issue in ASP.NET

In ASP.NET view, shown below, I'm getting the error that is specific to line <input asp-for="StateName" />
Error:
'List<GrantsViewModel>' does not contain a definition for 'StateName'
NOTE: View is supposed to display different State Names in an HTML table column.
Controller:
public class DbTestController : Controller
{
private MyProjContext _context;
public DbTestController(MyProjContext context)
{
_context = context;
}
public IActionResult GrantNumbers()
{
var qryVM = from s in _context.StateNames
join g in _context.AnnualGrants on s.StateNumber equals g.StateNumber into sg
from r in sg.DefaultIfEmpty()
select new GrantsViewModel() { StateNumber = s.StateNumber,StateName= s.State, GrantNo= (r == null ? String.Empty : r.GrantNo), FiscalYear = (r == null ? 1900 : r.FiscalYear) };
return View(qryVM.ToList());
}
}
ViewModel:
public class GrantsViewModel
{
public int GrantNo_Id { get; set; }
public string StateNumber { get; set; }
public string StateName { get; set; }
public string GrantNo { get; set; }
public int FiscalYear { get; set; }
}
View:
#model List<MyProjet.Models.GrantsViewModel>
<form asp-controller="DbTest" asp-action="GrantNumbers" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.First().StateName)
</th>
<th>
#Html.DisplayNameFor(model => model.First().GrantNo)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
<input asp-for="StateName" />
</td>
<td>
#Html.DisplayFor(modelItem => item.GrantNo)
</td>
<td></td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
#Html.DisplayNameFor(model => model.StateName)
In this line you are trying to access the property StateName from the object referenced by model. Except model references an object of type List<T>, which does not have a property StateName.
To access StateName, you need to provide which element of the list you are accessing, such as the following (assuming you don't need to iterate, since you are just getting the column titles).
#Html.DisplayNameFor(model => model[0].StateName)
To reference the element correctly in the asp-for helper, use
<input asp-for="#item.StateName" />
You want to use FirstOrDefault() when referencing names of properties. This will still be able to get the display names using reflection even if your IEnumerable/List Model is empty.
#Html.DisplayNameFor(model => model.FirstOrDefault().StateName)
Instead of the StateName asp-tag, here is the Razor Helper for an input:
#Html.EditorFor(modelItem => item.StateName)
I'm a bit confused as to why you have this table in a form if you're wanting to just display your list of information. If you clarify, I can better answer based on your intent.
If you just want this data displayed, then use a DisplayFor() like you did for GrantNo column:
#Html.DisplayFor(modelItem => item.StateName)

Html.BeginForm call the right Action in Controller

There are a lot of topics related to this question but I still did't figured out what I'm doing wrong.
I have a database where I manage access of different users to folders. On my View the User can select Employees which should have access to certain folder. Then I want to pass the selected Employees to Controller, where the database will be updated.
My Problem is: The right Action in the Controller class didn't get invoked.(I have a breakpoint inside)
Here is the View
#model DataAccessManager.Models.EmployeeSelectionViewModel
#{
ViewBag.Title = "GiveAccessTo";
}
#using (Html.BeginForm("SubmitSelected", "FolderAccessController", FormMethod.Post, new { encType = "multipart/form-data"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.fr_folder_uid_fk)
<div class="form-horizontal">
<input type="submit" value="Save" id="submit" class="btn btn-default" />
<table id="tableP">
<thead>
<tr>
<th>Selection</th>
<th>Second Name</th>
<th>First Name</th>
<th>Department</th>
</tr>
</thead>
<tbody id="people">
#Html.EditorFor(model => model.People)
</tbody>
</table>
</div>
</div>
</div>
}
Here is the Controller reduced to the minimum
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SubmitSelected(EmployeeSelectionViewModel model)
{
return View();
}
More Details: I am not sure what is causing the problem, so here some more details.
The view is strongly typed to EmployeeSelectionViewModel, it represets the table with all Employees as a List here is the the code:
public class EmployeeSelectionViewModel
{
public List<SelectEmployeeEditorViewModel> People { get; set; }
public EmployeeSelectionViewModel()
{
this.People = new List<SelectEmployeeEditorViewModel>();
}
public Int64 fr_folder_uid_fk { get; set; }
public IEnumerable<string> getSelectedIds()
{
// Return an Enumerable containing the Id's of the selected people:
return (from p in this.People where p.Selected select p.fr_mavnr_fk).ToList();
}
}
The SelectEmployeeEditorViewModel represents one row of the table with all Employees.
public class SelectEmployeeEditorViewModel
{
public bool Selected { get; set; }
public string fr_mavnr_fk { get; set; }
public string firstName { get; set; }
public string secondName { get; set; }
public string dpt { get; set; }
}
And it has a View which create the checkboxes for each Employee
#model DataAccessManager.Models.SelectEmployeeEditorViewModel
<tr>
<td style="text-align:center">
#Html.CheckBoxFor(model => model.Selected)
#Html.HiddenFor(model => model.fr_mavnr_fk)
</td>
<td>
#Html.DisplayFor(model => model.secondName)
</td>
<td>
#Html.DisplayFor(model => model.firstName)
</td>
<td>
#Html.DisplayFor(model => model.dpt)
</td>
</tr>
The /FolderAccessController/SubmitSelected URL is called in the browser when I press the Submit button, but as mentioned the Action isn't invoked.
EDIT: I get the HTTP 404 not found error after pressing the button
Try removing the "Controller" word from your Html.BeginForm() second parameter, it's not needed.
#using (Html.BeginForm("SubmitSelected", "FolderAccess", FormMethod.Post, new { encType = "multipart/form-data"}))
Thiago Ferreira and haim770 Thanks a lot! The solution is to use the combination of your comments. So:
#using (Html.BeginForm("SubmitSelected", "FolderAccess", FormMethod.Post))
at the Controller

MVC model data that is rendered in the view is null when posted back

I have seen similar questions to this and followed the routine answer which is to ensure all model data is rendered in the HTML.
I have done that and the model is rendered in the view with #Html.HiddenFor() but when the posting back to the controller there are no items in the list ?
The view will happily render multiple items in the list, but List<Item> Items in the posted data is always an empty list (not null)
Model
public class ItemCollection
{
public List<string> AvailiableActions { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string SelectedAction { get; set; }
}
View
#model ItemCollection
#using (Html.BeginForm("myAction", "myController", FormMethod.Post))
{
<fieldset>
<div>
#Html.HiddenFor(m => Model.Items)
#Html.DisplayNameFor(x => x.AvailiableActions)
<table>
#{
foreach (var item in Model.Items)
{
#Html.HiddenFor(m => item)
#Html.HiddenFor(s => item.Id)
<tr>
<td>#item.Name</td>
<td>#Html.DropDownList(item.SelectedAction, new SelectList(Model.AvailiableActions))</td>
</tr>
}
}
</table>
</div>
</fieldset>
}
Controller
[HttpPost]
private ActionResult myAction(ItemCollection model)
{
if (model.Items.Count() == 0)
{
// this is true.. something is wrong......
}
}
You cannot use a foreach loop to render controls for a collection. It renders duplicate id and name attributes without the necessary indexers to bind to a collection. Use a for loop
for (int i = 0; i < Model.Items.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Items[i].Id)
#Html.DisplayFor(m => m.Items[i].Name)
</td>
<td>#Html.DropDownList(m => m.Items[i].SelectedAction, new SelectList(Model.AvailiableActions))</td>
</tr>
}
Note your view also included #Html.HiddenFor(m => Model.Items) and #Html.HiddenFor(m => item) which would also have failed because item is a complex object and you can only bind to value types. You need to remove both.
Instead of iterating over all items to make sure the index is added to the generated output, you may consider using EditorTemplates (an example on an other site).
EditorTemplates allow you to specify a template for a single Item in \Views\Shared\EditorTemplates\Item.cshtml:
#model Item
#{
var options= (List<string>)ViewData["Options"];
}
<tr>
<td>
#Html.HiddenFor(m => m.Id)
#Html.DisplayFor(m => m.Name)
</td>
<td>#Html.DropDownList(m => m.SelectedAction, new SelectList(options))</td>
</tr>
Then you may simply change your view to:
#model ItemCollection
#using (Html.BeginForm("myAction", "myController", FormMethod.Post))
{
<fieldset>
<div>
<table>
#Html.EditorFor(m => m.Items, new {Options = Model.AvailiableActions })
</table>
</div>
</fieldset>
}

Post Multiple Data from View to Controller MVC

I want to post quantity property to Controller (It's an edit action). I'm editing OrderedProductSet which is connected with ProductSet in my SQL Database (I get the name and price from there). How to pass multiple data from the view to controller? How to write method in controller class to receive the data (I'm asking about method arguments in this specific case).
My view:
#model Shop.Models.ProductViewModel#{
ViewBag.Title = "Edycja zamówienia";
}
<h2>Edycja zamówienie</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<table class="table">
<tr>
<th>
<b>Nazwa produktu</b>
</th>
<th>
<b>Cena</b>
</th>
<th>
<b>Ilość</b>
</th>
<th></th>
</tr>
#foreach (var item in Model.orderedProductSet)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProduktSet.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProduktSet.price)
</td>
<td>
#Html.EditorFor(model => item.quantity, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Potwierdź zmiany" class="btn btn-default" />
</div>
</div>
}
<div>
#Html.ActionLink("Powrót", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
My model (in separated classes of course):
public class ProductViewModel
{
public OrderSet orderSet { get; set; }
public IEnumerable<OrderedProductSet> orderedProduktSet { get; set; }
}
public partial class OrderedProduktSet
{
public int orderNumber{ get; set; }
public int productNumber { get; set; }
public int ilosc { get; set; }
public virtual ProduktSet ProduktSet { get; set; }
public virtual OrderSet OrderSet { get; set; }
}
You need to construct controls for you collection in a for loop or use a custum EditorTemplate for OrderedProduktSet so that the controls are correctly named with indexers and can be bound on post back. Note the for loop approach required that the collection be IList.
#model Shop.Models.ProductViewModel
#using(Html.BeginForm())
{
....
for(int i = 0; i < Model.orderedProductSet.Count; i++)
{
#Html.DisplayFor(m => m.orderedProductSet[i].ProduktSet.name)
....
#Html.EditorFor(m => m.orderedProductSet[i].quantity, new { htmlAttributes = new { #class = "form-control" } })
}
<input type="submit" />
}
Controller (the model will be bound, including the collection of OrderedProductSet)
public ActionResult Edit(ProductViewModel model)
{
....
}
Alternatively, you can create an EditorTemplate
/Views/Shared/EditorTemplates/OrderedProduktSet.cshtml
#model OrderedProduktSet
#Html.DisplayFor(m => m.ProduktSet.name)
#Html.EditorFor(m => m.quantity, new { htmlAttributes = new { #class = "form-control" } })
and in the main view
#model Shop.Models.ProductViewModel
#using(Html.BeginForm())
{
....
#Html.EditorFor(m => m.orderedProductSet)
<input type="submit" />
}
Viewbag is your friend here. You normally pass data from View to Controller in MVC. You can access data set in a Viewbag in the controller in your View.
The simplest way to let your controller handle your view is to create an actionresult method in your controller with the same name as your view.
For example, your view is called Index, thus you would have the following method in your controller to handle the view data:
public ActionResult Index()
{
return View();
}
Accessing a list:
Use a Viewbag.
Controller
Viewbag.MyList = myList
View
#foreach (var item in Viewbag.MyList)
Here is good link for more info:
http://www.asp.net/mvc/overview/older-versions/getting-started-with-aspnet-mvc4/adding-a-view

Posting Ienumerable Values and Saving to M-2-M Relationship

VS'12 KendoUI InternetApplication Template C# asp.net EF Code First
My Question is how to pass both the Regular ( are passing now ) values and the Ienumerable(passing null) into my controller and saving them to the Database using EF Code First in a Many-2-Many Relationship manor.
The Following is what i have tried
Main View
#model OG.Models.UserProfiles
#using (Html.BeginForm())
{
<div class="editor-field">
<div class="Containter">
<div>
#Html.DisplayFor(model => model.UserName)
</div>
<div class="contentContainer">
#foreach (var item in Model.Prospects)
{
<table>
<tr>
<td>
#Html.Label("Current Prospects")
</td>
</tr>
<tr>
<td>
#Html.DisplayNameFor(x=>item.ProspectName)
</td>
</tr>
</table>
}
</div>
</div>
<div class="contentContainer2">
#Html.Partial("_UsersInProspectsDDL", new OG.ModelView.ViewModelUserInProspects() { Users = Model.UserName })
</div>
</div>
}
Partial View
#model OG.ModelView.ViewModelUserInProspects
<label for="prospects">Prospect:</label>
#(Html.Kendo().DropDownListFor(m=>m.Prospects)
.Name("Prospects")
.HtmlAttributes(new { style = "width:300px"}) //, id = "countys"})
.OptionLabel("Select Prospect...")
.DataTextField("ProspectName")
.DataValueField("ProspectID")
.DataSource(source => {
source.Read(read =>
{
read.Action("GetCascadeProspects", "ChangeUsersInfo")
.Data("filterProspects");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("Clients")
</div>
Model for PartialView
public class ViewModelUserInProspects
{
public string Clients { get; set; }
public IEnumerable<dbClient> AvailableClients { get; set; }
public string Prospects { get; set; }
public IEnumerable<dbProspect> AvailableProspects { get; set; }
public string Users { get; set; }
public IEnumerable<UserProfiles> AvailableUsers {get;set;}
}
}
Main Model
Standart SimpleMemberShipUserTable
Post Method
[HttpPost]
public ActionResult UsersInProspect(
[Bind(Include= "ProspectName, ProspectID")]
UserProfiles userprofiles, ViewModelUserInProspects values, FormCollection form)
//<- Trying different things sofar
{
if (ModelState.IsValid)
{
//string something = form["Prospects"];
int prosID = Convert.ToInt16(values.Prospects);
int UserID = userprofiles.UserID; // <- THIS VALUE is null atm.
This is where i need to save both ID's to the EF Generated / Mapped Table. Unsure how.
db.Entry(userprofiles).CurrentValues.SetValues(userprofiles);
db.Entry(userprofiles).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(userprofiles);
}
Please take a look Here
Goes over ViewModels
What EditorTemplate are and how to use them
What the GET Method would look like
What the Edit View would look like
Give you a View Example
What the Post Method would look like

Resources