How to save changes to an Edit view in MVC - c#

I have an MVC Application which displays items from a database. Each item has a Details and an Edit view. The Edit view allows you user to make changes to the record by displaying the values. The problem I am having is that I cannot get the changes that are made to save.
I believe that the first edit action result is used to display the records when the user clicks on "Edit" in the "Index" view. The second edit action result should be callled when the user presses the "Save Changes" button in the Edit view.
When debugging I did find that the values such as "item.Item_No_" where not being populated by the values in the View
I have provided code from my Controller,
Any help would be greatly appreciated,
Nick
public ActionResult Edit(string itemNo)
{
LinnWorksItemViewModel model = new LinnWorksItemViewModel(_data.Connection.ConnectionString, itemNo);
return View(model);
// Declare item no here?
foreach (string upload in Request.Files)
{
if (!Request.Files[upload].HasFile()) continue;
string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string fileName = Path.GetFileName(Request.Files[upload].FileName);
Request.Files[upload].SaveAs(Path.Combine(path, fileName));
}
return View(model);
}
[HttpPost]
public ActionResult Edit(Item item, string itemNo)
{
LinnWorksItemViewModel model = new LinnWorksItemViewModel(_data.Connection.ConnectionString, itemNo);
try
{
Item _linnItem = _data.Items
.Where(x => x.Item_No_ == item.Item_No_)
.FirstOrDefault();
_linnItem.Description = item.Description;
_linnItem.Search_Description = item.Extended_Description;
_linnItem.Default_Barcode = item.Default_Barcode;
_linnItem.Variations_Group_Code = item.Variations_Group_Code;
_linnItem.LinnWork_Category_Code = item.LinnWork_Category_Code;
_linnItem.Unit_Cost = item.Unit_Cost;
_linnItem.Unit_Price = item.Unit_Price;
_linnItem.Shipping_Agent_Code = item.Shipping_Agent_Code;
_linnItem.Package_Group_Code = item.Package_Group_Code;
_linnItem.Stockkeeping_Units = item.Stockkeeping_Units;
_linnItem.Weight = item.Weight;
_linnItem.Width = item.Width;
_linnItem.Height = item.Height;
_linnItem.Depth = item.Depth;
_data.SubmitChanges();
return RedirectToAction("Index");
}
catch (Exception ex)
{
string message = ex.Message;
return View();
}
}

Related

Passing multiple forms from View to Controller in ASP.NET MVC

