Access view model data in controller - c#

In my application I have created a view model to show data in the View.
I created view model as this.
[NotMapped]
public class EmpExcelUploadViewModel {
public int CompanyId {
get;
set;
}
public int EmpNo {
get;
set;
}
public string EmpName {
get;
set;
}
public int EmpDep {
get;
set;
}
public int EmpDes {
get;
set;
}
}
In the controller I have assigned data to the view model and passed to the view. Just to show the data to the user, there is no editing or deleting data from the user view. That stage is works fine.
This is the view
#model IEnumerable
<Asp_PASMVC.ViewModels.EmpExcelUploadViewModel>
#{
ViewBag.Title = "Import";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
#using (Html.BeginForm("Import", "M_Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="card card-primary">
<div class="card-header">
<h1 class="card-title"><b>Upload Employees from Excel</b></h1>
</div>
<div>
<br />
#Html.Raw(ViewBag.Error)
<h4><span>Select Excel File</span></h4>
<input type="file" name="excelFile" class="btn btn-warning" />
<br />
</div>
<div class="row">
<div class="col-md-1">
<br />
<br />
<input type="submit" value="Import" class="btn btn-info" />
</div>
<div class="col-md-1">
<br />
<br />
<input type="button" value="Upload" class="btn btn-success" onclick="location.href='#Url.Action("UploadEmployees", "M_Employee")'" />
</div>
</div>
<div class="card-body p-0">
<table class="table table-striped">
<tr>
<th>Company</th>
<th>EmpId</th>
<th>EmpName</th>
<th>Department</th>
<th>Dessignation</th>
</tr>
#if (ViewBag.EmpList != null)
{
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.CompanyId)
</td>
<td>
#Html.DisplayFor(modelItem => item.EmpNo)
</td>
<td>
#Html.DisplayFor(modelItem => item.EmpName)
</td>
<td>
#Html.DisplayFor(modelItem => item.EmpDep)
</td>
<td>
#Html.DisplayFor(modelItem => item.EmpDes)
</td>
</tr>
}
}
</table>
</div>
<!-- /.card-body -->
</div>
}
</div>
I want to know, when user clicks to the Upload button, that the data already in the view model I want to pass to the database. So how can I access the data already in the view model? I'm still learning MVC and I don't know if it's possible or not.
This is what I tried. Here in the empExcel getting error that EmpExcelUploadViewModel does not contain public instance.
public ActionResult UploadEmployees(EmpExcelUploadViewModel empExcel) {
try {
foreach(var item in empExcel) // error in empExcel {
int ComId = item.CompanyId;
int EmpNo = item.EmpNo;
string EmpName = item.EmpName;
int DepId = item.EmpDep;
int DesId = item.EmpDes;
var isEmployeeExists = (from e in db.CreateEmployee where e.EmpNo == EmpNo select e.Id).First();
if (isEmployeeExists != 0) {
M_Employee e = new M_Employee();
e.CompanyId = ComId;
e.EmpNo = EmpNo;
e.EmpName = EmpName;
e.DepId = DepId;
e.DesignId = DesId;
e.Status = true;
e.CreateDate = DateTime.Now;
e.CreateBy = int.Parse(((System.Security.Claims.ClaimsIdentity) User.Identity).FindFirst("UserId").Value);
db.CreateEmployee.Add(e);
db.SaveChanges();
} else {
M_Employee m_Employee = db.CreateEmployee.First(x => x.Id == isEmployeeExists);
m_Employee.DepId = DepId;
m_Employee.DesignId = DesId;
db.SaveChanges();
}
}
} catch (Exception ex) {
ViewBag.Error = "Error " + ex;
}
ViewBag.Error = "All Records uploaded to the database";
ViewBag.EmpList = null;
return View("Import");
}
}

Instead of saving data in the view, you should do it in the controller.
In the controller, if there's any error saving the data to the database, set a separate error flag indicating this. Then, the View uses this flag to determine what to display

Related

ASP.NET Core 2.1 Edit w/ Concatenated Primary Key

