I have recently started playing around with ASP.net MVC (4), but I can't wrap my head around this one issue I'm having. I'm sure it's easy when you know it.
I'm essentially trying to do the the following in my Index view:
List the current items in the database of type "Note" in the Index view (that's easy)
Creating new items in the same Index view (not so easy).
So I figured I needed a partial view, and that I have created as following (_CreateNote.cshtml):
#model QuickNotes.Models.Note
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Note</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Content)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Content)
#Html.ValidationMessageFor(model => model.Content)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
In my original Index view (Index.cshtml) I'm trying to render this partial view:
#model IEnumerable<QuickNotes.Models.Note>
#{
ViewBag.Title = "Personal notes";
}
<h2>Personal notes</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Content)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Content)
</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>
<div>
#Html.Partial("_CreateNote")
</div>
(using: #Html.Partial("_CreateNote"))
However. This doesn't seem to work, as I get the following error message:
Line 35:
Line 36: <div>
Line 37: #Html.Partial("_CreateNote");
Line 38: </div>
Source File: c:\Dropbox\Projects\workspace .NET MVC\QuickNotes\QuickNotes\Views\Notes\Index.cshtml Line: 37
Stack Trace:
[InvalidOperationException: The model item passed into the dictionary is of type 'System.Data.Entity.DbSet`1[QuickNotes.Models.Note]', but this dictionary requires a model item of type 'QuickNotes.Models.Note'.]
System.Web.Mvc.ViewDataDictionary`1.SetModel(Object value) +405487
My NotesController looks like this:
public ActionResult Index()
{
var model = _db.Notes;
return View(model);
}
//
// GET: /Notes/Create
public ActionResult Create()
{
return View();
}
//
// GET: /Notes/_CreateNote - Partial view
public ViewResult _CreateNote()
{
return View("_CreateNote");
}
I think it has to do with the fact that the Index view is using the model differently, as in #model IEnumerable, but no matter how I change it around, using RenderPartial, RenderAction, changing ActionResult to ViewResult etc, I can't get it to work.
Any tips would be greatly appreciated!
Please let me know if you need any more information. I'd be happy to zip down the entire project if needed.
Change the code where you load the partial view to:
#Html.Partial("_CreateNote", new QuickNotes.Models.Note())
This is because the partial view is expecting a Note but is getting passed the model of the parent view which is the IEnumerable
You're passing the same model to the partial view as is being passed to the main view, and they are different types. The model is a DbSet of Notes, where you need to pass in a single Note.
You can do this by adding a parameter, which I'm guessing as it's the create form would be a new Note
#Html.Partial("_CreateNote", new QuickNotes.Models.Note())
Related
On my website, I have an <aside> that I would like to list top news in.
I created a database to store the top news articles and connected it to my project using Entity Framework. It's a simple database with an ID and Message property.
I created a partial view to render just the messages without the ability to edit them from the partial view. It's essentially a copy of the Index view generated by EF when it created the database using code-first.
I'm using #RenderPage() to render the <aside>. Inside of the <aside>, I'm using #RenderPage() to display some other information.
Here are some of the things I've tried so far:
Used #RenderPage() to display the list of messages - failed
Used #Html.Partial("_TopNews", Model) - Error: Object reference not set to an instance of an object. - I also have the #model IEnumerable<Namespace.Models.TopNews> declared at the top of the page.
Tried the same thing with #Html.RenderPartial - same results.
I created an ActionResult (_DisplayTopNews) in the TopNews Controller and a View in the TopNews view folder. I used #Html.Partial("_DisplayTopNews", Model), but with no luck still.
Here is the code for the aforementioned items starting with the <aside>:
#model IEnumerable<Namespace.Models.TopNews>
<aside class="sidebar sidebar-primary block-content-area col-md-4 panel-sides">
<section class="roundBox">
<div class="wrap">
<h4 class="block-title">Next Cycle Begins:</h4>
<div class="margin-top-lg text-left statement margin-sides-xl" aria-label="News and Announcements">
<div class="statement margin-sides pad-sides">
<h3>#RenderPage("~/Views/Shared/_CycleStateDate.cshtml")</h3>
</div>
</div>
</div>
</section>
<section class="sidebar-block sidebar_nav_menu roundBox">
<div class="wrap">
<h4 class="block-title"><strong>TOP NEWS</strong></h4>
<div class="margin-top-lg text-left statement margin-sides-xl" aria-label="News and Announcements">
<div class="statement margin-sides pad-sides">
#Html.Partial("_DisplayTopNews", Model)
</div>
</div>
</div>
</section>
</aside>
TopNews Partial:
#model IEnumerable<Namespace.Models.TopNews>
<table class="table">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Message)
</td>
</tr>
}
</table>
_DisplayTopNews view
#model IEnumerable<Namespace.Models.TopNews>
<ul class="list">
#foreach (var item in Model)
{
<li>
#Html.DisplayFor(modelItem => item.Message)
</li>
}
</ul>
TopNewsController ActionResult Method _DisplayTopNews():
private DbContext db = new DbContext();
public ActionResult _DisplayTopNews()
{
return View(db.TopNews.ToList());
}
Can anyone please point out what I've done wrong and how I can correct it please?
*As a quick note I would like to add, all CRUD functionality works.
So, to answer my own question. The first thing I realized was that I didn't want my partial mixed in with the other TopNews views. So I created the partial view in my Home controller.
Here's the partial:
#model IEnumerable<Namespace.Models.TopNews>
#foreach (var item in Model)
{
<blockquote class="left">
<small>
#Html.DisplayFor(modelItem => item.Message)
</small>
</blockquote>
}
Besides how it will be displayed on the page, everything is the same.
In my Home controller I added an ActionResult that returned the view _TopNews and the model.
Here is the HomeController ActionResult:
private readonly DbContext db = new DbContext();
public ActionResult _TopNews()
{
return PartialView("_TopNews", db.TopNews.ToList());
}
In the <aside> where the partial will be rendered, instead of using #Html.Partial or #Html.RenderPartial, I used #Html.Action.
Here is the <aside>:
#model IEnumerable<Namespace.Models.TopNews>
<aside class="sidebar sidebar-primary block-content-area col-md-4 panel-sides">
<section class="sidebar-block sidebar_nav_menu" aria-label="Top News sidebar">
<div class="panel panel-default">
<!--SIDEBAR HEADER-->
<div class="panel-heading">
<h3 class="Agenda">Top News</h3>
</div>
<div class="panel-body">
#Html.Action("_TopNews", "Home")
</div>
</div>
</section>
</aside>
I hope this helps someone else.
I would like to be able to press a submit button in my Index view which saves all the changes made to the list through the EditorFors that I use.
In the Edit View I can edit and save one list item at a time, but I would like to be able to save the full list in one click.
I've tried wrapping the whole table in the Index view with #using(Html.BeginForm()) {}, then added an HttpPost method in the controller like this:
[HttpPost]
public ActionResult Index(List<TileModel> modelList) {
return View(modelList);
}
But modelList is null when I enter this method. I've tried adding [Bind("MyModel")] to the parameter list but I'm a bit clueless as what to bind ..
I'm basically trying to copy the functionality of this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Name,Employment")] TileModel mModel) {
if(ModelState.IsValid) {
db.Entry(mModel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mModel);
}
, but have it save the full list while inside the Index view.
Any ideas how one could achieve this? Am I even remotely on the right track?
Edit:
Here's my index view(my edit view is basically unchanged from what visual studio generates so I don't think it's that interesting to show?).
#model IEnumerable<Anslagstavlan.Models.TileModel>
#{ ViewBag.Title = "title"; }
<ul class="nav navbar-nav">
<li>#Html.ActionLink("TILES", "tileboard", "tilemodels")</li>
<li>#Html.ActionLink("NEW ENTRY", "Create")</li>
</ul>
#using(Html.BeginForm()) {
<table class="table">
<tr>
<th> #Html.DisplayNameFor(model => model.Name) </th>
<th> #Html.DisplayNameFor(model => model.InfoFields) </th>
<th> #Html.DisplayNameFor(model => model.Employment) </th>
<th></th>
</tr>
#foreach(var item in Model) {
<tr>
<td> #Html.DisplayFor(mItem => item.Name) </td>
<td> #Html.EditorFor(mItem => item.InfoFields, new { htmlAttributes = new { #class = "form-control" } }) </td>
<td> #Html.DisplayFor(mItem => item.Employment) </td>
<td>
#Html.ActionLink("SAVE", "Save", new { id = item.ID }) |
#Html.ActionLink("EDIT", "Edit", new { id = item.ID })
</td>
</tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default" />
}
Edit2, working:
My loop with the table now looks like:
#using(Html.BeginForm()) {
<table class="table">
#for(int i = 0; i < Model.Count; i++) {
<tr> <td>
#Html.TextBoxFor(m => m[i].Name)
</td> </tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default" />
}
The only difference from Stephens answer is the # sign before the for loop..
Change the model in the view to be IList<TileModel> and generate the controls in a for loop so they are correctly named with indexers and can be bound to a collection on post back
View
#model IList<Anslagstavlan.Models.TileModel>
#using(Html.BeginForm())
{
....
for(int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(m => m[i].InfoFields, new { #class = "form-control" })
.... // controls for other properties of your model that you want to post back
}
<input type="submit" value="Save" class="btn btn-default" />
}
POST method
[HttpPost]
public ActionResult Index(List<TileModel> modelList)
{
// save each item and redirect
}
I am sorry if I ask a newbie question but i am new to asp.net mvc.
So, I have this view :
#model FirstProject.Models.SelectRolesViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("SelectRolesOverall","SelectRoles"))
{
<table class="table">
<tr>
<th>Users</th>
<th>Roles</th>
</tr>
#Html.EditorFor(model=>model.UsersAndRoles)
<tr>
<td></td>
<td>
<input type="submit" />
</td>
</tr>
</table>
}
And the EditorTemplate :
#model FirstProject.Models.UserRole
<tr>
<td>#Model.User.UserName</td>
<td>
#Html.RadioButtonFor(model => model.Role, "Applicant") Applicant
<br/>
#Html.RadioButtonFor(model => model.Role, "Professor") Professor
</td>
</tr>
My question is next : How do I see what radio buttons have been checked in my controller after the submit button has been pressed? I want to have the following logic : If Applicant have been selected then userRole is Applicant , else if Professor has been selected then userrole is Professor. My controller is Empty momentarily because I don't know what to write in it.
If your action method is
public SelectRolesOverall(SelectRolesViewModel model)
then you can access the collection with
IEnumerable<UsersAndRoles> usesAndRoles = model.UsersAndRoles;
and access each item in the collection
foreach (UserRole userRole in model.UsersAndRoles)
{
string role = userRole.Role;
string name = userRole.UserName; // see note below
}
Note you have not included an input for the property UserName so this value wont post back and you might have trouble matching the role to the user. You might want to add #Html.HiddenFor(m => m.UserName) or change <td>#Model.User.UserName</td> to <td>#Html.TextBoxFor(m => m.UserName, new { #readonly = "readonly" })</td>
Try this
public ActionResult [YourActionName](string role)
{
switch(role){
case "Applicant": /*your applicant logic*/
break;
case "Professor": /*your Professor logic*/
break;
/*
Other logic here
*/
}
Replace the action name by your own
Note that the parameter role should have the same name of the radio button in your view, this is to allow data binding to work
I'm writing my first MVC3 application which is a simple order tracking application. I would like to edit the order and the details at the same time. When I edit the order the ActionResult for the Edit returns the order and the associated line (i'm using EF as well).
public ActionResult Edit(int id)
{
// Get the order with the order lines
var orderWithLines = from o in db.Orders.Include("OrderLines")
where o.ID == id
select o;
// Not sure if this is the best way to do this.
// Need to find a way to cast to "Order" type
List<Order> orderList = orderWithLines.ToList();
Order order = orderList[0];
// Use ViewData rather than passing in the object in the View() method.
ViewData.Model = order;
return View();
}
The order and the lines display with no issue but when I save the page I do not get any of the lines passed back to the controller. Only the order. Here is the View code.
#model OrderTracker.Models.Order
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Order</legend>
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.UserId)
<div>
#Html.LabelFor(model => model.OrderDate)
</div>
<div>
#Html.EditorFor(model => model.OrderDate)
</div>
<div>
#Html.LabelFor(model => model.Description)
</div>
<div>
#Html.EditorFor(model => model.Description)
</div>
<table>
<tr>
<th>
Description
</th>
<th>
Quantity
</th>
<th>
Weight
</th>
<th>
Price
</th>
<th></th>
</tr>
#foreach (var line in Model.OrderLines)
{
<tr>
<td>
#Html.EditorFor(modelItem => line.Description)
</td>
<td>
#Html.EditorFor(modelItem => line.Quantity)
</td>
<td>
#Html.EditorFor(modelItem => line.Weight)
</td>
<td>
#Html.EditorFor(modelItem => line.Price)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Can I please get some guidance on the best way to save the line data as well as the order data.
Thanks.
The issue that you are facing is related to the names generated by the ICollection<T> controls. Here is a detailed discussion by Phil Haack and a solution by him (in terms of an #Html extension method; download the sample project from the link given at the end of his blog post). This post targets MVC/MVC2; however it is still applicable with MVC3.
Alternatively if you don't want to follow the hack, you can opt for a EditorTemplate for your OrderLine entity model.
Here are the steps.
1) Create Editor template under (Views ->Shared -> EditorTemplates -> OrderLine.cshtml)
It is important to create a folder named EditorTemplates under Shared, and the template name should be same as the EntityModel for which you want to create the templete; hence the name OrderLine.cshtml)
2) Code for OrderLine.cshtml
#model OrderTracker.Models.OrderLine
#{
Layout = null;
}
<!DOCTYPE html>
#Html.HiddenFor(modelItem => Model.id)
<tr>
<td>
#Html.EditorFor(modelItem => Model.Description)
</td>
<td>
#Html.EditorFor(modelItem => Model.Quantity)
</td>
<td>
#Html.EditorFor(modelItem => Model.Weight)
</td>
<td>
#Html.EditorFor(modelItem => Model.Price)
</td>
</tr>
3) Edit your View with this code (note that I've used EditorFor for OrderLines collection)
#model OrderTracker.Models.Order
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Order</legend>
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.UserId)
<div>
#Html.LabelFor(model => model.OrderDate)
</div>
<div>
#Html.EditorFor(model => model.OrderDate)
</div>
<div>
#Html.LabelFor(model => model.Description)
</div>
<div>
#Html.EditorFor(model => model.Description)
</div>
<div>
<table>
<tr>
<th>
Description
</th>
<th>
Quantity
</th>
<th>
Weight
</th>
<th>
Price
</th>
</tr>
#Html.EditorFor(model => model.OrderLines)
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
4) Now on post back you will see the values
Using MVC it should be rather straight forward as the framework is designed to to turn a form into a model.
[HttpGet]
public ActionResult Edit(int id)
{
// (you can probably rewrite this using a lambda
var orderWithLines = from o in db.Orders.Include("OrderLines")
select o;
// Use ViewData rather than passing in the object in the View() method.
ViewData.Model = orderWithLines.FirstOrDefault(x => x.ID = id);
return View();
}
[HttpPost]
public ActionResult Edit(OrderTracker.Models.Order model)
{
if (ModelState.IsValid)
{
// call the service layer / repository / db to persist the object graph
_service.Save(model); // this assumes your view models are the same as your domain
}
}
The issue is with your foreach if you look at the raw html that it produces it will not be generating unique ids for each of the order lines and thus will not be able to bind the model when the form is posted back.
Change the foreach to a for loop and then reference each orderline using the index. This will allow for unique ids to be generated in the form and allow you to bind the to the model when it is posted back.
e.g.
#for (var counter = 0; counter < Model.OrderLines.Count(); counter++)
{
<tr>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Description)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Quantity)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Weight)
</td>
<td>
#Html.EditorFor(modelItem => Model.OrderLines[counter].Price)
</td>
</tr>
}
</table>
This question already has an answer here:
How to pass IEnumerable list to controller in MVC including checkbox state?
(1 answer)
Closed 6 years ago.
All, please clear up my confusion on how the model binding works with IEnumerables and Editor Templates.
I have a view, Approve.cshtml
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
A model, Approve.cs
public class Approve
{
public string Name { get;set;}
public string Role { get; set; }
}
And an editor template
#model MvcWebsite.Models.Approve
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post))
{
<tr>
<td>
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
This is all fine and good. It renders the following output.
<input name="[0].Name" type="hidden" value="" />
....
However, in my Controller, I can't seem to receive values back for the Model (binding).
[HttpPost]
public ActionResult Approve(Approve approveModel)
{
.... approveModel has all default values
}
Can someone shed light on what I am doing wrong here? I abbreviated the code, I am using the Editor Template with other EditorFor and HiddenFor fields from my model...
Edited: I basically have a table layout, each with the User's Name, a textbox where I can enter their role (User or Admin) and then an Approve button which submits to my controller. Hence the reason I want to only return a single Approve object. I can return the entire IEnumerable to my Controller, but if I do that, how can I tell which of the items was the one I clicked the Approve button (submit) for?
EDIT:
So I have modified the code so that I have a single form surrounding my entire View Approve.cshtml
#model IEnumerable<MvcWebsite.Models.Approve>
#using (Html.BeginForm("Approve", "Program", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
}
And then changed the controller to
[HttpPost]
public ActionResult Approve(IEnumerable<Approve> approvals)
{
// ???????????????????????
}
Now I'm still not clear on how to know which row I clicked Approve for. I know there are other ways to accomplish this task (create a checkbox for approve, and approve anything checked, etc.) However, I need the ability to click a button and only save 1 row back to the database, regardless if the user entered information into the other rows. Is it better practice to wrap my IEnumerable inside of it's own model (i.e. AllApprovals) and then add helper properties to that parent model (SelectedIndex, etc.)? If that is the approach to take, then how do I set the SelectedIndex after clicking an Approve button? Is that still jquery magic or is there a correct MVC way to accomplish this? Jquery magic seems very hackish to me?
EDIT: Based on Brian's response, here is my final. Still doesn't feel quite right, but it works!
View
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
Editor Template
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post))
{
<tr>
<td>
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
Controller
[HttpPost]
public ActionResult Approve([Bind(Prefix="approval")]Approve approval) {
// WORKS!
}
Since you are only changing one at a time, I think the following is easier than trying to figure out at the controller which values changed, or adding a changed property and setting it via javascript.
Change Approve.cshtml to
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th colspan=2>
Name
</th>
</tr>
#foreach(var user in Model){
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post)) {
<tr>
<td>
#Html.EditorFor(m => user)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
}
</table>
Change the Approve Editor Template to
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
You're binding to the single Approve class, you should bind to IEnumerable<Approve>
Martin is correct I just want to add some more information. Your rendered HTML with the [0] is special syntax the model binder looks at and assumes you are working with a list if objects. Since your action method has a single Approve class and not a kist, you are experiencing this problem.