I have a little serach box that returns results from a database. That works fine. The results are in a List page and display correctly. However, I need to take the selected object and pass it to my controller. I am getting NULL values when I debug it, and an empty results page.
Here is the model:
public class CodeSnip
{
public short Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Code { get; set; }
public LangType Language { get; set; }
public string Creator { get; set; }
}
public class ListOfCodeSnips : List<CodeSnip>
{
public CodeSnip CodeSnip { get; set; }
}
public enum LangType
{
CSharp,
css,
HTML,
JavaScript,
Perl,
PHP,
Python,
Ruby,
SQL,
VB,
XML,
Other
}
Here is the controller method (which does nothing atm):
[HttpPost]
public ActionResult Display(CodeSnip snip)
{
return View(snip);
}
Here is what I have for a view. Again, it posts only NULLS for the object values:
#model Models.ListOfCodeSnips
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CodeSnip.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.CodeSnip.Description)
</th>
<th>
#Html.DisplayNameFor(model => model.CodeSnip.Language)
</th>
<th>
#Html.DisplayNameFor(model => model.CodeSnip.Creator)
</th>
</tr>
#using (Html.BeginForm("Display", "Home", FormMethod.Post))
{
foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Language)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Creator)
</td>
<td>
<input type="submit" value ="Display"/>
</td>
</tr>
}
}
</table>
so, in short, what I want to do is take the selected model-item from the view and pass it into my controllers Display method, but as I said, all I get are nulls.
I have looked around and all i find are examples f how to pass a List of objects. I tried monkeying with those, but got nothing.
Thanks for your time.
Since you said that you have tried with ActionLink and it did not work, here is how it would work.. instead of passing the type that you are looking for as a parameter for the Display action, pass the ID of the record.
So it would look something like this:
Controller Action
[HttpGet]
public ActionResult Display(int id)
{
var snip = db/* connection string */.TableName.Find(id);
return View(snip);
}
ActionLink
#Html.ActionLink("Display", "Display", "ControllerName", new { id = item.Id }, null )
// Or if you want a button that acts as a link, and not just a plain link
<input type="button" class="btn" value="Display" onclick="location.href='#Url.Action("Display", "ControllerName", new { id = item.Id })'" />
Let me know if this helps!
There are two problems with your code:
1) You do not render any <input> elements in which you send the selected values back to the controller. Use Html.HiddenFor or Html.EditorFor in addition to Html.DisplayFor.
2) In order for the MVC Modelbinder to be able to bind your list, use a for loop instead of foreach.
See also MVC Form submit not transferring the updated model back to action method for List
for (var i = 0; i < Model.Count(); i++) {
<tr>
<td>
#Html.DisplayFor(m => m[i].Title)
#Html.HiddenFor(m => m[i].Title)
</td>
#* etc ... *#
<tr>
}
PS: this loop would post all displayed CodeSnips, i.e. the receiving action would have the signature public ActionResult Display(ListOfCodeSnips postData).
Related
I'm getting myself acquainted with ASP.NET MVC but i'm running into something probably trivial. I have a model called ToDoList, this is a complex type with a list of ToDoItems:
public class ToDoList
{
public Guid Id {get;set;}
public string Name { get; set; }
public virtual ICollection<ToDoItem> Items {get;set;}
}
public class ToDoItem
{
public int Id { get; set; }
public string Task { get; set; }
public bool IsDone { get; set; }
public virtual ToDoList ToDoList { get; set; }
}
My Details page with form looks like this:
#model DataLayer.TomTest.Entities.ToDoList
<h2>#Model.Name</h2>
#using (#Html.BeginForm())
{
#Html.AntiForgeryToken()
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Items.First().Id)
</th>
<th>
#Html.DisplayNameFor(model => model.Items.First().Task)
</th>
<th>
#Html.DisplayNameFor(model => model.Items.First().IsDone)
</th>
</tr>
#foreach (var toDoItem in Model.Items)
{
<tr>
<td>
#toDoItem.Id
</td>
<td>
#Html.EditorFor(model => toDoItem.Task)
</td>
<td>
#Html.EditorFor(model => toDoItem.IsDone, new {htmlAttributes = new {#Style = "margin-left: 10px;"}})
</td>
</tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default"/>
}
And this is the method it posts to:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Details([Bind(Include = "Id,Name,Items")] ToDoList todoList)
{
if (ModelState.IsValid)
{
_context.Entry(todoList).State = EntityState.Modified;
await _context.SaveChangesAsync();
return View();
}
return View();
}
As you can see I included the [Bind] attribute as I read somewhere that would ensure i get the correct properties passed. When I debug this however, only the Id property is filled the rest remains null.
What can I do to fix this? Is it a mistake in the View? Or is it possible Entity Framework isn't setup correctly?
Thanks in advance for your help.
Model binding to a list doesn't work with a foreach; you need to use a for loop instead.
You'll also need hidden inputs for any properties which don't have editors within the loop.
#for (int index = 0; index < Model.Items.Count; index++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Items[index].Id)
#Model.Items[index].Id
</td>
<td>
#Html.EditorFor(m => m.Items[index].Task)
</td>
<td>
#Html.EditorFor(m => m.Items[index].IsDone, new { htmlAttributes = new { #Style = "margin-left: 10px;" } })
</td>
</tr>
}
ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries - Scott Hanselman's Blog
I am having difficulty getting my Index view to display the values I have from the SQL database.
The main issue is that I cannot use #foreach (var item in Model) {... because my table is not created as Enumerable (I think). I run into the error message System.NullReferenceException: 'Object reference not set to an instance of an object.' pointing at Model in the foreach statement expression.
I am wondering if I need to set my table up as an Enumerable list, then display each item. Or if I am missing something with regards to my Index view. Or maybe I need to pass something through the Index return View()?
Here is the Image Model:
namespace Uploadimage.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web;
public partial class Image
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string City { get; set; }
public string Province { get; set; }
public string Phone { get; set; }
[Required]
[RegularExpression(#"[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
ErrorMessage = "Email doesn't look like a valid email address.")]
public string Email { get; set; }
[Compare("Email", ErrorMessage = "Emails do not match.")]
public string ConfirmEmail { get; set; }
[DisplayName("Upload File")]
public string ImagePath { get; set; }
public HttpPostedFileBase ImageFile { get; set; }
}
}
Here is the controller:
public class ImageController : Controller
{
[HttpGet]
public ActionResult Add()
{
return View();
}
[HttpPost]
public ActionResult Add(Image imageModel)
{
string fileName = Path.GetFileNameWithoutExtension(imageModel.ImageFile.FileName);
string extension = Path.GetExtension(imageModel.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
imageModel.ImagePath = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
imageModel.ImageFile.SaveAs(fileName);
using(LoginDBEntities db = new LoginDBEntities())
{
db.Images.Add(imageModel);
db.SaveChanges();
}
ModelState.Clear();
return View();
}
[HttpGet]
public ActionResult View(int id)
{
Image imageModel = new Image();
using (LoginDBEntities db = new LoginDBEntities())
{
imageModel = db.Images.Where(x => x.Id == id).FirstOrDefault();
}
return View(imageModel);
}
public ActionResult Index()
{
return View();
}
}
And lastly, my Index View:
#model IEnumerable<Uploadimage.Models.Image>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
#Html.DisplayNameFor(model => model.LastName)
</th>
<th>
#Html.DisplayNameFor(model => model.City)
</th>
<th>
#Html.DisplayNameFor(model => model.Province)
</th>
<th>
#Html.DisplayNameFor(model => model.Phone)
</th>
<th>
#Html.DisplayNameFor(model => model.Email)
</th>
<th>
#Html.DisplayNameFor(model => model.ImagePath)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.City)
</td>
<td>
#Html.DisplayFor(modelItem => item.Province)
</td>
<td>
#Html.DisplayFor(modelItem => item.Phone)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.ImagePath)
</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>
I have tried thoroughly looking this up but have found things that don't specifically apply to me.
Or I don't know what to look up.
Two things:
Your Index controller action will need to load the images to pass to the view. At a minimum to serve a collection of images as the view's "model":
public ActionResult Index()
{
using (LoginDBEntities db = new LoginDBEntities())
{
var images = db.Images.ToList();
return View(images);
}
}
Then in the view you will need to check whether you actually get any results, then extract your labels from the first result if there are any, or display a suitable message if there are no images:
<!-- ... -->
#if (Model.Any())
{
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.ElementAt(0).FirstName)
</th>
<th>
#Html.DisplayNameFor(model => model.ElementAt(0).LastName)
</th>
...
</tr>
#for (int count = 0; count < Model.Count; count++) {
<tr>
<td>
#Html.DisplayFor(model => model.ElementAt(count).FirstName)
</td>
<td>
#Html.DisplayFor(model => model.ElementAt(count).LastName)
</td>
...
<td>
#Html.ActionLink("Edit", "Edit", new { id=#Model.ElementAt(count).Id }) |
#Html.ActionLink("Details", "Details", new { id=#Model.ElementAt(count).Id }) |
#Html.ActionLink("Delete", "Delete", new { id=#Model.ElementAt(count).Id })
</td>
</tr>
}
</table>
}
else
{
<p> No Images. </p>
}
The above is written from memory so it will likely have some syntactic issues but it should point you in the right direction.
Typically when working with collections like search results I define a view model class for the results page that itself contains the collection of results. The page's model becomes that wrapper view model which can contain details about the page (such as things like current search criteria, lookup values for searches, etc.) along with the collection of results. (typically a PagedList to support pagination)
I.e.
[Serializable]
public class ImageIndexViewModel
{
public string NameSearchString { get; set; }
public ICollection<ImageViewModel> Results { get; set; } = new List<ImageViewModel>();
}
Where ImageViewModel is a serializable POCO view model to represent just the details about images that the view will display. Often the views don't need everything about a data row, and in cases where the data row has navigation properties and extra fields we don't need to display, serializing entities results in lazy load calls or simply sending a lot of extra data that isn't needed.
You don't have anything in your Index method which gets a list of entities.
Your Index method in your controller should look something like:
public ActionResult Index()
{
var model = db.Images.ToList();
return View(model);
}
I have been reviewing possible ways to return a View's #model information which is of type IEnumerable back to the controller, so that if I sort/filter on a query from database, I can refine the return each iteration without restarting with a fresh full list being returned. All the ways show you need to POST back based on model[index] which works if you are inside a for loop. But I am working with sending the collection back from an #HTML.ActionLink within a table's header section, so there is no possible indexing available.
My WebAPI setup is based on this where they show how to sort and filter. I am trying to make it a little more complex in that after I filter a list based on my original DB query, I will then be able to sort (from a clickable-actionLink on a table's header column) from that filtered list; where as currently it would just sort from a fresh complete list.
The only way I can think of to do this is pass back (by a POST) to the controller the updated list of the customClass.
#Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName,
companyListIds = Model.???? })
A better option (based on comments from Tacud) which would require a smaller POST URL would be by returning a list of the id properties only which can then be applied to a query. But its still a list and still needs to be sent back without an index from and ActionLink. This will help keep track and allow me to continue drilling down to a smaller and smaller list.
Below is parts of my model class, the index Action from the controller, and the index view.
Model namespace:
public class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Controller Namespace:
public async Task<IActionResult> Index(ICollection<int> prev, string orderBy , string searchCategory ="", string searchString = "")
{
List<string> Categories = new List<string>() { "Name", "City", "State", "Zip", "Contact Person" };
ViewBag.searchCategory = new SelectList(Categories);
ViewBag.sortByName = orderBy == null ? "name" : orderBy == "name" ? "namedesc" : "name";
ViewBag.sortByCity = orderBy == "city" ? "citydesc" : "city";
ViewBag.sortByState = orderBy == "state" ? "statedesc" : "state";
ViewBag.companyIndex = companyList.Count==0 ? await _context.Company.ToListAsync() : companyList ;
List<Company> resultSet = new List<Company>(ViewBag.companyIndex);
if (!String.IsNullOrEmpty(searchCategory) && !String.IsNullOrEmpty(searchString))
{
switch (searchCategory)
{
.....
}
}
switch (orderBy)
{
....
}
return View(resultSet);
}
View namespace:
#model IEnumerable<Laier_It.Models.Company> <p>
#using (Html.BeginForm() {
<p>
Search By: #Html.DropDownList("SearchCategory", "")
Search For: #Html.TextBox("SearchString")
<input type="submit" value="Filter" />
</p> }
<table class="table ">
<thead>
<tr>
<th>
#Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName, companyList = Model })
</th>
<th>
#Html.DisplayNameFor(model => model.Address)
</th>
<th>
#Html.ActionLink("City", "Index", new { orderBy = ViewBag.sortByCity, companyList = Model })
</th>
<th>
#Html.ActionLink("State", "Index", new { orderBy = ViewBag.sortByState, companyList = Model })
</th> </tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Address)
</td>
<td>
#Html.DisplayFor(modelItem => item.City)
</td>
<td>
#Html.DisplayFor(modelItem => item.State)
</td> </tr>
}
</tbody>
</table>
To first obtain the list of Id's that I want to post back to the controller of the current rendition showing within the WebAPI view page I used #Model.Select(x => x.Id)
My controller index method was only changed by this
var resultSet = prev.Count == 0 ? await _context.Company.ToListAsync() :
await _context.Company.Where(x => prev.Contains(x.Id)).ToListAsync();
And my View looks like this:
#model IEnumerable<Laier_It.Models.Company>
#using (Html.BeginForm() )
{
<p>
Search By: #Html.DropDownList("SearchCategory", "")
Search For: #Html.TextBox("SearchString")
<input type="submit" value="Filter" />
</p>
}
<table class="table ">
<thead>
<tr>
<th>
#Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName, prev = #Model.Select(x => x.Id) } )
</th>
<th>
#Html.DisplayNameFor(model => model.Address)
</th>
<th>
#Html.ActionLink("City", "Index", new { orderBy = ViewBag.sortByCity, prev = #Model.Select(x => x.Id) } )
</th>
<th>
#Html.ActionLink("State", "Index", new { orderBy = ViewBag.sortByState, prev = #Model.Select(x => x.Id) } )
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Address)
</td>
<td>
#Html.DisplayFor(modelItem => item.City)
</td>
<td>
#Html.DisplayFor(modelItem => item.State)
</td>
</tr>
}
</tbody>
</table>
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
Ok, please forgive if this is newbie error.. I have been looking here at various solutions but still can't get anywhere.
I need to be able to associate a User (Id) with a Company. I have linked to SelectCompInst and have passed in the user Id.
I have a general bool flag on the companyinstitution model which I set up in the get method. The flag is correctly shown in the view. I can change the fact that its a checkbox if needed.. but I just want to put a button, link that ideally causes a postback in the list in order to allow the operator to select a company to be attached to this user.. something I am sure is done all the time.
I have seen all kinds of references to this sort of problem saying I might have to use an Edit Template? but I never find it explained why? Surely it can't be that complicated can it?
Here's my model:
[Key]
public int Id { get; set; }
public DateTime RecordCreateDate { get; set; }
[ForeignKey("Address")]
public int AddressId { get; set; }
public string Name { get; set; }
[ForeignKey("PrimaryContact")]
public string PrimaryContactId { get; set; }
public virtual Address Address { get; set; }
public virtual ApplicationUser PrimaryContact { get; set; }
[NotMapped]
public bool UserFlag { get; set; } //Bool to define if user is in this activity type when searching
Here's the View:
#model IList<Dune.Models.CompanyInstitution>
#{
ViewBag.Title = "Index";
string staffid = ViewBag.StaffId;
}
<h2>Select Company/Institution for user #ViewBag.UserName</h2>
#using (Html.BeginForm("SelectCompInst","UserAdmin",FormMethod.Post, new {id ="compselect"}))
{
#Html.AntiForgeryToken()
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model[0].Address)
</th>
<th>
#Html.DisplayNameFor(model => model[0].PrimaryContact)
</th>
<th>
#Html.DisplayNameFor(model => model[0].RecordCreateDate)
</th>
<th></th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.ElementAt(i).Name)
</td>
<td>
#{
var AddressString = Model.ElementAt(i).Address.AddressAsString();
}
#Html.DisplayFor(modelItem => AddressString)
</td>
<td>
#{ var NameString = "Not Allocated";
if (Model.ElementAt(i).PrimaryContact != null)
{
NameString = Model.ElementAt(i).PrimaryContact.FullNameNoMidString();
}
}
#Html.DisplayFor(modelItem => NameString)
</td>
<td>
#Html.DisplayFor(modelItem => modelItem.ElementAt(i).RecordCreateDate)
</td>
<td>
#Html.EditorFor(modelItem => modelItem.ElementAt(i).UserFlag)
</td>
<td>
#* Ideally I would like to put a simple 'Select' Link here and also post back the UserId
contained in Viewbag.StaffId.. I figure I can add that to the view model though
if I can get anything at all*#
</td>
</tr>
}
</table>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Select" />
</div>
}
.. and here's the controller methods:
//
// GET:
public async Task<ActionResult> SelectCompInst(string StaffId)
{
ApplicationUser user = await UserManager.FindByIdAsync(StaffId);
ViewBag.UserName = "Error User not found!";
if (user != null)
{
ViewBag.UserName = user.FullNameString();
ViewBag.StaffId = StaffId;
}
var companysInstitutions = db.CompanysInstitutions.Include(c => c.Address).Include(c => c.PrimaryContact);
// Walk the list and set the flag if this user is already mapped to this institution.
foreach (CompanyInstitution comp in companysInstitutions)
{
if (user.CompanyInstitutionStaffMember.CompanyInstituteId == comp.Id)
{
comp.UserFlag = true;
}
else
{
comp.UserFlag = false;
}
}
IList<CompanyInstitution> companys = await companysInstitutions.ToListAsync();
return View(companys);
}
//
// POST:
[HttpPost, ActionName ("SelectCompInst")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SelectComp(IList<CompanyInstitution> Company )
{
//
// Code removed because it doesnt get this far with any data :)
//
ModelState.AddModelError("", "Something failed.");
return View();
}
I have looked at the post back using fiddler and am getting nothing at all.. although the Post function is being hit. The IList Company is null...
I am sure this boils down to a fundamental misunderstanding on my part but having struggled with it for 2 days now.. have to throw myself on the mercy of the wiser community :)
I would like to put a link against each row in the table and simply pass back an id for the row and the user id.. Thanks.
Further edit after Stephens comment..
The scenario is in the editing of a User. A user can have a CompanyInstituion Id as part of its model. I wish to present a list of companies to the operator (not necessarily the User) to allow them to attach a company id to that user. So in concept its dead simple - present a list of companies to pick from. Press a 'select' link on the company line and return to the controller to sort it out. Hence I need to preserve the UserId in the Viewbag - yes I can create a viewmodel if needed but getting the company id alone would be a start)..
So that's it.. present the list, select one and return it.. I don't really want to use checkboxes anyway.. I kind of 'got there' after trying some things. I originally had the submit button in the loop before I added the checkbox. I did try putting the Company Id on the button but that didn't work either.
If the operator must select a single company that will be associated with the user, then it would make more sense your POST action to take the selected company id and the user id. That should be enough information for you to do the job:
[HttpPost, ActionName ("SelectCompInst")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SelectComp(int companyId, string staffId)
{
...
}
and then in your view you could have multiple forms and a submit button on each row of the table:
#model IList<Dune.Models.CompanyInstitution>
#{
ViewBag.Title = "Index";
string staffid = ViewBag.StaffId;
}
<h2>Select Company/Institution for user #ViewBag.UserName</h2>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model[0].Address)
</th>
<th>
#Html.DisplayNameFor(model => model[0].PrimaryContact)
</th>
<th>
#Html.DisplayNameFor(model => model[0].RecordCreateDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(m => m[i].Name)
</td>
<td>
#{
var AddressString = Model[i].Address.AddressAsString();
}
#Html.DisplayFor(modelItem => AddressString)
</td>
<td>
#{
var NameString = "Not Allocated";
if (Model[i].PrimaryContact != null)
{
NameString = Model[i].PrimaryContact.FullNameNoMidString();
}
}
#Html.DisplayFor(modelItem => NameString)
</td>
<td>
#Html.DisplayFor(modelItem => modelItem[i].RecordCreateDate)
</td>
<td>
#Html.EditorFor(modelItem => modelItem[i].UserFlag)
</td>
<td>
#using (Html.BeginForm("SelectCompInst", "UserAdmin", new { companyId = Model[i].Id, staffId = staffid }, FormMethod.Post, new { #class = "compselect" }))
{
#Html.AntiForgeryToken()
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Select" />
</div>
}
</td>
</tr>
}
</tbody>
</table>
Basically we would have an HTML form on each row of the table which will pass the necessary information to the server:
#using (Html.BeginForm(
actionName: "SelectCompInst",
controllerName: "UserAdmin",
routeValues: new { companyId = Model[i].Id, staffId = staffid },
method: FormMethod.Post,
htmlAttributes: new { #class = "compselect" }))
{
#Html.AntiForgeryToken()
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Select" />
</div>
}
I am struggling with ASP.NET MVC3 when trying to update data via Entity Framework. The process is as followed:
I have this Model:
public class Bet
{
public string BetID { get; set; }
public int FixtureID { get; set; }
public string Better { get; set; }
public int GoalsHome { get; set; }
public int GoalsGuest { get; set; }
public virtual Fixture { get; set; }
}
In the controller I filter the table via a where statement to get only the entrys that match the user:
[HttpGet]
public ActionResult Index()
{
var user = User.Identity.Name;
var model = _db.Bets.Where(t => t.Better == user);
return View(model);
}
The View is two parts, one that takes care of the table headers, and the other that lists all bets of the user:
<fieldset>
<legend>Bets</legend>
<table>
<tr>
<th>
Kickoff
</th>
<th>
Home Team
</th>
<th>
Guest Team
</th>
<th>
Group
</th>
<th>
Bet
</th>
</tr>
#Html.EditorFor(m => m) //this is resolved in an self made EditorTemplate
</table>
The EditorTemplate:
#model Betting.Models.Bet
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Fixture.Kickoff)
</td>
<td>
#Html.DisplayFor(modelItem => Model.Fixture.HomeTeam)
</td>
<td>
#Html.DisplayFor(modelItem => Model.Fixture.GuestTeam)
</td>
<td>
#Html.DisplayFor(modelItem => Model.Fixture.Group)
</td>
<td>
#Html.TextBoxFor(modelItem => Model.GoalsHome, new { style = "width: 30px" }):
#Html.TextBoxFor(modelItem => Model.GoalsGuest, new { style = "width: 30px" })
</td>
#Html.HiddenFor(modelItem => Model.FixtureID)
</tr>
Back in the controller I try to update the model:
[HttpPost]
public ActionResult Index(FormCollection collection)
{
var user = User.Identity.Name;
var model = _db.Bets.Where(t => t.Better == user);
TryUpdateModel(model);
_db.SaveChanges();
return RedirectToAction("Index");
}
This does absolutely nothing. The Entity Framework won't update the database at all. I even separated the table so that the resulting bet tags in the html file will be distinguishable (note the [index] before the name value:
<input data-val="true" data-val-number="The field GoalsHome must be a number." data-val-required="The GoalsHome field is required." name="[1].GoalsHome" style="width: 30px" type="text" value="3" />:
<input data-val="true" data-val-number="The field GoalsGuest must be a number." data-val-required="The GoalsGuest field is required." name="[1].GoalsGuest" style="width: 30px" type="text" value="0" />
Can somebody tell me why the Entity Framework isn't updating the database? Is there something wrong with the object mapping?
I was having the same issue before. MSDN can be pretty confusing on this subject but halfway down the page states that if you are creating your POCO entities without proxycreationenabled, you will have to call detectchanges before calling savechanges.
TryUpdateModel(model);
_db.DetectChanges();
_db.SaveChanges();
Ok, it seems that I found a way to update the database without using the TryUpdateModel method. Since the items sent in the html post are distinguishable, I can add a parameter to the controller method which holds the bets I get from the view. Then I iterate over the results from the database and update the fields that changed with the field values of the bets from the view:
[HttpPost]
public ActionResult Index(FormCollection collection, List<Bet> bets)
{
var user = User.Identity.Name;
var model = _db.Bets.Where(t => t.Better== user);
int i = 0;
foreach (var bet in model)
{
Bet the_Bet= bets.Find(
delegate(Bet _bet)
{
return _bet.BetID == bet.BetID;
});
bet.GoalsHome= the_Bet.GoalsHome;
bet.GoalsGuest= the_Bet.GoalsGuest;
i++;
}
_db.SaveChanges();
return RedirectToAction("Index");
}
I wonder if there is still a way to get it work with the TryUpdateModel method.