I'm a student using ASP.NET Core and related technologies for the first time. I've spent hours searching for a solution to this problem with no luck, so if anyone could help or point me at a tutorial to fix this, I would be very grateful.
My group and I are trying to build a web app to interface with an existing SQL database which has a lot of tables using concatenated primary keys. We can't change that. I used this tutorial to get our CRUD edit pages to even show, but if I try to submit any changes via that Edit page, I get an error like this:
No webpage was found for the web address: https://localhost:44311/GenCollectionSamplers/Edit
After some debugging, we're pretty sure this is a problem with the POST Edit method in the Controller getting null IDs, but we cannot figure out how to pass the correct IDs into the POST Edit method. The IDs are present in the GET Edit message.
Let me know if more code is needed.
Edit GET and POST from Controller:
// GET: GenCollectionSamplers/Edit/5
public async Task<IActionResult> Edit(double? peopleID, double? colID)
{
if (peopleID == null || colID == null)
{
return NotFound();
}
var genCollectionSamplers = await _context.GenCollectionSamplers.FindAsync(peopleID, colID);
if (genCollectionSamplers == null)
{
return NotFound();
}
ViewData["FkPeopleId"] = new SelectList(_context.GenPeople, "PkPeopleId", "PkPeopleId", genCollectionSamplers.FkPeopleId);
ViewData["FkCollectionId"] = new SelectList(_context.GenCollections, "CollectionId", "CollectionId", genCollectionSamplers.FkCollectionId);
return View(genCollectionSamplers);
}
// POST: GenCollectionSamplers/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(double peopleID, double colID, [Bind("FkPeopleId,FkCollectionId")] GenCollectionSamplers genCollectionSamplers)
{
//Causes error because peopleID and colID are 0
if (peopleID != genCollectionSamplers.FkPeopleId || colID != genCollectionSamplers.FkCollectionId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(genCollectionSamplers);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!GenCollectionSamplersExists(genCollectionSamplers.FkPeopleId) || !GenCollectionSamplersExists(genCollectionSamplers.FkCollectionId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["FkPeopleId"] = new SelectList(_context.GenPeople, "PkPeopleId", "PkPeopleId", genCollectionSamplers.FkPeopleId);
ViewData["FkCollectionId"] = new SelectList(_context.GenCollections, "CollectionId", "CollectionId", genCollectionSamplers.FkCollectionId);
return View(genCollectionSamplers);
}
Model:
public partial class GenCollectionSamplers
{
[Required]
[Display(Name = "Fk People Id")]
public double FkPeopleId { get; set; }
[Required]
[Display(Name = "Fk Collection Id")]
public double FkCollectionId { get; set; }
[Required]
[Display(Name = "Fk Collection")]
public GenCollections FkCollection { get; set; }
[Required]
[Display(Name = "Fk People")]
public GenPeople FkPeople { get; set; }
}
Edit view:
#model {path removed}.Models.GenCollectionSamplers
#{
ViewData["Title"] = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
<h4>GenCollectionSamplers</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FkCollectionId" class="control-label"></label>
<input asp-for="FkCollectionId" class="form-control" />
<span asp-validation-for="FkCollectionId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FkPeopleId" class="control-label"></label>
<input asp-for="FkPeopleId" class="form-control" />
<span asp-validation-for="FkPeopleId" class="text-danger"></span>
</div>
<input type="hidden" asp-for="FkPeopleId" />
<input type="hidden" asp-for="FkCollectionId" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Part of Index view:
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => efModel.FkCollectionId)
</th>
<th>
#Html.DisplayNameFor(model => efModel.FkPeople)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FkCollection.CollectionId)
</td>
<td>
#Html.DisplayFor(modelItem => item.FkPeople.PkPeopleId)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId }) |
#Html.ActionLink("Details", "Details", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId }) |
#Html.ActionLink("Delete", "Delete", new { peopleID = item.FkPeopleId, colID = item.FkCollectionId })
</td>
</tr>
}
</tbody>
</table>
Why do you have the hidden fields on your Edit view:?
<input type="hidden" asp-for="FkPeopleId" />
<input type="hidden" asp-for="FkCollectionId" /
It is this that gets bound to the Model when you submit the HttpPost and since they do not have any values assigned, you are getting 0. Get rid of these two hidden fields. The rendered page will create the hidden fields at run-time (inspect your rendered markup for Edit page) along with __RequestVerificationToken that knows which instance of this Model should be validated and then updated.

