I am new to ASP.NET programming and I encounter some problem, maybe someone can help me to find the solution.
I followed and completed the procedure here BeginForm with IEnumerable. But when I tried to run my codes. Something error happens on browser. The error is:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ek_oms.Models.AddCategoryViewModel]', but this dictionary requires a model item of type 'ek_oms.Models.AddCategoryViewModel'.
Here's my related codes to that part.
Class:
public class AddCategoryViewModel
{
public IEnumerable<CategoryViewModel> CurrentCategories { get; set; }
public CategoryModel NewCategory { get; set; }
}
Partial codes in View:
#model ek_oms.Models.AddCategoryViewModel
#if (Model.CurrentCategories != null)
{
foreach (var item in Model.CurrentCategories)
{
<tr>
<td>#item.CategoryName</td>
<td>
#using (Html.BeginForm("CategoryDelete", "Admin", new { catID = item.Id }, FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.ActionLink("Edit", "CategoryEdit", null, new { catID = item.Id }, new { #class = "btn btn-primary btn-xs" })
<button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('Are you sure you want to delete record with Category = #item.CategoryName')">Delete</button>}
</td>
</tr>
}
}
<div class="tab-content">
#using (Html.BeginForm("Categories", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { role = "form" }))
{
#Html.AntiForgeryToken()
<div class="box box-primary">
<!-- /.box-header -->
<div class="box-body">
<div class="box-header">
<h3 class="box-title">Add New Category</h3>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
#Html.EditorFor(model => model.NewCategory.CategoryName, new { htmlAttributes = new { #class = "form-control", id = "Category", #placeholder = #Html.DisplayNameFor(model => model.NewCategory.CategoryName) } })
#Html.ValidationMessageFor(model => model.NewCategory.CategoryName, "", new { #class = "text-danger" })
</div>
<div class="form-group">
</div>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary" id="submitAddCatBtn">Add</button>
</div>
</div>
}
</div>
Controller:
public async Task<ActionResult> Categories()
{
ViewBag.Message = TempData["Message"];
ViewBag.CatUsedMessage = TempData["CatUsedMessage"];
HttpResponseMessage responseMessage = await client.GetAsync(urlProducts + "/categories");
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
var categories = JsonConvert.DeserializeObject<List<AddCategoryViewModel>>(responseData);
return View(categories);
}
return View("Error");
}
Can someone help me to this. I don't know the right term to start searching. Thanks.
var categories = JsonConvert.DeserializeObject<List<AddCategoryViewModel>>(responseData);
Above line is the issue.
Your view is expecting just one AddCategoryViewModel object and not a List<AddCategoryViewModel>. As the error message clearly suggests.
So, change your code to return only one object if you are expecting just one from the api as below:
var categories = JsonConvert.DeserializeObject<AddCategoryViewModel>(responseData);
OR if you need more than one category
Change your #model in view as below:
#model List<ek_oms.Models.AddCategoryViewModel>
In this case you will need to change your view code to handle Category list accordingly as below:
#foreach(var cat in Model)
{
if (cat != null)//use cat in place of Model in your view
{
foreach (var item in cat.CurrentCategories)
{
}
}
}
Related
I have a partial view for editing a collection (most of which I've truncated here):
#using CustomSurveyTool.Models
#model Basiclife
<div class="editorRow">
#using (Html.BeginCollectionItem(""))
{
<div class="form-horizontal">
<div class="row">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="col-md-2">
#Html.LabelFor(model => model.Plantype, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Plantype, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Plantype, "", new { #class = "text-danger" })
</div>
<div class="col-md-1">
X
</div>
</div>
</div>
}
</div>
Which is part of a wrapper view:
#using CustomSurveyTool.Models
#model IEnumerable<Basiclife>
#{
ViewBag.Title = "CreateBasicLifeResponse";
}
<h2>Basic Life Insurance Plans</h2>
<div id="planList">
#using (Html.BeginForm())
{
<div id="editorRows">
#foreach (var item in Model)
{
#Html.Partial("_BasicLifeResponse", item)
}
</div>
#Html.ActionLink("Add", "BasicLifeResponse", null, new { id = "addItem", #class = "button" });
<input type="submit" value="submit" />
}
</div>
Which posts back to this controller:
[HttpPost]
public ActionResult CreateBasicLifeResponse(IEnumerable<Basiclife> model)
{
foreach(var item in model)
{
string currentUserId = User.Identity.GetUserId();
Response targetresponse = db.response.FirstOrDefault(x => x.Userid == currentUserId);
int responseid = targetresponse.Id;
item.ResponseId = responseid;
db.basiclife.Add(item);
db.SaveChanges();
}
List<Basiclife> basiclife = new List<Basiclife>();
return View(model);
}
I am getting a NullReferenceException on the foreach(var item in model) after submitting the form. What could be causing this?
You need to follow the MVC naming convention which involves using a for loop with an index instead of foreach in your Razor view. Check out this article:
How to pass IEnumerable list to controller in MVC including checkbox state?.
I would recommend the Editor Template approach described in the article, it is most similar to your current PartialView approach.
I am trying to update Two models at the same time.
Models:
Page
Fields
One page has multiple fields, I want them to update at the same time.
public class PageEditViewModel
{
public Page mPage { get; set; }
public IEnumerable<Field> Fields { get; set; }
}
Here is my View:
<div class="row">
<div class="col-md-12">
<h3>Fields</h3>
#foreach (var field in Model.ContentFields)
{
#Html.HiddenFor(m => field.Id)
switch (field.FieldType)
{
case "TextBox":
<div class="form-group">
<label class="control-label">
#field.FieldName<span class="required"> * </span>
</label>
#Html.TextBoxFor(m => field.Content , new { #class = "form-control" })
</div>
break;
case "TextArea":
<div class="form-group">
<label class="control-label">
#field.FieldName<span class="required"> * </span>
</label>
#Html.TextAreaFor(m => field.Content, new { #class = "form-control" })
</div>
break;
case "Image":
<div class="form-group">
<label class="control-label">
#field.FieldName<span class="required"> * </span>
</label>
<input type="file" name="contentImage" id="cImage" class="form-control" accept="image/*" />
</div>
break;
}
}
</div>
</div>
And Controller:
public ActionResult Update(PageEditViewModel viewModel)
{
if (!ModelState.IsValid)
{
var page = _context.MenuPages.Single(s => s.Id == viewModel.mPage.Id);
var contentFields = _context.ContentFields.Where(c => c.MenuPageId == page.Id);
var viewM = new PageEditViewModel
{
DashboardHeading = "Edit a Page",
mPage = page,
ContentFields = contentFields
};
return View("EditPage", viewM);
}
var pageEdit = _context.MenuPages.SingleOrDefault(p => p.Id == viewModel.mPage.Id);
pageEdit.Name = viewModel.mPage.Name;
pageEdit.IsActive = viewModel.mPage.IsActive;
pageEdit.IsShowInMenu = viewModel.mPage.IsShowInMenu;
// _context.SaveChanges();
foreach (var field in viewModel.ContentFields)
{
var cfield = _context.ContentFields.SingleOrDefault(f => f.Id == field.Id);
cfield.Content = field.Content;
}
_context.SaveChanges();
When I Send the data from View to Controller, I get the data for Pages but Null for the Fields Model (Object Reference not set to an instance...).
I am looking forward to any guide from members here.
Thanks.
I think Umesh answer is correct.
After changing your loop, how are you setting your Html.TextBoxFor, HiddenFor and so on?
It should be:
#Html.HiddenFor(m => m.ContentFields[i].Id)
You need to replace #foreach loop on view
#foreach (var field in Model.ContentFields)
{
By for loop
#for (var i = 0; i < Model.ContentFields.Count; i++)
{
This will bind your list to model, while sending data to controller
I am trying to send data from database when pressing Edit on ActionLink(Last line on view). Having some problem in view Controller to understand how this data will be sent to the right input field so i can Edit it and send it back to database.
(This Question is only for the GET part so i have not included Post controller)
View(view name AddCourse):
#model IEnumerable<CadProject.Models.CourseModel>
#using (Html.BeginForm("AddCourse", "AdminModel", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.Label("Emnekode", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input name="CourseCode" type="text" />
</div>
</div>
<div class="form-group">
#Html.Label("Emne", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input name="CourseName" type="text" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Upload" class="btn btn-default" />
</div>
</div>
</div>
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CourseCode)
</th>
<th>
#Html.DisplayNameFor(model => model.CourseName)
</th>
<th>
Edit/Delete
</th>
</tr>
#if (Model != null)
{
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.CourseCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.CourseName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
}
</table>
View controller:
public ActionResult Edit(int id)
{
db.Courses.Find(id);
CourseModel model = db.Courses.Find(id);
if(model == null)
{
return View();
}
return View("AddCourse", db.Courses);
}
Database model:
public class CourseModel
{
[Key]
public int Id { get; set; }
public string CourseCode { get; set; }
public string CourseName { get; set; }
}
The functionality the Edit(HttpGet) method is to take the ID of the item the user want's to edit, find ONE record in the database with this ID and send the found record to the Edit view where you have various controls that display the current values and allow the user to change them through the use of text boxes e.t.c
In the code you posted you look up the course the user wants to edit but then you return all the courses to the Edit view, which is wrong.It's supposed to be like this:
public ActionResult Edit(int id)
{
db.Courses.Find(id);
CourseModel model = db.Courses.Find(id);
if(model == null)
{
return View();
}
return View("EditCourse", model);
}
Have a look at this simple example:
Edit Action:
[HttpGet]
public ActionResult Edit(int id)
{
var course1 = new Course { ID = 0, Name = "Software Engineering" };
var course2 = new Course { ID = 1, Name = "ASP.NET MVC" };
var course3 = new Course { ID = 2, Name = "C# Course" };
var courses = new List<Course> { course1 ,course2, course3};
var course = courses.FirstOrDefault(c => c.ID == id);
return View("EditCourse",course);
}
EditCourse View:
#model Example.Models.Course
#{
ViewBag.Title = "EditCourse";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Course</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
I have a view model which contains a class reference and 2 IEnumerable collections.
public class BuildingTypeViewModel
{
public Static_Item BuildingType { get; set; }
public IEnumerable<Reading_Type> ReadingTypes { get; set; }
public IEnumerable<Building_Type_Reading> BuildingReadings { get; set; }
}
I populate this ViewModel in the Edit action on the controller
public ActionResult Edit(int id)
{
Static_Item staticItem = db.Static_Item.Find(id);
BuildingTypeViewModel model = new BuildingTypeViewModel
{
BuildingType = staticItem,
ReadingTypes=db.Reading_Type.ToList(),
BuildingReadings = db.Building_Type_Reading.Where(bt => bt.UN_Building_Type == staticItem.UN_Building_Type).ToList()
};
return View(model);
}
Building type on the view model is a class with an ID and Description and the data would be something like this:
UN_Building_Type=1, Description = "Hospital"
The IEnumerable of Reading_Type's would be like this:
UN_Reading_Type = 1, Description = "Electric"
UN_Reading_Type = 2, Description = "Gas"
The IEnumerable of Building_Type_Readings would be like this:
UN_Building_Type_Readings=1, UN_Building_Type=1, UN_Reading_Type = 1, Typical=300, Good=150
UN_Building_Type_Readings=2, UN_Building_Type=1, UN_Reading_Type = 2, Typical=800, Good=400
I load this data into my view:
#model SSE.Enterprise.EE_Web_Portal.Models.BuildingTypeViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Static_Item</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.BuildingType.UN_Building_Type)
<div class="form-group">
#Html.LabelFor(model => model.BuildingType.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BuildingType.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BuildingType.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Building Readings</label>
<div class="col-md-10">
<table class="table">
<tr>
<th/>
<th>
#Html.LabelFor(model=>model.BuildingReadings.FirstOrDefault().Typical)
</th>
<th>
#Html.LabelFor(model => model.BuildingReadings.FirstOrDefault().Good)
</th>
</tr>
#foreach (var item in Model.ReadingTypes)
{
<tr>
<td>
#Html.Label(item.Description)
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Typical, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Good, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</table>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Here is my edit postback method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "BuildingType")] BuildingTypeViewModel model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
When the page is posted back the 2 IEnumerable collections are null so I'm not able to save the data that has been entered.
Any idea?
Ta
First of all, by specifying the attribute Bind(Include = "BuildingType") you're telling MVC to only bind this single property. Remove the attribute, and MVC will try to bind your 2 IEnumerable collections.
Next, check your #Html.EditorFor calls. I'm not sure MVC can understand FirstOrDefault inside. Try avoiding LINQ selectors inside your view.
And after that, as #will mentioned, try changing IEnumerable to List.
Been working on this issue for a few hours now, maybe I'm missing something simple here, but no matter what I try I can't get the default selected items to work.
The controller function:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Room room = db.Rooms.Find(id);
if (room == null)
{
return HttpNotFound();
}
List<int> allowedMods = new List<int> {1, 2};
List<keyval> allMods = new List<keyval>
{
new keyval(1,"A"),
new keyval(2,"B"),
new keyval(3,"C")
};
MultiSelectList multiList = new MultiSelectList(allMods, "ID", "Name", allowedMods);
ViewBag.mods = multiList;
return View(room);
}
Simple helper class keyval:
public class keyval
{
public int ID { get; set; }
public string Name { get; set; }
public keyval() { }
public keyval(int ID, string Name)
{
this.ID = ID;
this.Name = Name;
}
}
The view:
#model X.Models.Room
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Room</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.Label("Moderators Allowed", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBox("mods", ViewBag.mods as MultiSelectList, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Relevant generated Html:
<div class="col-md-10">
<select class="form-control" id="mods" multiple="multiple" name="mods">
<option value="1">A</option>
<option value="2">B</option>
<option value="3">C</option>
</select>
</div>
I've tried so many different variations and when I submit the form I do get the new list of selected items, However the default values is not working for me.
I would really appreciate help on this issue.
The name of your listbox is the same as the name of your ViewBag property that holds the list, there is a strange bug with ViewBag that causes things to not render properly if this is the case. Try changing ViewBag.mods to ViewBag.moderators or something other than ViewBag.mods.
Tested using your code and that corrects the problem for me.