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.
Related
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>
I have an index action that can be filtered and is done so by using a query string. When I choose a record I move to the Details action. From there I can navigate to other actions related to this record which will then lead me back to the Details action. I would like to be able to save the URL from the Index page which will have the query string parameters intact. Obviously I can't do this with a straight Request.UrlReferrer since it won't be correct if the previous action wasn't Index. I have come up with a solution but I was wondering if there was a better way. Thanks!
public ActionResult Details(int? id)
{
var url = Request.UrlReferrer;
// Save URL if coming from the Employees/Index page
if (url != null && url.AbsolutePath == "/Employees")
Session.Add("OfficeURL", url.ToString());
// Model Stuff
return View();
}
Details View
#Html.ActionLink("Back to List", "Index", null, new { #href = Session["OfficeURL"] })
You need to pass a "return URL" with your links to the other views. Essentially:
Index.cshtml
#Html.ActionLink("View Details", "Details", "Foo", new { returnUrl = Request.RawUrl })
This will have the effect of putting the current index URL in the query string of the link. Then, in your other actions, you'll accept this as a param and store it in ViewBag:
public ActionResult Details(int? id, string returnUrl = null)
{
...
ViewBag.ReturnUrl = returnUrl;
return View();
}
Then, in these other views, you'll utilize this ViewBag member in the same way as above:
Details.cshtml
#Html.ActionLink("Click Me!", "Foo", "Foo", new { returnUrl = ViewBag.ReturnUrl })
When you're ready to go back to the index, then, you'd link/redirect to this return URL that you've been passing around.
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
I have such an action method:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Search(String filter, String value, Int32? page) {
var set = new List<Employee>();
switch(filter) {
case "by-name": {
set = this.repository.Get(
e => (e.LastName + " " + e.FirstName + " " + e.MiddleName) == value
).ToList();
break;
}
case "by-empn": {
set = this.repository.Get(
e => e.EmployeeNumber == value
).ToList();
break;
}
default: return RedirectToAction("Search", "Employee");
}
ViewBag.SearchedEmployees = set.Count();
return View(set.ToPagedList(page ?? 1, PageSize));
}
Search view is like this:
#if(Model.Count > 0) {
foreach(var item in Model) {
Html.RenderPartial("Employee.Card", item);
}
#Html.PagedListPager(
Model,
page => Url.Action("Search", new { page = page }),
new PagedListRenderOptions {
LinkToFirstPageFormat = "<< Beginning",
LinkToPreviousPageFormat = "< Back",
LinkToNextPageFormat = "Forth >",
LinkToLastPageFormat = "End >>"
}
)
}
Search form is presented as a partial view:
#using(Html.BeginForm("Search", "Employee", FormMethod.Get, new { #class = "search-form" }))
{
<p>
#Html.TextBox("value")
</p>
<p>
#Html.RadioButton("filter", "by-name", true) By name <br/>
#Html.RadioButton("filter", "by-empn") By empn <br/>
</p>
<p>
<input type="image" src="#Url.Content("~/Content/Images/Search.png")" />
</p>
}
Problem: I have N page links. When I try to go to the second page I face an infinite loop of redirects. That's the way I implemented my action - default case is fired. So filter/value values are null on the second action call? Why?
How do I refactor my search action?
Also how should I configure route for such an action?
Thanks!
EDIT
So should route for the search action look like:
routes.MapRoute(
null,
"{controller}/{action}/Page{page}/filter{filter}/val{value}",
new { controller = "Employee", action = "Search" }
);
?
EDIT 2
So it is possible to write next:
page => Url.Action("Search", new { filter = ViewBag.SearchFilter, value = ViewBag.SearchValue, page = page }),
And inside a controller:
public ActionResult Search(String filter, String value, Int32? page) {
ViewBag.SearchFilter = filter;
ViewBag.SearchValue = value;
// ...
}
Is this right?
So filter/value values are null on the second action call? Why?
Because their corresponding input fields are inside a separate form and are never sent to the server.
You seem to be using some custom Html.PagedListPager helper (the code for which you haven't shown) but I guess that this helper generates the page links as anchors and it simply doesn't take into account any current query string or POSTed values when generating those links. So the href of your pagination link looks like this /SomeController/Search?page=5 instead of the correct one which would take into account those parameters which is /SomeController/Search?page=5&filter=somefilter&value=somevalue.
You can now easily understand why the filter and value parameters in your controller action are always null. It's because you never send them to the server when clicking on the pagination links.
So in order to resolve this issue you could modify this custom HTML helper that you are using to generate the pagination links to include those additional parameters. Or maybe the helper allows you to pass additional parameters? Check the documentation if this is some third party plugin that you are using.
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);