Return Viewmodel to HttpPost ActionResult

I'm trying to update my collection of users' roles in my ASP.NET Identity project, but I'm currently stuck because I'm getting a null UsersAndRolesDictionary property in the ViewModel sent to my [HttpPost] method.
Here is my ViewModel, UpdateUserRolesViewModel:
namespace Project_Name.Models
{
public class UpdateUserRolesViewModel
{
public IDictionary<ApplicationUser, ICollection<IdentityUserRole>> UsersAndRolesDictionary { get; set; } // <-- This is returning null currently
}
}
Here's my HomeController's methods:
[Authorize(Roles = "Admin")]
public ActionResult RoleManager()
{
ViewBag.Message = "Role Management Page";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var users = databaseContext.Users.Include(u => u.Roles); // Get all users from the Database and their Roles
var newDict = new Dictionary<ApplicationUser, ICollection<IdentityUserRole>>();
// Add each user and their roles to the dictionary
foreach (var user in users)
{
newDict.Add(user, user.Roles);
}
// Update the ViewModel with the collection of users and roles
var updateUserRolesViewModel = new UpdateUserRolesViewModel {UsersAndRolesDictionary = newDict};
return View(updateUserRolesViewModel);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdateUsersRolesAsync(UpdateUserRolesViewModel updateUserRolesViewModel)
{
try
{
//TODO: Attempt to update the user roles or delete the user
return View("RoleManager");
}
catch
{
//TODO: Properly catch errors
return View("RoleManager");
}
}
Here is my View, RoleManager:
#using Project_Name.Models
#model UpdateUserRolesViewModel
#{
ViewBag.Title = "Role Manager";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var roles = databaseContext.Roles; // Get all Roles from the database, use this to compare against
}
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
#using (Html.BeginForm("UpdateUsersRolesAsync", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Roles</th>
<th>Delete User?</th>
</tr>
</thead>
<tbody>
#{
int i = 0; // Used to make unique IDs for the user's table row, and deleteUserCheckbox
int j = 0; // Used to make unique IDs for the role checkboxes
foreach (var user in Model.UsersAndRolesDictionary.Keys)
{
i++;
<tr id="userTableRow_#i">
<td>#user.Email</td>
<td>
#* Show each role availabe as a checkbox. Check them if the user has that role. *#
#foreach (var role in roles)
{
#Html.CheckBox("userRoleCheckbox_" + j++, user.Roles.Any(identityUserRole => identityUserRole.RoleId.Contains(role.Id)))
<span>#role.Name</span>
<br />
}
</td>
<td>
#Html.CheckBox("deleteUserCheckbox_" + i)
<span>Delete User</span>
</td>
</tr>
}
}
</tbody>
</table>
</div>
#* Reset and Submit buttons *#
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="reset" class="btn btn-danger btn-block" value="Reset" />
</div>
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="submit" class="btn btn-primary btn-block" value="Submit" />
</div>
</div>
}
</div>
</div>
I'm using the dictionary UsersAndRolesDictionary to collect all the users and their roles, then enumerating through that to produce my view in the form of a table.
I'm hoping to change the checkbox values of potential multiple users, then passing that updated ViewModel to my [HttpPost] UpdateUsersRolesAsync method in order to update my user roles, but right now I'm getting a null value for the UsersAndRolesDictionary property and I'm not sure why or how to fix it.
Thanks to Stephen Muecke's links/answers in the comments I was able to answer this question. See my answer post below.
Following the suggestions of Stephen Muecke in the comments, I have gotten a valid ViewModel to be returned.
Added/updated three ViewModels that combine together:
The first being RoleViewModel:
public class RoleViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Second being UserViewModel:
public class UserViewModel
{
public string Id { get; set; }
public string Email { get; set; }
public List<RoleViewModel> RoleViewModels { get; set; }
public bool DeleteUser { get; set; } // Doesn't work yet, might be in the wrong place
}
And finally the third being an updated version of UpdateUserRoleViewModel:
public class UpdateUserRolesViewModel
{
public int Id { get; set; }
public List<UserViewModel> UserViewModels { get; set; }
}
In my updated HomeController are the methods again:
[Authorize(Roles = "Admin")]
public ActionResult RoleManager()
{
ViewBag.Message = "Role Management Page";
var databaseContext = new ApplicationDbContext(); // Get the Database Context
var users = databaseContext.Users.Include(u => u.Roles).ToList(); // Get all users from the Database and their Roles
// Create the UpdateUserRolesViewModel
var updateUserRolesViewModel = new UpdateUserRolesViewModel
{
Id = 0, // Not sure what else the Id would be
UserViewModels = new List<UserViewModel>()
};
// Add each user to the UserViewModels list
for (int i = 0; i < users.Count(); i++)
{
var userViewModel = new UserViewModel
{
Id = users.AsEnumerable().ElementAt(i).Id,
Email = users.AsEnumerable().ElementAt(i).UserName,
RoleViewModels = new List<RoleViewModel>(),
DeleteUser = false
};
// Add each role from the Roles table to the RoleViewModels list, check if user has that role
foreach (var role in databaseContext.Roles)
{
var roleViewModel = new RoleViewModel
{
Id = role.Id,
Name = role.Name,
IsSelected = users.AsEnumerable().ElementAt(i).Roles.Any(identityUserRole => identityUserRole.RoleId.Contains(role.Id))
};
userViewModel.RoleViewModels.Add(roleViewModel);
}
updateUserRolesViewModel.UserViewModels.Add(userViewModel);
}
return View(updateUserRolesViewModel);
}
[HttpPost]
[Authorize(Roles = "Admin")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdateUsersRolesAsync(UpdateUserRolesViewModel updateUserRolesViewModel)
{
try
{
// Attempt to update the user roles
foreach (var user in updateUserRolesViewModel.UserViewModels)
{
// Delete user
//TODO: Prompt user to confirm deletion if one or more people are being deleted
if (user.DeleteUser)
{
var userToDelete = await UserManager.FindByIdAsync(user.Id); // Get the ApplicationUser object of who we want to delete
await UserManager.DeleteAsync(userToDelete); // Delete the user
continue; // Don't try to update the roles of a deleted user.
}
// Remove all roles from the User
var rolesToRemove = await UserManager.GetRolesAsync(user.Id);
await UserManager.RemoveFromRolesAsync(user.Id, rolesToRemove.ToArray());
// Add roles to the User
foreach (var roleViewModel in user.RoleViewModels.Where(r => r.IsSelected))
{
await UserManager.AddToRoleAsync(user.Id, roleViewModel.Name);
}
}
return RedirectToAction("RoleManager");
}
catch
{
//TODO: Properly catch errors
return RedirectToAction("RoleManager");
}
}
Finally, here is my View, RoleManager
#using Project_Name.ViewModels
#model UpdateUserRolesViewModel
#{
ViewBag.Title = "Role Manager";
}
#* Debugging text *#
#foreach (var user in Model.UserViewModels)
{
<div>User ID: #user.Id</div>
<div>User Name: #user.Email</div>
<p>
#foreach (var roleViewModel in user.RoleViewModels.Where(r => r.IsSelected))
{
<div>Role ID: #roleViewModel.Id</div>
<div>Role Name: #roleViewModel.Name</div>
}
</p>
<hr />
}
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
#using (Html.BeginForm("UpdateUsersRolesAsync", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
<div class="form-group">
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Roles</th>
<th>Delete User?</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.UserViewModels.Count; i++)
{
<tr id="userTableRow_#i">
<td>
#Html.HiddenFor(m => m.UserViewModels[i].Id)
#Html.HiddenFor(m => m.UserViewModels[i].Email)
#Model.UserViewModels[i].Email
</td>
<td>
#for (int j = 0; j < Model.UserViewModels[i].RoleViewModels.Count; j++)
{
#Html.HiddenFor(m => m.UserViewModels[i].RoleViewModels[j].Id)
#Html.HiddenFor(m => m.UserViewModels[i].RoleViewModels[j].Name)
#Html.CheckBoxFor(m => m.UserViewModels[i].RoleViewModels[j].IsSelected)
#Html.DisplayTextFor(m => m.UserViewModels[i].RoleViewModels[j].Name)
<br/>
}
</td>
<td>
#Html.CheckBoxFor(m => m.UserViewModels[i].DeleteUser)
#Html.DisplayNameFor(m => m.UserViewModels[i].DeleteUser)
</td>
</tr>
}
</tbody>
</table>
</div>
#* Reset and Submit buttons *#
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="reset" class="btn btn-danger btn-block" value="Reset" />
</div>
<div class="col-lg-2 col-lg-push-8 col-md-2 col-md-push-8 col-sm-2 col-sm-push-8 col-xs-2 col-xs-push-8">
<input type="submit" class="btn btn-primary btn-block" value="Submit" />
</div>
</div>
}
</div>
</div>
This now updates the user's Roles, and Deletes them (though there is no confirmation check so be careful with that!)