I'm creating an application that books Guests in hotelrooms. In the HttpGet I pass a ReservationViewModel from my Controller to the View. This VM contains all the reservationdetails and 2 empty Guest objects (or however many). For each Guest object I show a form where the user needs to enter information about the Guest. However when I try to submit it only returns the info of one Guest. I've tried looking for a way to pass an array or multiple guests, but that doesn't seem to be possible, only sending one Guest object with parameters seems to work..
Here is the code for my GET:
[HttpGet]
public ActionResult Edit2(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Reservation reservation = resRepo.GetReservationByID(id);
ReservationVM reservationVM = new ReservationVM(0);
for (int i = 0; i < reservation.amount_people; i++)
{
reservationVM.guests.Add(new Guest());
}
foreach(Guest guest in reservationVM.guests)
{
guest.name = " ";
guest.zipcode = " ";
guest.housenumber = 0;
guest.suffix = "";
guest.email = " ";
guestRepo.AddGuest(guest);
}
guestRepo.Save();
reservationVM.date = (DateTime)reservation.date;
reservationVM.amount_people = (int)reservation.amount_people;
reservationVM.ID = reservation.ID;
reservationVM.room_ID = (int)reservation.room_ID;
if (reservation == null)
{
return HttpNotFound();
}
return View(reservationVM);
}
And my POST:
[HttpPost]
public ActionResult Edit2([Bind(Include = "room_ID,date,amount_people,ID")]Reservation reservation, [Bind(Include = "ID,name,zipcode,housenumber,suffix,email")]Guest guest)
{
if (ModelState.IsValid)
{
resRepo.UpdateReservation(reservation);
resRepo.Save();
guestRepo.UpdateGuest(guest);
guestRepo.Save();
Reservation r = new Reservation { ID = reservation.ID };
db.Reservations.Add(r);
db.Reservations.Attach(r);
Guest g = new Guest { ID = guest.ID };
db.Guests.Add(g);
db.Guests.Attach(g);
r.Guests.Add(g);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reservation);
}
and my form for completion:
Form
Now I was wondering how to pass all the forminfo to my Controller and use that information to save the Reservation and each Guest, so I can write that to my db.
Thanks!
EDIT: I tried the solution given, but I can't seem to get the Guest information in my database. Problem seems to be that when it reaches the foreach loop to get the guests out of reservationVM it's empty. Tried writing to Debug output whenever it entered the loop but it never does. Here's my code
[HttpPost]
public ActionResult Edit2(ReservationVM reservationVM)
{
if (ModelState.IsValid)
{
//Get reservation
Reservation reservation = resRepo.GetReservationByID(reservationVM.ID);
//Update values in model
reservation.date = reservationVM.date;
reservation.amount_people = reservationVM.amount_people;
reservation.ID = reservationVM.ID;
reservation.room_ID = reservationVM.room_ID;
//Update to DB and save changes
resRepo.UpdateReservation(reservation);
resRepo.Save();
foreach (Guest guest in reservationVM.guests)
{
Guest temp = guestRepo.GetGuestByID(guest.ID);
temp.name = guest.name;
temp.zipcode = guest.zipcode;
temp.housenumber = guest.housenumber;
temp.suffix = guest.suffix;
temp.email = guest.email;
temp.ID = guest.ID;
guestRepo.UpdateGuest(temp);
reservation.Guests.Add(temp);
}
guestRepo.Save();
//Reservation r = new Reservation { ID = reservation.ID };
//db.Reservations.Add(r);
//db.Reservations.Attach(r);
//Guest g = new Guest { ID = guest.ID };
//db.Guests.Add(g);
//db.Guests.Attach(g);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reservationVM);
}
You are passing ReservationVM from view to the post method. So write your post method as follows:
[HttpPost]
public ActionResult Edit2(ReservationVM reservationVM)
{
if (ModelState.IsValid)
{
// Here access the necessary values you need from `reservationVM` and do your necessary stuffs
return RedirectToAction("Index");
}
return View(reservation);
}
To get your guest information in you post action, you need to make the guest fields as follow:
#for(var i = 0; i < Model.guests.count; i++)
{
Your editor will be like below
#Html.EditorFor(model => model.guests[i].ID)
#Html.EditorFor(model => model.guests[i].name)
}
try making the fields in razor view like the above code .. it’ll work.

asp.net MVC5 handling errors inside child action method in parent view -- would like to redirect to error page

I'm still somewhat new to ASP.NET MVC so I hope my question is clear. In my project I have a parent view, that renders partial views inside the parent view by calling #Html.Action(controller action name) inside the parent view.
My goal is that if the controller action that returns the partial view fails, I would like the application to redirect to my error page. This works perfectly fine for my controller actions that are returning full views by doing the following: return RedirectToAction("Index", "ErrorHandler", null). However in the controller methods returning partial views (that are invoked with #Html.Action inside parent View), this returns error that child action cannot do redirect.
I also tried doing, return View("~/Views/ErrorHandler/Index.cshtml"), inside controller action that returns partial view. This doesn't throw an error but results in displaying the error page inside the parent page. That is not ideal as I stated earlier, my goal is to have application fully redirect to error page.
Here is some code. Let me know if you want to see more of my code:
Inside Parent View
#Html.Action("LoadEmployeeNames")
Controller method below:
public ActionResult LoadEmployeeNames()
{
string ManageRSVPApplicationName =
ConfigurationManager.AppSettings["ManageRSVPApplicationName"];
Log log = new Log(ManageRSVPApplicationName);
try
{
int applicationId =
Convert.ToInt32(ControllerContext.HttpContext.Session["ApplicationId"]);
Application application = new Application();
application.ApplicationId = applicationId;
if (DataBase.PopulateInviteeList(ref application, ref log))
{
List<EmployeeDropDownOption> employees = new List<EmployeeDropDownOption>();
if(false)
//if (EmployeeData.LoadEmployees(ref employees, application.InviteeList, ref log))
{
string currUserAccountName = User.Identity.Name.Split('\\')[1];
EmployeeDropDownOption currEmployee = employees.FirstOrDefault(t => t.AccountName.Trim().ToLower() == currUserAccountName.Trim().ToLower());
ViewBag.CurrentUserIdentity = (currEmployee == null) ? "" : currEmployee.EmployeeID.ToString() + ":" + currEmployee.DisplayName;
int currOfficeId = 0;
if (currEmployee != null)
{
DataBase.GetOfficeId(currEmployee.Office, ref currOfficeId, ref log);
if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["TestOffice"]))
{
ControllerContext.HttpContext.Session["CurrentOffice"] = currEmployee.Office;
}
else
{
ControllerContext.HttpContext.Session["CurrentOffice"] = ConfigurationManager.AppSettings["TestOffice"];
}
}
ControllerContext.HttpContext.Session["CurrentOfficeId"] = currOfficeId;
EmployeeDropDown employeeNameDD = new EmployeeDropDown(employees);
return PartialView("~/Views/RSVP/RSVP/SelectEmployeeName.cshtml", employeeNameDD);
}
}
log.Capture(Log.LogLevel.Error, "Unable to load employees when " + User.Identity.Name + " attempted to view RSVP application " + applicationId);
return View("~/Views/ErrorHandler/Index.cshtml");
}
catch (Exception e)
{
log.Capture(Log.LogLevel.Error, "Unable to load employees when " + User.Identity.Name + " attempted to view RSVP application ");
log.Capture(Log.LogLevel.Error, e);
return View("~/Views/ErrorHandler/Index.cshtml");
}
}
In general avoid using try catch block in controller actions, it make error handling process hard to maintain. instead use action filters to separate error handling code from code that define application normal flow
here is a simple custom error handler filter
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult
{
ViewName = "CustomError",
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.ExceptionHandled = true;
}
}
And use the filter on controller level or individual action as follow
[CustomHandleErrorAttribute]
public ActionResult LoadEmployeeNames()
{
string ManageRSVPApplicationName =
ConfigurationManager.AppSettings["ManageRSVPApplicationName"];
Log log = new Log(ManageRSVPApplicationName);
int applicationId = Convert.ToInt32(ControllerContext.HttpContext.Session["ApplicationId"]);
Application application = new Application();
application.ApplicationId = applicationId;
if (DataBase.PopulateInviteeList(ref application, ref log))
{
List<EmployeeDropDownOption> employees = new List<EmployeeDropDownOption>();
if (false)
//if (EmployeeData.LoadEmployees(ref employees, application.InviteeList, ref log))
{
string currUserAccountName = User.Identity.Name.Split('\\')[1];
EmployeeDropDownOption currEmployee = employees.FirstOrDefault(t => t.AccountName.Trim().ToLower() == currUserAccountName.Trim().ToLower());
ViewBag.CurrentUserIdentity = (currEmployee == null) ? "" : currEmployee.EmployeeID.ToString() + ":" + currEmployee.DisplayName;
int currOfficeId = 0;
if (currEmployee != null)
{
DataBase.GetOfficeId(currEmployee.Office, ref currOfficeId, ref log);
if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["TestOffice"]))
{
ControllerContext.HttpContext.Session["CurrentOffice"] = currEmployee.Office;
}
else
{
ControllerContext.HttpContext.Session["CurrentOffice"] = ConfigurationManager.AppSettings["TestOffice"];
}
}
ControllerContext.HttpContext.Session["CurrentOfficeId"] = currOfficeId;
EmployeeDropDown employeeNameDD = new EmployeeDropDown(employees);
return PartialView("~/Views/RSVP/RSVP/SelectEmployeeName.cshtml", employeeNameDD);
}
}
log.Capture(Log.LogLevel.Error, "Unable to load employees when " + User.Identity.Name + " attempted to view RSVP application " + applicationId);
return View("~/Views/ErrorHandler/Index.cshtml");
}
Finally add CustomError.cshtml to Views/Shared folder

