I have a problem with calls to action with POST attribute exclusively from Grid.MVC tool.
I set up a column within the Grid.MVC tool, setting the property RenderValueAs follows:
#Html.Grid(Model).Columns(columns =>
{
// other lines...
columns.Add().Encoded(false)
.Sanitized(false)
.RenderValueAs(item => Html.ActionLink("Delete", "Delete", new { item.ID }));
}).WithPaging(3).Sortable(true)
In my controller I have set the following code which refers to the Delete action:
[HttpPost]
public ActionResult Delete(int id)
{
// anything here
return RedirectToAction("Index");
}
The best practices tell you that the action method should support only POST requests, because deleting objects is not an idempotent operation. This because browsers and caches are free to make GET requests without the user’s explicit consent, and so I must be careful to avoid making changes as a consequence of GET requests.
But, when I try to apply this configuration I have the next error:
Server Error in application '/'
Can not find the resource.
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
But if I remove the [HttpPost] attribute the code works. Is this behavior correct?
Thanks
UPDATE:
Bayu, you have reason, now I explain the problem from another scenario: I had my View as follows:
<table>
<tr>
<th>ID</th>
#*another columns*#
<th>Name</th>
<th>Actions</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.ID</td>
#*another columns*#
<td>#item.Name</td>
<td>
#using (Html.BeginForm("Delete", "Admin"))
{
#Html.Hidden("ID", item.ID)
<input type="submit"
value="Delete row" />
}
</td>
</tr>
}
</table>
That is, I used HTML tables, and therefore I could use an input to build a button that allows me to delete a row from the table.
Now, I want to use the tool Grid.View (because among other benefits allows me effortlessly filter columns), but I can not use an input for this purpose.
You have any idea how?
Change new { item.ID }) to new {id = item.ID })
Related
I have found the answer to the question "how to execute POST method using hyperlink (actionlink) instead of button" in this topic.
I have used form's submit method in this way - looks straightforward:
#foreach (var item in Model)
{
using (Html.BeginForm("Delete", "Area", new { id = item.id }, FormMethod.Post))
{
<tr>
...
<td>
Delete
<input id="sbmt" type="submit" style="visibility: hidden" />
</td>
</tr>
}
}
Typical declaration without button declaration
Delete
will not work.
The questions are:
1/ Is it possible to do the same operation but without hiding the button? I mean I don't want to use (and hide) button in my code at all.
2/ What are pros and cons of the solution listed above?
It is possible without button.One of the way is Just simply giving id to your form like
using (Html.BeginForm("Delete", "Area", new { id = item.id }, FormMethod.Post,new {id="frm"}))
{
}
and then
and if not jquery then
What is the problem with button I don't know. I can not see any cons except dirty coding(bad way) to submit a form.
I have following ActionResult in a controller and you can see that I set a message in the ViewBag if it's successful. Then on the View it should output that message if it's not empty. However, I can't get the message to display and I'm not seeing what the problem is.
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
Name = collection["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully.";
return RedirectToAction("Index");
}
catch (Exception)
{
return View();
}
}
This is my Index.cshtml
#model IEnumerable<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>
#{
ViewBag.Title = "Index";
}
<h2>Roles Listing </h2>
#ViewBag.ResultMessage
#Html.ActionLink("Create New Role", "Create") | #Html.ActionLink("Manage User Role", "ManageUserRoles")
<div>
<table class="table table-bordered table-condensed table-striped table-hover ">
<thead>
<tr>
<th>Role</th>
<th>Action</th>
</tr>
</thead>
<tbody>
#foreach (var role in Model)
{
<tr>
<td><strong>#role.Name</strong></td>
<td>
<span onclick="return confirm('Are you sure you want to delete #role.Name?')">Delete</span> |
#Html.ActionLink("Edit", "Edit", new { roleName = #role.Name })
</td>
</tr>
}
</tbody>
</table>
</div>
ViewBag helps to maintain data when you move from controller to view. Short life means value becomes null when redirection occurs. This is because their goal is to provide a way to communicate between controllers and views. It’s a communication mechanism within the server call.
Since you are using RedirectToAction, the ViewBag becomes null when it reaches the view.
you can use TempData for this:
TempData["ResultMessage"] = "Role created successfully.";
It uses Session as storage, but it will not be around after the second response.
TempData helps to maintain data when you move from one controller to other controller or from one action to other action. In other words, when you redirect, Tempdata helps to maintain data between those redirects. It internally uses session variables. TempData use during the current and subsequent request only means it is used when you are sure that next request will be redirecting to next view.
For more understanding on this refer this link
The ViewBag property enables you to dynamically share values from the
controller to the view. (MSDN)
It’s life lies only during the current request, and if redirection occurs then it’s value becomes null.
And since you using RedirectToAction, which redirects to some different controller, value of ViewBag is lost.
Consider using TempData instead.
TempData["ResultMessage"] = "Role created successfully.";
(See this for usage)
The viewbag/viewdata scope is available for controller to view only. if you use tempdata, it will available one request , you can extend more than one request
I don't see this problem too often but I've got a .cshtml that uses a layout. In the layout I've got:
#using (Html.BeginForm(null, null, FormMethod.Post, new { #class = "someCssClass", #id = "UserForm" }))
{
...rest of the code
}
My main .cshtml using this layout has the model defined at the top as we always do:
#model CarViewModel
#{
Layout = "~/Views/Shared/_CarLayout.cshtml";
}
When It gets back to my action method, I get nulls for all values of the model:
public ActionResult Cars(CarViewModel model)
{
carBL.RemoveCars(model.CarIds, model.DealerId);
...
}
Not sure what I need to do here and why this is happening. Usually I just get it back successfully via autobind. It seems to me when the model is used via RAzor in the markup- that gets posted back fine with the returned ViewModel but if I'm not using those fields, it doesn't...so I assume that's how that works and if I don't use them in mark-up I need to send them back as hidden values then to force the persistence since I am not using x fields from the ViewModel (Which would have automatically persisted those fields if I had used them in the form)?
If the values are not bound to a form field, they will come back null.
in the form use the below for things like ID fields.
#Html.HiddenFor(x => x...)
A quick test, to see if the form is being posted correctly would be to modify the signature of your action:
public ActionResult Cars(FormCollection form)
{
...
}
If form is not populated then you have an issue with the form post. As a side, note you could accomplish this when reviewing the post data of the form with a tool like FireBug, Chrome Dev tools or Fiddler if you prefer.
If the form is posting correctly, then I you should check to make sure the name's of the input fields on the form align with the names of the CarViewModel you are expecting.
Not sure if this has been resolved yet, but this is how I do it (partial code):
#model MyProject.ViewModels.MyViewModel
#using (Html.BeginForm())
{
<table>
<tr>
<td>First Name:</td>
<td>#Html.TextBoxFor(x => x.FirstName, new { maxlength = "50" })
#Html.ValidationMessageFor(x => x.FirstName)
</td>
</tr>
</table>
<button id="btnSave" type="submit">Save</button>
<button id="btnCancel" type="button">Cancel</button>
}
Then my action method to handle the HTTP post request:
[HttpPost]
public ActionResult Create(MyViewModel viewModel)
{
// Check for null on viewModel
// Do what needs to be done
}
Doing it this way should not let you loose your values filled in on the form/view.
I'm having a problem with an ASP.NET MVC application that I'm developing. I'm still fairly new at web development and in particular MVC, so please forgive me is this is a really stupid newbie mistake ;-)
I have a view that displays a list of products. Each product has a 'details' link that I want to link to a details view for that product. So, here's the relevant markup from the view:
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.ActionLink("Edit", "Edit", new { id=item.StockCode.ToString() }) %>
<%= Html.ActionLink("Details", "Details", new { id=item.StockCode.ToString() })%>
</td>
<td>
<%= Html.Encode(item.StockCode) %>
</td>
<td>
<%= Html.Encode(item.Description) %>
</td>
</tr>
So far so good. When I hover the mouse over the details link in the web browser, the link shows up as:
http://localhost:40733/RawMaterial/Details/R8517
R8517 is the stock code for the item. So, that's what I expect to see in my Action Method. Here's the Action Method:
//
// GET: /RawMaterial/Details/5
public ActionResult Details(string stockCode)
{
return View(repository.GetByStockCode(stockCode));
}
But... when the Action Method executes, the parameter (stockCode) is null.
Any thoughts?
The parameter you need to accept shouldn't be "stockCode" is should be "id"
MVC is trying to model bind the "id=item.StockCode.ToString()" to a parameter that is named the same. Your action method should be:
public ActionResult Details(string id)
+1 to DM's answer, but you also have another option. You can register special route for your case. Something like this:
routes.MapRoute("RawMaterial", "RawMaterial/{action}/{stockCode}",
new { controller = "RawMaterial", action = "Index" } );
I have a partial view that is bound to an object Cart. Cart has a collection of CartLines. My view is below:
<tbody>
<% foreach (var line in Model.Lines) { %>
<tr>
<td align="center"><%=Html.CatalogImage(line.Product.DefaultImage, 80) %></td>
<td align="left">
<%=Html.ActionLink(line.Product.Name, "Product", "Catalog",
new { productId = line.Product.Id }, new { title = "View " + line.Product.Name })%>
</td>
<td align="right"><%= line.Product.Price.ToString("c")%></td>
<td align="center">
<%=Html.Hidden("lines[" + i + "].key", line.Product.Id) %>
<%=Html.TextBox("lines[" + i + "].value", line.Quantity, new { #class = "quantity" })%>
</td>
<td align="right"><%= (line.LineTotal).ToString("c")%></td>
<td>
<%using (Ajax.BeginForm("RemoveFromCart", "Cart",
new {ProductId = line.Product.Id, returnUrl = ViewData["returnUrl"]},
new AjaxOptions { UpdateTargetId="cart", LoadingElementId="loading" }))
{%>
<input type="image" src="<%=AppHelper.ImageUrl("delete.gif")%>" value="Remove item" />
<%} %>
</td>
</tr>
<% i++; } %>
</tbody>
There are two things to note. The first is that I am using a form per line for removing items.
The second is that I had attempted to allow users to change the quantity of line items and then click an update button to pass all the changes to the controller action:
// POST: /Cart/Update
[HttpPost]
public ActionResult Update(Cart cart, IDictionary<int,int> lines, string returnUrl)
{
foreach (var line in lines) {
Product p = _catalogService.GetProduct(line.Key);
cart.UpdateItem(p, line.Value);
}
if (Request.IsAjaxRequest())
return PartialView("Cart", cart);
else
return RedirectToAction("Index", new { returnUrl });
}
Note that I am using a dictionary since I am only concerned about the product and quantity. I don't really like the fact that I am having to retrieve the product again before calling cart.UpdateItem but I couldn't figure out how to pass the Product from the model to my action instead of the id.
The main problem however, is rather stupidly I wrapped the entire cart in a form so that I could post back the values and then spent a good hour wondering why things were not working correctly in IE - doh! nested forms
So I am stuck on how to get round this. I want the ability to remove items individually but allow a user to change item quantities and then pass all changes at once to the controller. I can't use links for my remove action as I would need to use javascript to force a post and everything must work without javascript enabled.
[Update]
Would a better solution be to allow updates on my custom model binder? This way I could make changes inside my view and post the cart object back to the controller - although I'm not sure whether this is possible with child collections (Cart.CartItems).
I've had a look on sites like Amazon and it would appear they wrap the entire cart in a form and both global update buttons and indidivual remove item buttons post back to the same action when javascript is disabled.
Any help would be appreciated.
Thanks,
Ben
There is only one way here and thats the ugly way. Have 1 form around everything.
Then in the action you have to check which button was pressed (you get the name of the button in the request).
It gets even more ugly with differences in firefox and ie. If you have a button pressed ie or firefox (Dont remember which one) not only sends the name of the pressed button, but also the location where the button was pressed.
You have more options if your solution can rely on JS enabled browsers. But thats another story.