Unable to pass value to controller from view through viewmodel

I'm pretty sure my brain is friend and this is something I'm going to laugh at tomorrow morning, but unfortunately I'm stuck on this portion and am asking for assistance.
I have a ViewModel:
public class HousingDetailsViewModel : AppViewModel
{
DataContext db = new DataContext();
public List<string> units { get; set; }
public List<AvailableHousing> availableHousings { get; set; }
public Person person { get; set; }
public OccupiedHousing currentHousing { get; set; }
public OccupiedHousing newHousing;
public HousingDetailsViewModel(int? id)
{
units = db.AvailableHousings.OrderBy(ah => ah.Unit).Select(h => h.Unit).Distinct().ToList();
availableHousings = db.AvailableHousings.Where(h => h.Available == true).OrderBy(h => h.Bed)
.OrderBy(h => h.Room).ToList();
currentHousing = db.OccupiedHousing.Include(o => o.AvailableHousing)
.Include(o => o.Person).Where(o => o.PersonID == id && o.CurrentHousing == true).FirstOrDefault();
person = db.Persons.Find(id);
newHousing = new OccupiedHousing();
}
}
My controller methods for this view:
public ActionResult Details(int? id)
{
return View(new HousingDetailsViewModel(id));
}
[HttpPost]
public ActionResult Move(OccupiedHousing newHousing, int? personID)
{
newHousing.PersonID = personID;
newHousing.DateArrived = DateTime.Now;
newHousing.CurrentHousing = true;
newHousing.AvailableHousingID = housingID;
db.OccupiedHousings.Add(newHousing);
db.SaveChanges();
return RedirectToAction("Index", new HousingViewModel());
}
And my form works fine for all of my fields except for 1, and that's the AvailableHousingID. I've tried setting a hidden value. I put a breakpoint where I set the value of the hidden field and I watched it change, but it didn't make it to the controller. So I changed it to a form submission and tried to catch it as a routevalue and that didn't work either. I'm at a loss, can anyone see where I'm going wrong?
EDIT: Adding View
#model AppName.ViewModels.HousingDetailsViewModel
#{
ViewBag.Title = "Housing Details";
}
#Html.BeginForm("Move", "Housing", new { personID = #Model.person.ID }, FormMethod.Post, new { })
<script>
function setID(id) {
$('#HiddenHousingID').val(id);
$('#HiddenSubmit').click();
}
</script>
<h2>Housing Details</h2>
<div class="row">
<div class="col-xs-12 container">
<div class="col-xs-5">
<img src="//placehold.it/150x200/77CCDD/66BBCC" class="img-responsive" />
</div>
<div class="col-xs-7">
<h4>#Model.person.ComboName</h4>
<h4>#Model.currentHousing.AvailableHousing.Unit - #Model.currentHousing.AvailableHousing.Room - #Model.currentHousing.AvailableHousing.Bed</h4>
<h4>#Model.person.DateOfBirth.Value.ToShortDateString()</h4>
#Html.HiddenFor(m => m.newHousing.AvailableHousingID, new { id = "HiddenHousingID", name = "newHousing.AvailableHousingID")}
</div>
</div>
</div>
<div class="row">
#foreach (var unit in Model.units)
{
<div class="col-xs-6">
<div class="panel panel-primary">
<div class="panel-heading">
<span class="panel-title">
#unit
</span>
</div>
<div class="panel-body">
<table id="MoveHousingTable" class="table table table-condensed table-striped">
<thead>
<tr>
<th>
Available Housing
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var housing in Model.availableHousings.Where(h => h.Unit == unit))
{
<tr>
<td>
#housing.Room -
#housing.Bed
</td>
<td>
<input type="button" value="Select" name="select" onclick="setID(#housing.ID)" />
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
</div>
<input type="submit" class="hidden" id="HiddenSubmit">
}
For the route :
#Html.BeginForm("Move", "Housing", new { personID = #Model.person.ID , housingID= #Model.newHousing.AvailableHousingID}, FormMethod.Post, new { })

