How to get Button to Update Database? - c#

I have a View where in a GridView is generated of checklist to do items. One particular item is status. If the item isn't done, a button is shown to mark the item as done as well as mark who clicked the button and when:
<td>
#if (item.status.Equals("Done"))
{
#Html.DisplayFor(modelItem => item.status)
}
else
{
<input type="button" title="TestTitle" value="TestValue" onclick="location.href='#Url.Action("Update", "Checklist", new { item = item})'" />
}
</td>
I took the code from the scaffolded Edit and modified it slightly in my ChecklistController as all I want to do is modify three columns in the DB:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Update([Bind(Include = "ID,ww,taskitem,owner,manager,project,status,applied_by,timestamp")] TaskItem taskItem)
{
taskItem.timestamp = DateTime.UtcNow;
taskItem.applied_by = System.Web.HttpContext.Current.User.Identity.Name;
taskItem.status = "Done";
if (ModelState.IsValid)
{
db.Entry(taskItem).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View("Index", db.TaskItemSet.ToList());
}
I'm somewhat new to binding and anon types so regardless of using new { item = item}, new { id = item.id} or new {item} I get:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /Checklist/Update
Version Information: Microsoft .NET Framework Version:4.0.30319;
ASP.NET Version:4.6.81.0
What am I doing wrong and how do I get to do what I want?

onclick="location.href='...'" is making a GET call to a method which does not exist (you only have a POST method).
Change you view to to include a form for each item with the appropriate status
else
{
using (Html.BeginForm("Update", "Checklist", new { id = item.ID }))
{
#Html.AntiForgeryToken()
<input type="submit" title="TestTitle" value="TestValue" />
}
}
And modify the controller method to
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Update(int ID)
{
// Get the model
TaskItem model = db.TaskItems.Where(m => m.ID == ID).FirstOrDefault();
// Update properties
model.timestamp = DateTime.UtcNow;
model.applied_by = System.Web.HttpContext.Current.User.Identity.Name;
model.status = "Done";
// Save and redirect
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
Side note: If the view that these buttons are on is the Index view, then you would get far better performance by using ajax to post the value and then just updating the DOM to replace the button with the status text

Related

Why are form field entries disappearing after submitting?

I have form that a user submits. For some reason, in our production environment when they click submit, the form is cleared and they lose all that they filled in when they click submit. I've been lost on this for awhile so any input would really help.
I've tried on multiple browsers and this occurs on each one (Chrome, Safari).
The first action creates the view model for the form, the second one takes that data/model and submits an application:
[Authorize(Roles = "Applicant")]
public IActionResult Application()
{
var vm = _applicantManager.GetApplicationViewModel(CurrentUser.UserName);
return View(vm);
}
[HttpPost]
//[ValidateAntiForgeryToken]
[Authorize(Roles = "Applicant")]
public IActionResult Application(ApplicationViewModel model)
{
var saved = false;
model = _applicantManager.RebuildApplicationViewModel(model);
if (ModelState.IsValid)
{
saved = _applicantManager.SubmitApplication(model, CurrentUser.UserName);
return RedirectToAction("Index");
}
return View(model);
}
I'm thinking if I remove the ValidateAntiForgeryToken that maybe this would solve issue, but am not sure.
Is it possible that the first action needs a route? On testing, it goes to the action with the HttpPost attribute when I click submit on my local environment.
Note: This is a problem on the production server, this does not occur on the local. This leads me to believe that may a possible IIS setting that needs changed?
JavaScript on the Page:
<script>
require(['jquery', 'jqueryui'], function($, jqueryui) {
$(function() {
$("#preHealthAreaDiv input:radio").click(function () {
var selectedVal = $("#preHealthAreaDiv input:radio:checked").val();
if (selectedVal == "Yes") {
$("#preHealthAreaDetail").show();
}
else {
$("#preHealthAreaDetail").hide();
}
})
});
});
</script>

Combine detail and delete views ASP.Net MVC 5

I currently have separate views and controller actions for my details and delete methods. I would like to place the delete button on the details view so a user doesn't have to click delete, then delete again on they are on the delete view. I have this most of the way by not having a "get" delete method and using an ajax.actionlink helper within the details view to call the post method:
#Ajax.ActionLink("Delete", "Delete",
new { id = Model.DepartmentId },
new AjaxOptions { HttpMethod="POST", UpdateTargetId="output", Confirm= "Are you sure you want to delete this item?" },
new { #class = "btn btn-danger" })
The only problem is when the delete is successful, I want to redirect to a search view. Currently, my delete controller "post" method is as follows:
//
// POST: /Department/Delete/5
[HttpPost]
//[ValidateAntiForgeryToken]
public ActionResult Delete(DepartmentViewModel vmNotUsed, int id = 0)
{
if (id != 0)
{
// check to see if the department item is associated with an asset assignment
bool InUseByAssetAssignment = AssetAssignmentService.ValueInUse(x => x.DepartmentId == id);
if (InUseByAssetAssignment == false)
{
DepartmentService.DeleteDepartment(id);
return RedirectToAction("Search");
}
else
{
return Content("<p style='color:#f00';>This department cannot be deleted because there are items associated with it.</p>");
}
}
else
{
return Content("You must select a Department to delete!");
}
}
Unfortunately, it returns the view INSIDE of the current details view:
I don't know if this makes sense or not.
As your request is AJAX based, you need to return javascript to perform the redirect - something like:
return JavaScript(string.format("window.location = '{0}'", Url.Action("Search")));
Should do what you are asking.

Why isn't viewbag value passing back to the view?

straight forward question , can't seem to get my viewBag value to display in a view that the user is directed to after completing a form.
Please advise..thanks
My Index ActionResult simple returns model data..
public ActionResult Index()
{
var source = _repository.GetByUserID(_applicationUser.ID);
var model = new RefModel
{
test1 = source.test1,
};
return View(model);
}
My Get Edit" ActionResult , simply uses the same model data as Index.
My Post "Edit" ActionResult, assigns the new values if any to the model and redirects to the Index page, but Index page does not display ViewBag value ??
[HttpPost]
public ActionResult Edit(RefModell model)
{
if (ModelState.IsValid)
{
var source = _repository.GetByUserID(_applicationUser.ID);
if (source == null) return View(model);
source.test1 = model.test1;
_uow.SaveChanges();
#ViewBag.Message = "Profile Updated Successfully";
return RedirectToAction("Index");
}
return View(model);
}
And in my Index view...
#if(#ViewBag.Message != null)
{
<div>
<button type="button">#ViewBag.Message</button>
</div>
}
ViewBag only lives for the current request. In your case you are redirecting, so everything you might have stored in the ViewBag will die along wit the current request. Use ViewBag, only if you render a view, not if you intend to redirect.
Use TempData instead:
TempData["Message"] = "Profile Updated Successfully";
return RedirectToAction("Index");
and then in your view:
#if (TempData["Message"] != null)
{
<div>
<button type="button">#TempData["Message"]</button>
</div>
}
Behind the scenes, TempData will use Session but it will automatically evict the record once you read from it. So it's basically used for short-living, one-redirect persistence storage.
Alternatively you could pass it as query string parameter if you don't want to rely on sessions (which is probably what I would do).
RedirectToAction causes an HTTP 302 response, which makes the client make another call to the server and request a new page.
You should be returning a view instead of redirecting.
The RedirectToAction(msdn) instructs your browser to make a new request.
So your server will be called again but it will be a new request with a blank viewbag and all
You could do a sort of internal redirect by just calling the index method, this way the viewbag will still have its data.
Edit : you'll also have to modify your index method or your View(model) line will try to render the edit. Full code below
public ActionResult Index()
{
var source = _repository.GetByUserID(_applicationUser.ID);
var model = new RefModel
{
test1 = source.test1,
};
return View("Index",model);
}
[HttpPost]
public ActionResult Edit(RefModell model)
{
if (ModelState.IsValid)
{
var source = _repository.GetByUserID(_applicationUser.ID);
if (source == null) return View(model);
source.test1 = model.test1;
_uow.SaveChanges();
#ViewBag.Message = "Profile Updated Successfully";
return Index();
}
return View(model);
}
You can try this way also
Controller
public ActionResult Test()
{
ViewBag.controllerValue= "testvalue";
..................
}
View -
define top of razor page
#{string testvalue= (string)ViewBag.controllerValue;}
$(function () {
var val= '#testvalue';
});

How to add ModelState.AddModelError message when model item is not binded

I am new to MVC4. Here I added the ModelState.AddModelError message to display when the delete operation is not possible.
<td>
<a id="aaa" href="#Url.Action("Delete", "Shopping", new { id = Request.QueryString["UserID"], productid = item.ProductID })" style="text-decoration:none">
<img alt="removeitem" style="vertical-align: middle;" height="17px" src="~/Images/remove.png" title="remove" id="imgRemove" />
</a>
#Html.ValidationMessage("CustomError")
</td>
#Html.ValidationSummary(true)
In my controller
public ActionResult Delete(string id, string productid)
{
int records = DeleteItem(id,productid);
if (records > 0)
{
ModelState.AddModelError("CustomError", "The item is removed from your cart");
return RedirectToAction("Index1", "Shopping");
}
else
{
ModelState.AddModelError(string.Empty,"The item cannot be removed");
return View("Index1");
}
}
Here I didnt pass any of the model item in the View to check for the item in Model and I couldnt get the ModelState error message .. Any suggestions
The ModelState is created at each request so you should use TempData.
public ActionResult Delete(string id, string productid)
{
int records = DeleteItem(id,productid);
if (records > 0)
{
// since you are redirecting store the error message in TempData
TempData["CustomError"] = "The item is removed from your cart";
return RedirectToAction("Index1", "Shopping");
}
else
{
ModelState.AddModelError(string.Empty,"The item cannot be removed");
return View("Index1");
}
}
public ActionResult Index1()
{
// check if TempData contains some error message and if yes add to the model state.
if(TempData["CustomError"] != null)
{
ModelState.AddModelError(string.Empty, TempData["CustomError"].ToString());
}
return View();
}
RedirectToAction will clear ModelState. You must return a view in order to use this data. Therefore, the first if case won't work. Also, ensure that you have a control in your view (like ValidationSummary) which displays the error... this could be the problem in the second case.
The RedirectToAction method returns 302 which causes the client to be redirected. Because of this the ModelState is lost as the redirect is a new request. You could however, use the TempData property which allows you to store a temporary piece of data that is unique to the session. You could then check for this TempData on the other controller and add a ModelState error in that method.

Why my ajax post does not replace the content?

I have this:
<div id="miniShoppingCartContainer">
#Html.Action("MiniShoppingCart", "ShoppingCart")
</div>
where MiniShoppingCart action returns MiniShoppingCart.cshtml partial view with all the content.
In this partial view I added an ajax call for increasing the quantity of product cart:
#using (Ajax.BeginForm("IncreaseProductQuantity", "ShoppingCart", new { shoppingCartItemId = item.Id }, new AjaxOptions { UpdateTargetId = "miniShoppingCartContainer", InsertionMode = InsertionMode.Replace }))
{
<li>
<input type="submit" class="btn-up" />
</li>
}
which calls a method:
public ActionResult IncreaseProductQuantity(int shoppingCartItemId)
{
//get shopping cart item
var cart = _workContext.CurrentCustomer.ShoppingCartItems
.Where(x => x.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();
var sci = cart.Where(x => x.Id == shoppingCartItemId).FirstOrDefault();
if (sci == null)
{
return RedirectToRoute("ShoppingCart");
}
//update the cart item
_shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer,
sci.Id, sci.Quantity + 1, true);
return MiniShoppingCart();
}
Please note that at the end of the method I call the MiniShoppingCart ActionResult which prepares the cart and return the PartialView (as you see at the beginning of the post).
Well, the update of a product is happening fine but the content is not refreshed (or replaced)...
Can you please indicate where I am wrong?
UPDATE:
Doing an inspection with Chrome Dev. Tools I see an error when doing post:
POST http://localhost/ShoppingCart/IncreaseProductQuantity?shoppingCartItemId=11 500 (Internal Server Error)
f.support.ajax.f.ajaxTransport.sendjquery-1.7.1.min.js:4
f.extend.ajaxjquery-1.7.1.min.js:4
ejquery.unobtrusive-ajax.min.js:5
(anonymous function)jquery.unobtrusive-ajax.min.js:5
f.event.dispatchjquery-1.7.1.min.js:3
f.event.add.h.handle.ijquery-1.7.1.min.js:3
2
It's strange to guess what is the problem from this log...Basically, If I make debug I can see that it does all the operations until return PartialView(model); of MiniShoppingCart() method...
Issue found:
>The partial view 'IncreaseProductQuantity' was not found or no view engine supports the searched locations.
So basically, doing return MiniShoppingCart(); from IncreaseProductQuantity method doesn't automatically return the MiniShoppingCart partial view but will still try to return IncreaseProductQuantity partial view which of course does not exists.
Consequently, I have done it like:
var model = PrepareMiniShoppingCartModel();
return PartialView("MiniShoppingCart", model);

Categories