How to redirect to view without action method

I want to redirect to a view, but there is no action method. Because it is not required.
Why I want to do that. Because You can delete a file(DeleteFile) or you can delete a folder(DeleteFolder).
I have this:
public ActionResult DeleteFile(string designId, int fileId)
{
DeleteFileModel DeleteFile = new DeleteFileModel();
TemplateLibraryEntry entry = GetTemplateLibraryEntry(designId, customerSchema);
FileTree tree = CreateTree(designId, entry.FilePath);
FileInfo fileInfo = new FileInfo(tree.Files[fileId].FullPath);
DirectoryInfo directoryInfo = new DirectoryInfo(tree.Files[fileId].FullPath);
if (directoryInfo.Attributes == FileAttributes.Directory) {
//Url.Action(designId, "DeleteFolder");
string directoryName = tree.Files[fileId].FileName;
DeleteFile.DesignId = designId;
DeleteFile.FileName = directoryName;
DeleteFile.Id = fileId;
DeleteFile.ModificationDate = fileInfo.LastWriteTime;
DeleteFile.RelativePath = fileInfo.Directory.Name + "/" + directoryName;
AddFileExplorerBreadCrumb(designId);
}
else {
string fileName = tree.Files[fileId].FileName;
DeleteFile.DesignId = designId;
DeleteFile.FileName = fileName;
DeleteFile.Id = fileId;
DeleteFile.ModificationDate = fileInfo.LastWriteTime;
DeleteFile.FileSize = fileInfo.Length;
DeleteFile.RelativePath = fileInfo.Directory.Name + "/" + fileName;
AddFileExplorerBreadCrumb(designId);
}
return View(DeleteFile);
}
So if it is a directory a user want to delete, then it goed to view: DeleteFolder and if the user want to delete a file, then it goes to: DeleteFile. But how to do that?
Thank You
You need to put return statement in if and else part
If( your directory delete condition){
Your directory delete logic
return view("deletedirectory.cshtml");
}
else{
//File delete logic
return view("deletefile.cshtml");
}
If I understand your question correctly you're trying to show a sort of confirmation page after a 'delete' action is called, and you want to specify which page (file vs directory) without having to create an action for each.
You can do this by specifying the name of the view you want in the View() method.
return View("DeleteFileConfirmation.cshtml", model)
or
return View("DeleteFolderConfirmation.cshtml", model)