mvc 5, model is List, submited model is null

I need to submit list of skills. when i try to do this submitted model is null. But in Fidler I see that data was send.
public class MvcSkill
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Display(Name = "Category Name")]
[StringLength(128, ErrorMessage = "Max length - {0} symbols")]
[Required(ErrorMessage = "Please enter your last name")]
public string Name { get; set; }
public int Level { get; set; }
[Required(ErrorMessage ="Choose skill category")]
public string CategoryName { get; set; }
}
Partial view for each skill (strange input for skill level is bootstrap-star-rating:
#using MvcApp.Infrastructure;
#model MvcApp.ViewModels.MvcSkill
<tr>
<td>
#Html.HiddenFor(x => x.Id)
</td>
<td>
#Html.HiddenFor(x => x.Name)
#Html.DisplayFor(x => x.Name)
</td>
<td>
#Html.HiddenFor(x => x.CategoryName)
#Html.DisplayFor(x => x.CategoryName)
</td>
<td>
<input for="Level" id="#Model.Id" value="#Model.Level" name="Level" type="number" class="rating-loading" data-size="xs" data-min="0" data-max="5" data-step="1" data-show-clear="false">
</td>
#{
var identity = (CustomIdentity)User.Identity;
if (identity.Roles.FirstOrDefault(r => r == "Administrator") != null)
{
<td>
#Html.RouteLink("Edit", new { controller = "Administrator", action = "EditSkill", id = Model.Id })
</td>
<td>
#Html.RouteLink("Remove", new { controller = "Administrator", action = "RemoveSkill", id = Model.Id })
</td>
}
}
</tr>
View for list of skills:
#model IList<MvcApp.ViewModels.MvcSkill>
#{
ViewBag.Title = "Skills";
}
#using (Html.BeginForm("Index","Skill", Model))
{
<table>
#foreach (var s in Model)
{
#Html.Partial("_Skill", s)
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="submit" type="submit" value="Update" class="btn btn-default" />
</div>
</div>
}
#section scripts{
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" rel="stylesheet">
<link href="~/Content/star-rating.css" media="all" rel="stylesheet" type="text/css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"></script>
<script src="~/Scripts/star-rating.js" type="text/javascript"></script>
#foreach (var item in Model)
{
string id = item.Id.ToString();
<script>
$(document).on('ready', function () {
$('##id').rating({
step: 1,
defaultCaption: 'Unknown',
starCaptions: { 0: 'Unknown', 1: 'Beginner', 2: 'Elementary', 3: 'Intermediate', 4: 'Advanced', 5: 'Profi' },
starCaptionClasses: function (val) { return 'text-success'; }
}).on('rating.change', function (event, value, caption) {
Model.Skills.FirstOrDefault(s => s.Key.Id.ToString() == id).Value = value;
});
});
</script>
}
}
And controller method:
[HttpPost]
public ActionResult Index(List<MvcSkill> skillModel)
{
//Do something...
return View();
}
Fidler text:
Id=1&Name=c%2B%2B&CategoryName=Programing&Level=5&Id=3&Name=PHP&CategoryName=Programing&Level=3&Id=6&Name=JAVA&CategoryName=Programing&Level=3&Id=7&Name=Name&CategoryName=.Net+Frameworks&Level=5
You have to manually add indexes so MVC framework can successfully bind data. Something like:
#using (Html.BeginForm("Index","Skill", Model))
{
<table>
#for (int i = 0; i < Model.Length; i++)
{
<tr>
<td>
#Html.Hidden("[" + i + "].Id");
</td>
<td>
#Html.DisplayFor(m => Model.ElementAt(i).Name)
#Html.Hidden("[" + i + "].Name");
</td>
<td>
#Html.DisplayFor(m => Model.ElementAt(i).CategoryName)
#Html.Hidden("[" + i + "].CategoryName");
</td>
</tr>
}
</table>
}
Or, as stated in comments you can use BeginCollectionItem (which I personally use). What BeginCollectionItem does is, generally it adds GUIDs as index fro you. Source code can be found here and you can get familiar with it here.

