Redirect to action is not working - c#

Hi I have a table with anchor tag in a column. when user clicks on the link, My action method in controller redirect to another method after doing some update logic. Redirect to another action method then after, is not working in my case?
my View :
<fieldset>
<legend>Emended</legend>
<table border="1">
<tr>
<th>
#Html.DisplayNameFor(model => model.CustomerEmneedOrderedProduct.FirstOrDefault().Prd_Qnty)
</th>
</tr>
#foreach (var item in Model.CustomerEmneedOrderedProduct)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Prd_Qnty)
<br />
#Html.ActionLink("Remove", "updateOrderedProdStatuCd", new { orderProductId = item.OrderProductId, OrderedProdStatuCd = 2 })
</td>
</tr>
}
</table>
</fieldset>
My Controller :
public class SellerOrderDetailsController : Controller
{
public ActionResult OrderDetails([Bind(Prefix = "id")] int? orderId)
{
}
public ActionResult updateOrderedProdStatuCd(int orderProductId, int OrderedProdStatuCd)
{
try
{
// Updating few stuffs
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return RedirectToAction("OrderDetails"); //this is not working
}
}
}

I guess it's because you're not passing orderId to OrderDetails action. The action doesn't match method's signature.
Try something like
return RedirectToAction("OrderDetails ", new { orderId = 123 });

Related

HttpPost returns null Model in ASP.NET MVC