CheckBoxList in a complex view

I have searched here many times but I could not find what I want.
I am developing an application where I have USERS with specific Skills, and I want to relate them to specific project.
So I have the following tables: Users, UserSkills and more
My question is: I am using CRUD in MVC4, and when I open the EDIT view from the UserDetail Controller, in order to edit the user information, I need also to add (in the same Edit view) partial view, or any mechanism, where I list the user skills, using CheckBoxes to help in multi-selecting various skills for this user, and then when pressing "Save" it should store the User and UserSkills information back to the dB (MS-SQL).
I am using this Model:
public class SkillsViewModel
{
public IList<Skill> AvailableSkills { get; set; }
public IList<Skill> SelectedSkills { get; set; }
public SavedSkills SevedSkills { get; set; }
public User Usr { get; set; }
}
SavedSkills are the UserSkills Table, which will be used for the dB
AvailableSkills are the Skills Table
SelectedSkills are the ones that are selected in the Edit view
Keeping in mind that the Edit view also contain an image upload file:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(User Usr, HttpPostedFileBase file)
{
#region//validate file is selected
if (file != null)
{
if (file.ContentLength > (512 * 1000)) // 512 KB
{
ModelState.AddModelError("FileErrorMessage", "File size
must be within 512KB");
}
string[] allowedType = new string
[]"image/png", "image/gif",
"image/jpg", "image/jpeg" };
bool isFileTypeValid = false;
foreach (var i in allowedType)
{
if (file.ContentType == i.ToString())
{
isFileTypeValid = true;
break;
}
}
if (!isFileTypeValid)
{
ModelState.AddModelError
("FileErrorMessage", "Only .png,
.gif and .jpg file allowed");
}
}
#endregion
if (ModelState.IsValid)
{
if (Skk.Skk.Count (x => x.IsSelected) == 0)
{
//return "You have not selected any City";
}
else
{
StringBuilder sb = new StringBuilder();
sb.Append("You selected - ");
foreach (Skill skilll in Skk.Skk)
{
if (skilll.IsSelected)
{
sb.Append(skilll.SkillName + ", ");
}
}
//sb.Remove(sb.ToString().LastIndexOf(","), 1);
//return sb.ToString();
}
//Update User
if (file != null)
{
string savePath = Server.MapPath("~/Pictures");
string fileName = Guid.NewGuid() + Path.GetExtension
(file.FileName);
file.SaveAs(Path.Combine(savePath, fileName));
Usr.ImagePath = fileName;
}
using (dBEntities dc = new dBEntities())
{
var v = dc.Users.Where(a => a.Id.Equals
(Usr.Id)).FirstOrDefault();
if (v != null)
{
v.UserName = Usr.UserName;
v.Email = Usr.Email ;
v.StartDate = Usr.StartDate ;
v.Company = Usr.Company ;
v.Position = Usr.Position;
v.Division = Usr.Division ;
v.Department = Usr.Department ;
v.PM = Usr.PM ;
v.AM = Usr.AM;
v.Permissions = Usr.Permissions;
v.IsActive = Usr.IsActive;
if (file != null)
{
v.ImagePath = Usr.ImagePath ;
}
}
dc.SaveChanges();
}
return RedirectToAction("Index");
}
ViewBag.Department = new SelectList
(db.Departments, "DepID", "DepName", Usr.Department);
ViewBag.Division = new SelectList
(db.Divisions, "DivID", "DivName", Usr.Division);
ViewBag.Position = new SelectList
(db.Positions, "PosID","PosName", Usr.Position);
return View(Usr);
}
I hope I have explained it well, and thank you in advance.
I had same situation with you.
I was used chosen to add user's skills. IMHO, it more user friendly then using checkboxes (you can look chosen in action in linked-in, when you add your skills) when you have more than 20 skills in your database.
Chosen is regular HTML "select" tag with multiple attribute but more beautiful.
I also use Ajax JQuery to post my data to controller.
JQuery POST method also support send form data with attached file.
So you don't need to change your Edit action a lot. What you need is add parameter for selected skills (it's a string which separated by comma).

