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
Related
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 })
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.
Suppose I have a simple search form with a textbox. And upon submitting the form I send the contents of the textbox to a stored procedure which returns to me the results. I want the results to be displayed on the same page the form was, except just below it.
Right now I'm doing the following but it's not working out exactly the way I want:
"Index" View of my SearchController
#using (Html.BeginForm("SearchResults", "Search", FormMethod.Post, new { #class = "searchform" }))`{
<fieldset>
<legend>Name</legend>
<div class="editor-label">
#Html.Label("Search")
</div>
<div class="editor-field">
#Html.TextBox("Name")
</div>
<input type="submit" value="Search" class="formbutton" />
</fieldset>
#{ Html.RenderPartial("SearchResults", null);
This is my "SearchResults" View:
#model IEnumerable<MyProject.Models.spSearchName_Result>
<table>
#foreach (var item in Model)
{
<tr>
<td>
#item.Name
</td>
</tr>
}
</table>
This is my Controller:
// GET: /Search/SearchResult
[HttpPost]
public ActionResult SearchResult(FormCollection collection)
{
var result = myentity.spSearchName(collection["Name"]);
return PartialView("SearchResults", result);
}
I can only seem to get the results to display on an entirely new page (not being embedded as a partial view), or I get an error when I load the search page because there are no results (since I hadn't searched yet).
Is there any better way to achieve what I'm trying to do? I feel like I'm going against some of the best practices in MVC.
You could return the results in a ViewData object then only show on the view it if is not null.
Very similar to this question MVC 3 form post and persisting model data
For this instance it looks like you are not passing the results to your partial view. Try this?
#{ Html.RenderPartial("SearchResults", Model.Results);
Since you are not persisting your search information using a model, the search information will be lost when the search form is posted.
Given your design and stated goal, it would be best to convert the form in your Index view into an Ajax form, then your controller can send your partial view back to populate a div below your Ajax form.
counsellorben
I am using FluentValidation to validate my models, and it works awesome.
One question I have though is how do you handle messages that are not attached to a property?
Example: A customer login view. When the login is invalid I want to display a simple message.
What I did was add a property to the model, Message, and then created a validation message for that property on the view.
It works, but was looking to see what others are doing.
Update
So for simplicity, consider the following:
View Model
'Uses a FluentValidation Validator
Public Class LogonViewModel
Public Property UserName AS String<br>
Public Property Password AS String
End Class
View
<div id="GenericMessage">--Generic Messages Go Here--</div>
#<table border="0" cellpadding="2" cellspacing="0">
<tr>
<td>User Name:</td>
<td>#Html.EditorFor(Function(x) x.UserName) #Html.ValidationMessageFor(Function(x) x.UserName)</td>
</tr>
<tr>
<td>Password:</td>
<td>#Html.EditorFor(Function(x) x.Password) #Html.ValidationMessageFor(Function(x) x.Password)</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Logon" /></td>
</tr>
</table>
What I want to do is have a generic message div that I can display messages in, like "Invalid Login". My question is where to I put that in the model? Do I create a property and then set it in the controller ModelState? Or should I be using ViewData?
Any reason you aren't using the ModelState for your errors?
For example, if your view model has a datetime property and a user enters something like "blah", the ModelState will automatically have that error returned when you render the view again.
That error can be retrieved in the Validation Summary...
<%: Html.ValidationSummary() %>
Or you can bind it to specific form elements.
<%: Html.ValidationMessageFor(m => m.Birthdate) %>
You can also manually add error messages to the ModelState in your controller.
// for a specific property
ModelState.AddModelError("Birthdate", "You can't use this date!")
// to show in summary
ModelState.AddModelError("", "Dates are too close!")
If you are just after a way to communicate things from your controller to your view (other than errors), then I think it's fine to add a property to your viewModel, assign it a value in your controller, and then access it in your view.
And I don't see anything wrong with doing this for errors if ModelState isn't meeting your needs.
One question I have though is how do you handle messages that are not attached to a property?
As far as I understood from that question is you do not give any hint to framework to validate the input. right? if so, do it that way.
put the following code on your view;
#Html.ValidationSummary()
and validate your input inside the post action method. if it is not valid, add the error message to view state. here is an example;
if (captchaValid == false) {
ModelState.AddModelError("recaptcha", "Invalid characters on securty code! Please try it again");
return View(model);
}
Although I think I like better the Modelstate answers what I usually do is to define things like this in my _Layout.cshtml:
#if(TempData["Error"]!=null)
{
<div class="error">#TempData["Error"]</div>
}
#if(TempData["Warning"]!=null)
{
<div class="warning">#TempData["Warning"]</div>
}
then I only have to assign TempData["Error"] or TempData["Warning"] in my controller.
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.