How to insert data within a table?

I need to insert a data from a table which has a foreach loop inside. I used to work with webforms so I'm newbie to mvc. I always encountered this scenario in webforms using gridview control and just implement the findcontrol command and it works fine. But in mvc, I could hardly get the solutions how to do it. I already search online but can't find any articles that fits my needs for this type of issue. So far, what I've got is if I insert a data from a first row of the table it will insert the record but if I insert them from the second, third, and so fourth, it will no longer insert the records into my database table. How do I make this perfectly be working?
Heres my model,view and controller to help you resolve the issue. Thanks...
Model
public class CommentModel
{
public int Id { get; set; }
[Required(ErrorMessage="Don't miss to put your name.")]
public string name { get; set; }
[Required(ErrorMessage = "Don't leave your comments empty.")]
public string comment { get; set;}
}
public class ReplyModel
{
public int idrep { get; set; }
public string namerep { get; set; }
public string reply { get; set; }
}
public class CreateViewModel
{
public CommentModel CreateComment { get; set; } // this line is optional
public ReplyModel CreateReply { get; set; }
public List<CommentModel> Comments { get; set; }
public List<ReplyModel> Replies { get; set; }
}
Repository:
public class ReplyRepository
{
private ProfileDataContext3 Reprepository;
public ReplyRepository()
{
Reprepository = new ProfileDataContext3();
}
public IEnumerable<ReplyModel> GetAllComments()
{
List<ReplyModel> profiles = new List<ReplyModel>();
var prof = from profile in Reprepository.RepTabs
orderby profile.Id descending
select profile;
var user = prof.ToList();
foreach (var item in user)
{
profiles.Add(new ReplyModel()
{
idrep = item.Id,
namerep = item.Name,
reply = item.Replies
});
}
return profiles;
}
//declaring methods for inserting records
public void InsertReply(ReplyModel profile)
{
var details = new RepTab()
{
Id=profile.idrep,
Name = profile.namerep,
Replies = profile.reply
};
Reprepository.RepTabs.Add(details);
Reprepository.SaveChanges();
}
}
Controller
public ActionResult PostComment()
{
var vModel = new CreateViewModel();
vModel.Comments = comrepository.GetAllComments().ToList();
vModel.Replies = replyrepository.GetAllComments().ToList();
return View(vModel);
}
[HttpPost]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult PostComment(CommentModel com,string butname, ReplyModel rep)
{
switch(butname)
{
case "Post Comments":
if (ModelState.IsValid)
{
comrepository.InsertComment(com); //this is to insert the records
}
//this is to display the records inserted
var vModel = new CreateViewModel();
vModel.Comments = comrepository.GetAllComments().ToList();
vModel.Replies = replyrepository.GetAllComments().ToList();
return View(vModel);
case "Post Reply":
if (ModelState.IsValid)
{
replyrepository.InsertReply(rep); //this is to insert the records
}
//this is to display the records inserted
var vModel2 = new CreateViewModel();
vModel2.Comments = comrepository.GetAllComments().ToList();
vModel2.Replies = replyrepository.GetAllComments().ToList();
return View(vModel2);
default:
return null;
}
}
View
#model MyFirstMVCApp.Models.CreateViewModel
#{
ViewBag.Title = "PostComment";
}
<h2>Post Comment</h2>
<br />
#using (Html.BeginForm("PostComment", "Profile", FormMethod.Post, new { }))
{
#Html.ValidationSummary("Unable to Post Comment. Please correct the errors and try again...")
<fieldset>
<legend>CommentModel</legend>
<div class="editor-label">
<label for="name">Name</label>
</div>
<div class="editor-field">
<input type="text" id="name" name="name" />
<span class="field-validation-valid" data-valmsg-for="name" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="comment">Post your Comment here:</label>
</div>
<div class="editor-field">
<textarea id="comment" name="comment" style="width:500px;height:100px;resize:none" ></textarea>
<span class="field-validation-valid" data-valmsg-for="comment" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Post Comments" name="butname" />
#Html.ActionLink("See Comments", "DisplayComment")
</p>
<br />
</fieldset>
}
<br />
<h2>Comments</h2>
<br />
#using (Html.BeginForm("PostComment", "Profile", FormMethod.Post, new { }))
{
<table>
#foreach (var item in Model.Comments)
{
<tr>
<td>
<div class="editor-field" style="display:none;margin-bottom:10px;margin-top:10px">
#Html.TextBoxFor(m => m.CreateComment.Id)
</div>
<div style="font-weight:bold;"> #Html.DisplayFor(modelItem => item.name) </div>
<p style ="margin-top:0px;margin-bottom:0px; border-radius: 4px 4px 4px 4px; max-width :500px; min-height :5px; display :block; background-color: #CCCCFF"> #Html.DisplayFor(modelItem => item.comment) </p>
<p style="margin-top:2px;margin-bottom:0px"> <input type="button" id="like" name="like" value="Like" style="color:blue;border:0px;background-color:inherit;cursor:pointer" /> <input type="button" id="Reply" name="Reply" value="Replie(s)" style="color:blue;border:0px;background-color:inherit;cursor:pointer" /></p>
<div id="divrep" style="position:relative;left:50px; overflow:auto;margin-top:0px">
<table>
#foreach (var item2 in Model.Replies)
{
<tr>
<td>
<p style ="margin-top:0px;margin-bottom:0px; border-radius: 4px 4px 4px 4px; max-width :445px; min-height :5px; display :block; background-color: #CCCCFF;">#Html.DisplayFor(modelItem => item2.reply) </p>
<br />
</td>
</tr>
}
</table>
</div>
<input type="text" id="idrep" name="idrep" value="#Html.DisplayFor(modelItem=>item.Id)" />
<span class="field-validation-valid" data-valmsg-for="idrep" data-valmsg-replace="true"></span>
<br />
<input type="text" id="namerep" name="namerep" />
<span class="field-validation-valid" data-valmsg-for="namerep" data-valmsg-replace="true"></span>
<br />
<textarea id="reply" name="reply" style="width:500px;height:100px;resize:none" ></textarea>
<span class="field-validation-valid" data-valmsg-for="reply" data-valmsg-replace="true"></span>
<br />
<input type="submit" value="Post Reply" name="butname" />
</td>
</tr>
}
</table>
}
<br />
<br />
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section scripts
{
<script type ="text/jscript" src="JQuery/jquery-1.4.3.min.js">
</script>
<script type="text/javascript">
$('body').keypress(function(e) {
if (e.which == 13) {
$("input[value='Post Comments']").trigger('click');
}
});
</script>
}
The problem is the naming of your controls, you have multiple controls with the same name and the DefaultModelBinder will only bind the first one (i.e the first row) and ignore the rest. I suggest a different approach using jquery to post new comments and avoid having to refresh the page each time.
View
...
#using (Html.BeginForm()) {
<div>
#Html.LabelFor(m => m.CreateComment.Name)
#Html.TextBoxFor(m => m.CreateComment.Name)
#Html.ValidationMessageFor(m => m.CreateComment.Name)
// repeat for CreateComment.Comment property
<input type="submit" id="post-comment" value="Submit Comment" />
</div>
}
...
Script
$('form').submit(function(e) {
e.preventDefault(); // prevent submission
}
$('#post-comment').click(function() {
// get values for postback
var inputs = $(this).closest('div').find('input');
var name = inputs.first().val();
var comment = inputs.last().val();
var url = '#Url.Action("PostComment", "Profile")';
$.post(url, { Name: name, Comment: comment }, function(data) {
if(data) {
inputs.val(''); // clear existing inputs
// create a new element for the comment and add to the DOM, for example
var para = $('div></div>).text(comment).appendTo(something);
} else {
// display an error message
}
}
}
and add a similar script for posting replies, except use a class name not id for the button so there are no duplicate id's
Controller
[HttpPost]
public ActionResult PostComment(CommentModel model)
{
// save the comment and if successful
return Json(true);
else
return null;
}
For posting back replies, in your foreach loop, add a hidden input for the respective comment ID and include that in the data to post back to a separate action
[HttpPost]
public ActionResult PostReply(ReplyModel model)
{
...

Categories