DropDownListFor() and 500 Errors

I am getting a 500 error when I post my AJAX form via clicking the submit button. The controller that handles the AJAX post is getting the data fine but when I return the Partial View, via this line, I am getting the 500:
return PartialView("_SiteSurveyNewClubTeam", model);
The reason I am returning the partial back instead of a HTTP status code is because if I don't, one of my dynamic dropdowns comes back unpopulated. Maybe I am pinting myself into a corner, here.
The data types supplied in the offending DropDownListFor() I believe are correct and in the right order: (string, IList<SelectListItem>)
Error
The ViewData item that has the key 'DistrictSelected' is of type 'System.String'
but must be of type 'IEnumerable<SelectListItem>'.
View Model Declarations
public IList<SelectListItem> DistrictSelect { get; set; }
public string DistrictSelected { get; set; }
Source of the Error is this line in my View
<span class="formColumn2">#Html.DropDownListFor(model => model.DistrictSelected, Model.DistrictSelect)</span>
Not sure why I am getting this. Any ideas?
Thanks
Here is the code that processes the AJAX form post
[HttpPost]
public ActionResult ProcessFormANewClubTeam(FormANewClubTeamViewModel model)
{
var httpStatus = HttpStatusCode.BadRequest;
var cosponsors = new List<NewClubSponsor>();
var errorMessages = new StringBuilder();
var tasks = new NewClubBuilderTasks();
var clubKeyNumber = tasks.GetClubKeyNumber();
var masterCustomerId = tasks.GetMasterCustomerId();
bool exceptionRaised = false;
if (ModelState.IsValid)
{
if (model.NewClub_Id > 0)
{
//Load the entity to be partially-updated
NewClub newClub = db.NewClubs.Single(nc => nc.Id == model.NewClub_Id);
//Set the values for the fields to be updated
newClub.District = model.DistrictSelected;
newClub.Division = model.DivisionSelected;
newClub.Region = Utility.Personify.GetRegionFromDistrict(newClub.District);
newClub.ClubCounselorMasterCustomerId = model.ClubCounselorMasterCustomerId;
newClub.ClubCounselorContact = model.ClubCounselorContact;
newClub.ClubCounselorEmail = model.ClubCounselorEmail;
newClub.ClubCounselorPhone = model.ClubCounselorPhone;
newClub.DateUpdated = DateTime.Now;
try
{
//Execute the UPDATE
var dbResult = db.SaveChanges() > 0;
httpStatus = HttpStatusCode.OK;
}
catch (SqlException ex)
{
//Catch exceptions here
}
// return new HttpStatusCodeResult((int) httpStatus);
return PartialView("_SiteSurveyNewClubTeam", model);
} else {
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new {x.Key, x.Value.Errors})
.ToArray();
return new HttpStatusCodeResult((int) httpStatus);
}
}
You have to repopulate your select list items in your DistrictSelect list in the post action. Your viewmodel that was posted has DistrictSelect as null, this is why you are getting that exception when you render your partial.

Categories