Advance warning, I am extremely new to ASP.NET.
I'm working on a project which will display rows of data from a db table. When a user clicks the "Ignore" button next to a row, it should update the corresponding "Ignore" column on that row with true in the database.
The view itself works fine, it displays all the data as expected. But when "Ignore" is clicked, and it calls the Ignore() method on the controller, the model is which is passed to the controller is null.
My model, generated by entity framework (with extraneous properties removed):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IgnoreDailyItems.Models
{
[Table("DataChecks.tbl.DailyItems")]
public partial class DataChecksTblDailyItems
{
[Column("entryId")]
public int EntryId { get; set; }
[Column("ignore")]
public bool? Ignore { get; set; }
}
}
The view:
#model IEnumerable<IgnoreDailyItems.Models.DataChecksTblDailyItems>
#{
ViewBag.Title = "Placeholder";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.EntryId)
</th>
</tr>
#{ var item = Model.ToList(); }
#for(int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item[i].EntryId)
</td>
<td>
#using (Html.BeginForm("Ignore", "Home", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item[i].EntryId)
<button type="submit" class="btn btn-danger">Ignore</button>
}
</td>
</tr>
}
</table>
And the Ignore() method on the controller:
[HttpPost]
public ActionResult Ignore(DataChecksTblDailyItems modelData)
{
using (var context = new IgnoreDailyItemsContext())
{
var query = context.DataChecksTblDailyItems
.Where(b => b.EntryId.Equals(modelData.EntryId));
foreach (var q in query)
{
q.Ignore = true;
}
context.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
You're generating the form in wrong way.
#Html.HiddenFor(modelItem => item[i].EntryId)
It will generate an input hidden with item[0].EntryId, item[1].EntryId... as name/id for each row in the table, for that reason the post model definition does not match.
To solve it, set the input hidden name manually:
#Html.Hidden("EntryId", item[i].EntryId)
You need to pass IEnumerable<IEnumerable> as a parameter.
public ActionResult Ignore(IEnumerable<DataChecksTblDailyItems> modelData)
{

IEnumerable<Model> returning null

I am having difficulty passing an IEnumerable as a model. The data is populating a form on one page - and doing so correctly. Upon submission the model returns as null.
I've seen various posts on this and they mostly reference naming-conventions so I have attempted different methods of naming the parameters to try to avoid any confusion in the model binding.
I have also tried various models and helpers to try and pass the data and all have the same result.
Current implementation:
Models:
public class UserProfileListModel
{
public IEnumerable<UserProfileViewModel> UserProfileViewModels { get; set; }
}
public class UserProfileViewModel
{
public UserProfile UserProfile { get; set; }
public Role UserRole { get; set; }
public Team UserTeam { get; set; }
public Scope UserScope { get; set; }
}
View:
#model Project.WebUI.Models.UserPRofileListModel
SNIP
<fieldset>
<legend>Administrate Users:</legend>
<table class="adminTbl">
<thead>
<tr>
<th>UserName:</th>
<th>Role:</th>
<th>Team:</th>
<th>Scope:</th>
<th>Update:</th>
<th>Delete:</th>
</tr>
</thead>
<tbody>
#{foreach (var user in Model.UserProfileViewModels)
{
<tr>
<td>
<p>#user.UserProfile.UserName
#{if (!user.UserProfile.Membership.IsConfirmed)
{
using (Html.BeginForm("Confirm", "Account", FormMethod.Post, null)){
#Html.AntiForgeryToken()
#Html.Hidden("Token", user.UserProfile.Membership.ConfirmationToken)
#Html.Hidden("Name", user.UserProfile.UserName)
}
<input type="submit" value="Confirm" />}
}
</p>
</td>
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(u => user.UserProfile)
if (user.UserProfile.UserName != User.Identity.Name && user.UserProfile.Membership.IsConfirmed)
{
<td>
#Html.DropDownListFor(u => user.UserRole, Project.WebUI.Controllers.AccountController.RoleList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserTeam, Project.WebUI.Controllers.AccountController.TeamList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserScope, Project.WebUI.Controllers.AccountController.ScopeList, new { #class = "formdrop" })
</td>
<td>
<input type="submit" value="Save Changes" onclick="return confirm('Are you sure you wish to update this user? ')" />
</td>
}
else
{
/*If user is self or not yet confirmed these are here to buffer the delete button into the last cell*/
<td></td>
<td></td>
<td></td>
<td></td>
}
}
}
<td>
#Html.ActionLink("Delete", "Delete", new { user.UserProfile.UserId }, new
{
onclick = "return confirm('Warning: Action cannot be undone. Are you sure you wish to permanently delete this entry?')"
})
</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
Controller:
Populate View:
public ActionResult AdministrateUsers()
{
populateLists();
var query = repository.UserProfiles.OrderBy(e => e.UserName);
List<UserProfileViewModel> list = new List<UserProfileViewModel>();
foreach(UserProfile up in query)
{
UserProfileViewModel vm = new UserProfileViewModel() { UserProfile = up };
list.Add(vm);
}
UserProfileListModel models = new UserProfileListModel()
{
UserProfileViewModels = list.OrderBy(up => up.UserProfile.UserName)
};
return View(models);
}
Accept Post:
public ActionResult SaveUserChanges(UserProfileListModel model)
{
foreach (UserProfileViewModel upvm in model.UserProfileViewModels)
{
UserProfile up = new UserProfile()
{
UserId = upvm.UserProfile.UserId,
UserEmail = upvm.UserProfile.UserName,
UserName = upvm.UserProfile.UserName
};
if (ModelState.IsValid)
{
repository.SaveUserProfile(up);
}
else
{
return View(model);
}
}
return RedirectToAction("Index", "Admin");
}
The code does still need a lot of work but I can't get past getting the model back to the controller on post. I have also tried returning the UserProfileViewModel instead of the entire list.
Can anyone tell what I am doing wrong?
Thanks!
You have a lot of invalid html including form elements as child elements of tr elements and duplicate id attributes. If you want to post back UserProfileListModel then you need a single form element and use an EditorTemplate or a for loop (not foreach) to render the controls so they are correctly named with indexers.
You are also trying to bind your dropdown lists to complex objects (for example UserProfile, Role etc.). <select> elements (and all form controls) only post back key/value pairs so you need to bind to a value type (for example UserProfile.UserId).
Your SaveUserChanges() post method is also trying access properties of UserProfile but you don't even have controls for properties of UserProfile in the form that post back to this method (for example UserId = upvm.UserProfile.UserId, UserEmail = upvm.UserProfile.UserName, ...) so they will always be null.
You probalby need to bind properties in POST method like here:
public ActionResult Create([Bind(Include = "Id,Subject,Text,IsImportant")] Announcment announcment) {... }
So it will be:
public ActionResult SaveUserChanges([Bind(Include = "UserProfile,Role,UserTeam,UserScope")]UserProfileListModel model)
Have you specified your action method is for HTTP Post? And change your action method to accept UserProfileViewModels instead.
[HttpPost]
public ActionResult SaveUserChanges(UserProfileViewModels model)
{
You are also only posting back one model: UserProfileViewModels.
You have your form in your foreach loop, so each UserProfileViewModels has its own form. If you want to change it to post back your UserProfileListModel, move
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
outside of your foreach.

Mass edit ISingleResult

I got a stored procedure which i show in the view for edits. I made a strong type of the stored procedure. When i edit the fields and then press the save button, the parameter "cm" is always empty. And it's not showing a list but just 1 record.
The custom model:
public class CustomModel
{
public string Description { get; set; }
public System.Data.Linq.ISingleResult<GetItems_ListResult> ItemList { get; set;}
}
This part of the controller sends it to the view:
public ActionResult Details(int id)
{
var row = dataContext.Items.FirstOrDefault(x => x.ItemID == id);
var cm = new CustomModel();
cm.ItemList = dataContext.GetItem_List(row);
cm.Description = row.Description;
return View(cm);
}
This controller receives data from the view:
[HttpPost]
public ActionResult UpdateItems(CustomModel cm)
{
return RedirectToAction("Index");
}
This is the view:
#model TestWeb.Models.CustomModel
#using (Html.BeginForm("UpdateItems", "Item", FormMethod.Post))
{
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
#foreach (var p in Model.ItemList.ToList())
{
<tr>
<td>
#Html.HiddenFor(mdl => p.ItemId)
</td>
<td>#p.Name</td>
<td>
#Html.EditorFor(mdl => p.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="save" />
</p>
}
What am i doing wrong here?
Try the following:
Make a GetItems_ListResult.cshtml like this:
<tr>
<td>
#Html.HiddenFor(mdl => mdl.ItemId)
</td>
<td>#Model.Name</td>
<td>
#Html.EditorFor(mdl => mdl.Description)
</td>
</tr>
Then in your for loop do this:
#for (int i = 0; i < Model.ItemList.Count(); i++)
{
#Html.EditorFor(m => m.ItemsList[i])
}
Update: I didn't quite notice you were using an ISingleResult. You could do this instead:
//Since it'll have none or one element..
if(Model.ItemList != null && Model.ItemList.Any())
{
#Html.EditorFor(m => m.ItemList.First())
}
Have you read this blog post? http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ Steve covers editing lists in asp mvc.
Once your read that take a look at this nuget package http://nuget.org/packages/BeginCollectionItem

How to pass a model from a PartialView to a Controller

I have a PaartialView declared like this:
#model IEnumerable<mvc1.Models.ProjectDetailModel>
#using (Html.BeginForm())
<form method="get" action="EditProject" enctype="multipart/form-data">
<br />
<fieldset>
<legend>Project Detail</legend>
#foreach (var item in Model)
{
<tr>
<th class="thdetail">
Project Code
</th>
<td class="tddetail">
#Html.DisplayFor(modelItem => item.projectCode)
</td>
<tr>
<th class="thdetail">
Project Name
</th>
<td class="tddetail">
#Html.DisplayFor(modelItem => item.projectName)
</td>
</tr>
<tr>
<th class="thdetail">
Project Type
</th>
<td class="tddetail">
#Html.DisplayFor(modelItem => item.projectType)
</td>
</tr>
<tr>
<th class="thdetail">
Detailed Description
</th>
<td class="tddetail">
<div style="height: 100px; width:700px; overflow: scroll">
#Html.DisplayFor(modelItem => item.projectDescription)
</div>
</td>
</tr>
</table>
</fieldset>
<input type="submit" value="Edit" />
</form>
}
On the submit button, i am calling a controller, but when it goes to the controller the model is not being passed back to the controller. How can i get the model back to the controller, or even just 1 field, ie. Model.projectCode which is the primary key
In the controller i have the fll which takes in the model and gets the primary key and calls a stored procedure to return results to another VIEW()
[HttpGet]
public ActionResult EditProject(ProjectDetailModel model)
{
DBController dbcontroller = new DBController();
string l_user_name = SessionBag.Current.UserName;
Int64 l_project_code = model.projectCode;
if (dbcontroller.DBConnection())
{
MySqlCommand command = new MySqlCommand("edit_projects", dbcontroller.conn);
command.CommandType = System.Data.CommandType.StoredProcedure;
// Input parameters for the insert_projects STORED PROC
command.Parameters.Add(new MySqlParameter("userName", SessionBag.Current.UserName));
command.Parameters["#userName"].Direction = System.Data.ParameterDirection.Input;
// Output parameters for the view_sr_projects_detail STORED PROC
command.Parameters.Add(new MySqlParameter("projectName", MySqlDbType.LongText));
command.Parameters["#projectName"].Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(new MySqlParameter("projectType", MySqlDbType.LongText));
command.Parameters["#projectType"].Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(new MySqlParameter("projectDescription", MySqlDbType.LongText));
command.Parameters["#projectDescription"].Direction = System.Data.ParameterDirection.Output;
try
{
MySqlDataReader rdr = command.ExecuteReader();
var model1 = new ProjectDetailModel();
while (rdr.Read())
{
model1.projectCode = (Int64)(rdr["projectCode"]);
model1.projectName = rdr["projectName"].ToString();
model1.projectType = rdr["projectType"].ToString();
model1.projectDescription = rdr["projectDescription"].ToString();
}
dbcontroller.conn.Close();
return View(model1);
}
catch (MySql.Data.MySqlClient.MySqlException ex)
{
dbcontroller.conn.Close();
ViewBag.Message = "Could not view your detail project. Error " + ex.Number + " has ocurred. Please try again or contact the system administrator.";
return View("Error");
}
}
else
{
ViewBag.Message = "Could not connect to the database. Please try again or contact the system administrator";
return View("Error");
}
}
My model looks like this:
public class ProjectDetailModel
{
[Display(Name = "Project Code")]
public Int64 projectCode { get; set; }
[Display(Name = "User Name")]
public string srUserName { get; set; }
[Display(Name = "Project Name")]
public string projectName { get; set; }
[Display(Name = "Project Type")]
public string projectType { get; set; }
[Display(Name = "Project Requirement")]
public string projectDescription { get; set; }
}
public class ProjectDetailModelList : List<ProjectDetailModel>
{
}
thanks
Naren
Here this is what you can do. Below is just partial code, modify it to fit in with your example:
Code in your view:
<button id="btnEdit" type="button">Edit</button>
I then use jQuery to add a click listener to the button (make sure that jQuery is added to the view):
<script>
$(document).ready(function () {
$('#btnEdit').click(function () {
window.location = '#Url.RouteUrl(new { action = "EditProject", projectCode = Model.projectCode })';
});
});
</script>
Your action method:
public ActionResult EditProject(int projectCode)
{
// Retrieve this specific project using this code
// Do what needs to be done to populate the required input fields on view
}
This is the best way that I have found to do it.
When MVC calls your controller.EditProject(ProjectDetailModel) action the binding system tries to create a ProjectDetailModel object from the data posted to the page (or in the query string or route data, etc.). In order for it to create and populate a ProjectDetailModel for you, you must have the following:
A parameterless constructor on ProjectDetailModel
A publically-settable property with a matching value in the form data
So if ProjectDetailModel looks like this:
public class ProjectDetailModel
{
public ProjectDetailModel()
{
}
public int ProjectId
{
get;
set;
}
}
...you can have it populated by MVC by posting this form:
<form method="get" action="EditProject" enctype="multipart/form-data">
<input type="hidden" id="ProjectId" name="ProjectId" value="123" />
</form>
Edit
Looking at the extra code you've added to the question, you have a few things to sort out:
#using (this.Html.BeginForm()) will render an open form tag - as you've manually written one you don't need that line.
Using this.Html.DisplayFor() will just write the property value to the screen; if you want it to be populated into a parameter in your action, you need to provide the values to the binding system. You can do this by using EditorFor() instead of DisplayFor(), which will cause them to be included in the request as it is sent to the action.

ASP.NET MVC Form Validation. How do you do it on a non-model object?

I have a view with model BlogPostViewModel:
public class BlogPostViewModel
{
public BlogPost BlogPost { get; set; }
public PostComment NewComment { get; set; }
}
This view is rendered when action method BlogPost is hit. The view displays information regarding the blog post as well as a list of comments on the blog post by iterating over Model.BlogPost.PostComments. Below that I have a form allowing users to post a new comment. This form posts to a different action AddComment.
[HttpPost]
public ActionResult AddComment([Bind(Prefix = "NewComment")] PostComment postComment)
{
postComment.Body = Server.HtmlEncode(postComment.Body);
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
return RedirectToAction("BlogPost", new { Id = postComment.PostID });
}
My problem is with validation. How do I validate this form? The model of the view was actually BlogPostViewModel. I'm new to validation and am confused. The form uses the strongly-typed helpers to bind to the NewComment property of BlogPostViewModel and I included the validation helpers as well.
#using (Html.BeginForm("AddComment", "Blog")
{
<div class="formTitle">Add Comment</div>
<div>
#Html.HiddenFor(x => x.NewComment.PostID) #* This property is populated in the action method for the page. *#
<table>
<tr>
<td>
Name:
</td>
<td>
#Html.TextBoxFor(x => x.NewComment.Author)
</td>
<td>
#Html.ValidationMessageFor(x => x.NewComment.Author)
</td>
</tr>
<tr>
<td>
Email:
</td>
<td>
#Html.TextBoxFor(x => x.NewComment.Email)
</td>
<td>
#Html.ValidationMessageFor(x => x.NewComment.Email)
</td>
</tr>
<tr>
<td>
Website:
</td>
<td>
#Html.TextBoxFor(x => x.NewComment.Website)
</td>
<td>
#Html.ValidationMessageFor(x => x.NewComment.Website)
</td>
</tr>
<tr>
<td>
Body:
</td>
<td>
#Html.TextAreaFor(x => x.NewComment.Body)
</td>
<td>
#Html.ValidationMessageFor(x => x.NewComment.Body)
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="submit" value="Add Comment" />
</td>
</tr>
</table>
</div>
}
How in the AddComment action method do I implement validation? When I detect Model.IsValid == false then what? What do I return? This action method is only binding to the PostComment property of the pages initial BlogPostViewModel object because I don't care about any other properties on that model.
You need to repopulate the model and send to view. However, you don't need to do this by hand, you can use action filters.
see:
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
Specifically:
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute
{
protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}
public class ExportModelStateToTempData : ModelStateTempDataTransfer
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
public class ImportModelStateFromTempData : ModelStateTempDataTransfer
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
base.OnActionExecuted(filterContext);
}
}
Usage:
[AcceptVerbs(HttpVerbs.Get), ImportModelStateFromTempData]
public ActionResult Index(YourModel stuff)
{
return View();
}
[AcceptVerbs(HttpVerbs.Post), ExportModelStateToTempData]
public ActionResult Submit(YourModel stuff)
{
if (ModelState.IsValid)
{
try
{
//save
}
catch (Exception e)
{
ModelState.AddModelError(ModelStateException, e);
}
}
return RedirectToAction("Index");
}
In your AddComment ActionResult, do this:
if(ModelState.IsValid)
{
// Insert new comment
..
..
// Redirect to a different view
}
// Something is wrong, return to the same view with the model & errors
var postModel = new BlogPostViewModel { PostComment = postComment };
return View(postModel);
After much time spent I have realized that I have to repopulate the view model and render the correct view, passing in the fully-populated model. Kind of a pain but at least I understand what's going on.

Categories

Resources