ASP.NET MVC4 + Get drop down value after HttpPost submit - asp.net

So I want to get a value of the dropdown (== id of the account) to insert a new transaction
Here below you can find my View:
<div id="content">
<h2>Overschrijving</h2>
<p id="topmsg">velden met een * zijn verplicht</p><p> </p>
<dl class="clearfix form">
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.ValidationSummary(true, "Gelieve alles correct in te vullen.")
<dt>#Html.Label("Rekening: *")</dt>
<dd>#Html.DropDownList("ddlAccounts")</dd>
<dt>#Html.Label("Begunstigde: *")</dt>
<dd>#Html.TextBox("ToAccountNumber")</dd>
<dt>#Html.Label("Bedrag: *")</dt>
<dd>#Html.TextBox("Amount", null, new { #class = "inpshort" })</dd>
<dt>#Html.Label("Mededeling: ")</dt>
<dd>#Html.TextBox("Message", null, new { #class = "inplong" })</dd>
<dd class="buttons">
<input type="submit" value="Bevestigen" />
<input type="submit" value="Annuleren" />
</dd>
}
</dl>
</div>
this is the Controller code:
public ActionResult Transfer(int? id) {
Holder h = Holder.GetHolderByHolderId(Convert.ToInt32(User.Identity.Name));
List<Account> holderAccounts = Account.GetAccountsByHolderId(h.Id);
List<SelectListItem> ddlHolderAccounts = new List<SelectListItem>();
ddlHolderAccounts.Add(new SelectListItem { Text = "selecteer...", Value = "-1", Selected = true });
foreach (Account acc in holderAccounts) {
if (acc.Id == id) {
ddlHolderAccounts.Add(new SelectListItem { Text = acc.Name, Value = acc.Id.ToString(), Selected = true });
} else {
ddlHolderAccounts.Add(new SelectListItem { Text = acc.Name, Value = acc.Id.ToString() });
}
}
ViewData["ddlAccounts"] = ddlHolderAccounts;
return View();
}
[HttpPost]
public ActionResult Transfer(Transaction tra) {
if (ModelState.IsValid) {
Transaction.InsertTransaction(tra.Amount, tra.Message, tra.FromAccountId, tra.ToAccountNumber);
}
return View(tra);
}
Now I searched a lot with Google, it's probably better to use the DropDownListFor to fill your drop down list? But could anyone show me an example?

By looking at your code, I can see that you're not passing a list of SelectListItems to the DropDownList helper. You can do this one of two ways.
1- Bind it to a property on your model:
#Html.DropDownListFor(x=>Model.Property, new List<SelectListItem> { new SelectListItem { Text = "Item1", Value = "Value1" })
Or
2- You can do it without binding to a model property like:
#Html.DropDownList("propertyName", new List<SelectListItem> { new SelectListItem { Text = "Item1", Value = "Value1" } })
If you're using the second approach then your controller action must accept "propertyName" as a parameter when submitting.
And don't forget to provide a list of SelectListItems to select from (which you're not doing in your code).
Hope this helps.

should be
#Html.DropDownListFor(x=>Model.Property, new List { new SelectListItem { Text = "Item1", Value = "Value1" }})

Related

How to store a <select> in a model property?

I am new to ASP.NET Core and Razor pages. I am trying to create a select drop-down list and get its value for further processing. I have the following in the view / Razor page (the .cshtml file):
<div class="field">
#Html.LabelFor(x => Model.FavouriteColour, htmlAttributes: new { #class = "label" })
<div class="control">
<div class="select">
#Html.DropDownListFor(x => Model.FavouriteColour, new SelectList(Model.FavouriteColoursList, "Value", "Text"), htmlAttributes: new { #id = "favouriteColour", #name = "favouriteColour" })
</div>
#Html.ValidationMessageFor(x => x.FavouriteColour, "", new { #class = "has-text-danger" })
</div>
</div>
And then I have the following in the OnPost() method of the view model (the .cshtml.cs file):
[Required]
[Display(Name = "My favourite colour is...")]
public string FavouriteColour { get; set; }
public IEnumerable<SelectListItem> FavouriteColoursList = new List<SelectListItem>()
{
new SelectListItem
{
Value = "White",
Text = "White"
},
(...)
}
The page is displayed correctly and all options are visible in the drop-down but the OnPost() method fails to retrieve the selected value. The following code:
Console.WriteLine("Colour: " + FavouriteColour);
Console.WriteLine("Colour (FORM): " + Request.Form["favouriteColour"] + " " + Request.Form["favouriteColour"].GetType());
Has the following output:
Colour:
Colour (FORM): White Microsoft.Extensions.Primitives.StringValues
When I also try to .GetType() of the model property, I get a System.NullReferenceException which, combined with the information above, shows that the data is transferred correctly but for some reason fails to be assigned to the model property.
What's the root cause and how to resolve it?

Changing number of table elements displayed on page resets my search results

I have numerous data displayed in a table, let's say a long list of users (first name & last name), so I set up a paging feature to display the elements by pages via the PagedList NuGet package. I was inspired by this tutorial: https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application
I implemented a drop-down list in my view, so that I can directly choose the number of elements to display per page. I managed to include a jQuery script that makes the page size update whenever the drop-down list has a new selected value.
Using the mentioned tutorial , I also added a search feature: submitting a string in a search form allows to filter the data.
My problem is: changing the page size by selecting a new value in the drop-down list after having done a search doesn't work: the search results are reset, all the entries being displayed instead. I guess I forgot to pass some parameter somewhere but I just can't figure out where...
Here is my controller:
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page, int? PageSize)
// Sort order is passed to view in order to keep it intact while clicking in another page link
ViewBag.CurrentSort = sortOrder;
// Ascending or descending sorting by first or last name according to sortOrder value
ViewBag.LastNameSortParm = String.IsNullOrEmpty(sortOrder) ? "lastname_desc" : "";
ViewBag.FirstNameSortParm = sortOrder == "firstname" ? "firstname_desc" : "firstname";
// Not sure here
if (searchString == null)
{
searchString = currentFilter;
}
// Pass filtering string to view in order to maintain filtering when paging
ViewBag.CurrentFilter = searchString;
var users = from u in _db.USER select u;
// FILTERING
if (!String.IsNullOrEmpty(searchString))
{
users = users.Where(u => u.lastname.Contains(searchString)
|| u.firstname.Contains(searchString)
}
// Ascending or descending filtering by first/last name
switch (sortOrder)
{
case "lastname": // Ascending last name
users = users.OrderBy(u => u.lastname);
break;
case "lastname_desc": // Descending last name
users = users.OrderByDescending(u => u.lastname);
break;
case "firstname": // Ascending first name
users = users.OrderBy(u => u.firstname);
break;
case "firstname_desc": // Descending first name
users = users.OrderByDescending(u => u.firstname);
break;
default:
users = users.OrderBy(u => u.lastname);
break;
}
// DROPDOWNLIST FOR UPDATING PAGE SIZE
int count = _db.USER.OrderBy(e => e.Id).Count(); // Total number of elements
// Populate DropDownList
ViewBag.PageSize = new List<SelectListItem>() {
new SelectListItem { Text = "10", Value = "10", Selected = true },
new SelectListItem { Text = "25", Value = "25" },
new SelectListItem { Text = "50", Value = "50" },
new SelectListItem { Text = "100", Value = "100" },
new SelectListItem { Text = "All", Value = count.ToString() }
};
int pageNumber = (page ?? 1);
int pageSize = (PageSize ?? 10);
ViewBag.psize = pageSize;
return View(users.ToPagedList(pageNumber, pageSize));
}
And my Index.cshtml view:
<script src="~/Scripts/jquery-3.2.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () { // Submit pageSizeForm when another pageSize value is selected
$("#pageSize").change(function () {
$("#pageSizeForm").submit();
});
});
</script>
#model PagedList.IPagedList<AfpaSIPAdmin.Models.USER>
#using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Users management";
}
<h1>Users management</h1>
<!-- Creating a new entry in table -->
<p>
#Html.ActionLink("Create new user", "Create")
</p>
<!-- Filtering table entries -->
#using (Html.BeginForm("Index", "Users", FormMethod.Get, new { id = "filterForm" }))
{
<p>
Filter: #Html.TextBox("SearchString", ViewBag.CurrentFilter as string, new { #placeholder = "First or last name..." })
<input type="submit" value="Apply"/>
</p>
}
<!-- Display table -->
<table class="table">
<tr>
<th>
#Html.ActionLink("Last name", "Index", new {
sortOrder = ViewBag.LastNameSortParm,
currentFilter = ViewBag.CurrentFilter
})
</th>
<th>
#Html.ActionLink("First name", "Index", new {
sortOrder = ViewBag.FirstNameSortParm,
currentFilter = ViewBag.CurrentFilter
})
</th>
<th style="min-width: 170px"></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td style = "min-width: 150px">
#Html.DisplayFor(modelItem => item.lastname)
</td>
<td style = "min-width: 150px">
#Html.DisplayFor(modelItem => item.firstname)
</td>
<td> <!-- Using images as buttons for actions -->
<a href="#Url.Action("Edit", "Users", new { id = item.Id })" title="Edit">
<img src="~/Content/images/edit.gif" />
</a>
<a href="#Url.Action("Details", "Users", new { id = item.Id })" title="Details">
<img src="~/Content/images/info.gif" />
</a>
<a href="#Url.Action("Delete", "Users", new { id = item.Id })" title="Delete">
<img src="~/Content/images/delete.gif" />
</a>
</td>
</tr>
}
</table>
<br/>
<!-- Paging -->
#using (Html.BeginForm("Index", "Users", FormMethod.Get, new { id = "pageSizeForm" }))
{
<div class="pager">
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) sur #Model.PageCount<br/>
#Model.Count of #Model.TotalItemCount elements
#Html.PagedListPager(Model, page => Url.Action("Index", new {
page,
sortOrder = ViewBag.CurrentSort,
currentFilter = ViewBag.CurrentFilter,
searchString = ViewBag.CurrentFilter,
pageSize = ViewBag.psize
}))
<!-- DropDownList for setting page size -->
Elements per page :
#Html.DropDownList("pageSize")
</div>
}
The reason is because you have 2 forms. When you submit the first form containing the textbox, the only value you send back to the controller is SearchString and all the other parameters in your method will be their default (for example when you return the view, PageSize will default to null and therefore return only 10 records even if the user previously selected say 50.
Likewise, when you submit the 2nd form containing dropdownlist for the page size, the value of SearchString will be null because its not sent in the request.
You need to have one form only containing both form controls. And if you wanted to send additional properties, for example the current sort order, then you can add those as query string values in the form element (for example, #using(Html.BeginForm("Index", "Users", new { sortOrder = .... }, FormMethod.Get))
I would also strongly recommend you use a view model containing the properties you need in the view and strongly bind to them rather than using ViewBag
public class UsersVM
{
public string SearchString { get; set; }
public int PageSize { get; set; }
public IEnumerable<SelectListItem> PageSizeOptions { get; set; }
.....
public IPagedList<USER> Users { get; set; }
}
View
#model UsersVM
...
#using(Html.BeginForm("Index", "Users", FormMethod.Get))
{
#Html.LabelFor(m => m.SearchString)
#Html.TextBoxFor(m => m.SearchString)
#Html.LabelFor(m => m.PageSize)
#Html.DropDownListFor(m => m.PageSize, Model.PageSizeOptions)
<input type="submit" value="Filter" />
}
....
<div class="pager">
Page #(Model.Users.PageCount < Model.Users.PageNumber ? 0 : Model.Users.PageNumber)
....
#Html.PagedListPager(Model.Users, page => Url.Action("Index", new {
page,
sortOrder = Model.CurrentSort,
currentFilter = Model.CurrentFilter,
searchString = Model.CurrentFilter,
pageSize = Model.PageSize
}))
</div>
and in the controller method, initialize a new instance of UsersVM and assign its properties
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page, int? pageSize)
{
UsersVM model = new UsersVM();
....
var users = from u in _db.USER select u;
....
pageSize = pageSize ?? 10;
model.PageSize = pageSize.Value;
model.Users = users.ToPagedList(pageNumber, pageSize);
model.PageSizeOptions = new List<SelectListItem> { .... };
return View(model);
}

Updating column in ASP.NET mvc 5 creates new row

I'm following this tutorial to create a modified version of a blog. In this case, the "posts" are the same things as "projects," the "tags" are called "technologies," and the comments are all the same. In this case, the create new post/project function also should be able to update existing posts/projects. When I click submit, however, editing an old post, it simply creates a new one.
Here is my controller:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Update(int? p, string title, string shortDescription, string longDescription, DateTime dateTime, string technologies)
{
Project project = GetProject(p);
if (!User.IsInRole("ChapterAdvisor") || !(User.Identity.GetFirstName() + " " + User.Identity.GetLastName()).Equals(project.ProjectLeader))
{
RedirectToAction("Index");
}
project.Title = title;
project.ShortDescription = shortDescription;
project.LongDescription = longDescription;
project.TimeCreated = dateTime;
project.ProjectLeader = User.Identity.GetFirstName() + " " + User.Identity.GetLastName();
project.Technologies.Clear();
technologies = technologies ?? string.Empty;
string[] technologyNames = technologies.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
foreach (string technologyName in technologyNames)
{
project.Technologies.Add(GetTechnology(technologyName));
}
if (!p.HasValue)
{
model.Projects.Add(project);
}
try
{
model.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
{
Exception raise = dbEx;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
string message = string.Format("{0}:{1}",
validationErrors.Entry.Entity.ToString(),
validationError.ErrorMessage);
// raise a new exception nesting
// the current instance as InnerException
raise = new InvalidOperationException(message, raise);
}
}
throw raise;
}
return RedirectToAction("Details", new { p = project.Id });
}
public ActionResult Edit(int? p)
{
Project project = GetProject(p);
StringBuilder technologyList = new StringBuilder();
foreach (Technology technology in project.Technologies)
{
technologyList.AppendFormat("{0} ", technology.Name);
}
ViewBag.Technologies = technologyList.ToString();
return View(project);
}
private Technology GetTechnology(string technologyName)
{
return model.Technologies.Where(x => x.Name == technologyName).FirstOrDefault() ?? new Technology() { Name = technologyName };
}
private Project GetProject(int? id) => id.HasValue ? model.Projects.Where(x => x.Id == id).First() : new Project() { Id = -1 };
And this is my view:
<form action="#Href("~/Projects/Update")" method="post" id="postForm">
#Html.AntiForgeryToken()
#if (Model.Id != -1)
{
<input type="hidden" value="#Model.Id" />
}
#{ DateTime dateTime = Model.TimeCreated.Year > 2000 ? Model.TimeCreated : DateTime.Now; }
<input type="text" name="dateTime" value="#dateTime" /> Date<br />
<input type="text" name="title" value="#Model.Title" /> Project Name<br />
#Html.DropDownListFor(m => m.Technologies, new SelectList(new List<Object> { new { value = "Animation", text = "Animation" }, new { value = "Robotics", text = "Robotics" }, new { value = "Architecture", text = "Architecture" }, new { value = "CAD", text = "CAD" }, new { value = "Websites", text = "Websites" }, new { value = "Games", text = "Games" }, new { value = "Biotechnology", text = "Biotechnology" }, new { value = "Club", text = "Club" }, new { value = "Other", text = "Other" } }, "value", "text"), new { #style = "border: 1px solid #e8e8e8;padding: 0.5em 1.07em 0.5em;background: #f5f5f5;font-size: 0.875rem;border-radius: 5px;width: 100%;line-height: 1.43;min-height: 3.5em;" })
<textarea name="shortDescription" rows="5" cols="80">#Model.ShortDescription</textarea><br />
<textarea name="longDescription" rows="10" cols="80">#Model.LongDescription</textarea><br />
<input type="submit" name="submit" value="Save Changes" />
</form>
Any ideas why it is creating a new "project" instead of updating the one defined by the variable passed in the url?
Every post from that form is being treated as a "new" record because it doesn't contain the ID from an existing record. So the logic always assumes it's new.
This is because the hidden input isn't included in the POST data because it has no name:
<input type="hidden" value="#Model.Id" />
It looks like your action expects the ID value to be called "p":
<input type="hidden" name="p" value="#Model.Id" />

BeginCollectionItem() gives only lastly appended item for PostBack

InquiryOrderViewModel
public class InquiryOrderViewModel
{
public InquiryOrder InquiryOrder { get; set; }
public List<InquiryOrderDetail> InquiryOrderDetails { get; set; }
}
InquiryOrderIndex View and the Script to add items
#model eKnittingData.InquiryOrderViewModel
#using (Html.BeginForm("Save", "InquiryOrder"))
{
<div id="editorRows">
#foreach (var item in Model.InquiryOrderDetails)
{
Html.RenderPartial("_DetailEditorRow", item);
}
</div>
#Html.ActionLink("Add another...", null, null, new { id = "addItem" })
<div class="col-md-6"> <input type="submit" value="Save" class="btn btn-success" /> </div>
}
<script>
$('#addItem').click(function (e) {
e.preventDefault();
var isExist = false;
$('.editorRow').each(function () {
if ($(this).children('.class01').val() == 0 || $(this).children('.class02').find("option:selected").text() == "Select") {
isExist = true;
return false;
}
});
if (isExist == false) {
$('.editorRow').each(function () {
$(".editorRow").children().attr("disabled", "disabled");
});
$.ajax({
url: '#Url.Action("BlankEditorRow", "InquiryOrder")',
cache: false,
success: function (data) {
$("#editorRows").append(data);
}
});
}
});
</script>
DetailEditorRow PartialView
#model eKnittingData.InquiryOrderDetail
#using eKnitting.Helpers
#using (Html.BeginCollectionItem("InquiryOrderDetails"))
{
<div class="editorRow">
#Html.DropDownListFor(a => a.ComponentId, (SelectList)ViewBag.CompList, "Select", new { Class = "class02" })
#Html.DropDownListFor(a => a.DesignCodeId, (SelectList)ViewBag.DCodeList, "Select", new { Class = "class03" })
#Html.TextBoxFor(a => a.NoOfParts, new { Class = "class01" })
delete
</div>
}
ActionResult which returns PartialView
public ActionResult BlankEditorRow()
{
var objContext = new KnittingdbContext();
ViewBag.CompList = new SelectList(objContext.Components, "ComponentId", "ComponentName");
ViewBag.DCodeList = new SelectList(objContext.DesignCodes, "DesignCodeId", "DesignCodeCode");
return PartialView("_DetailEditorRow", new InquiryOrderDetail());
}
ActionResult for 'GET'
var objContext = new KnittingdbContext();
var newIovm = new InquiryOrderViewModel();
var newIo = new InquiryOrder();
//initial item
var newIoD = new List<InquiryOrderDetail>
{
new InquiryOrderDetail()
};
newIovm.InquiryOrder = newIo;
newIovm.InquiryOrderDetails = newIoD;
ViewBag.CompList = new SelectList(objContext.Components, "ComponentId", "ComponentName");
ViewBag.DCodeList = new SelectList(objContext.DesignCodes, "DesignCodeId", "DesignCodeCode");
return View(newIovm);
ActionResult for 'POST'
public ActionResult Save(InquiryOrderViewModel inquiryOrderViewModel)
{
.................
}
When i click the add button im able to add items dynamically. But for PostBack it gives me only the lastly appended item. I checked it by putting a break point on post ActionResult. How can i get the whole collection for PostBack? Where did i go wrong? All help appreciated. Thanks!
Your scripts sets a variable var isExist = false;. When you add a new item, you check if the value is false (which it is if you got that far) and then disable all existing inputs.
Disabled form controls do not post back, hence you only get the values for the last row you have added.
Its unclear why you would want to disable them, but if you want to prevent editing of existing rows, the make them readonly
$(".editorRow").children().prop("readonly", true);

How to add ViewBag property as html class attribute properly?

I have this line:
#Html.ActionLink("Discounts", "ListDiscounts", "Product", null, new { #class = ViewBag.Discount })
The ListDiscounts is:
public ViewResult ListDiscounts(int nrProducts = 5)
{
ViewBag.Discount = "selected";
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products
.Where(p => p.Discount != false)
.Take(nrProducts)
};
return View(model);
}
The View that renders the Menu (where my separate Discounts will also be)
#model IEnumerable<string>
#Html.ActionLink("Home", "List", "Product")
#foreach (var link in Model) {
#Html.RouteLink(link, new
{
controller = "Product",
action = "List",
category = link,
page = 1
},
new
{
#class = link == ViewBag.SelectedCategory ? "selected" : null,
})
}
ListDiscounts.cshtml
#model Sportsstore.WebUI.Models.ProductsListViewModel
#{
ViewBag.Title = "ListDiscounts";
}
<h2>Discounts available</h2>
#foreach (var p in Model.Products)
{
Html.RenderPartial("ProductSummary", p);
}
I'm trying to add the selected class to my 'a' element in a view but this doesn't work. The ViewBag property remains empty when I click on that Discounts link.
The View associated with ListDiscounts is not the one where that ActionLink line is from (they're separate with the one that has it being a Partial View) but from what I understand ViewBag features have some sort of a global state so this should work?
Any ideas on what is wrong here?
EDIT: Using MVC 4
I believe there is something you are not showing us that is the problem. Perhaps you only populated the ViewBag in your post method but not your get method. I created a test application that mocks your app very closely and it works fine.
Controller
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ViewResult Index()
{
ViewBag.Discount = "selected";
return View();
}
[HttpPost]
public ViewResult Index(int nrProducts = 5)
{
var model = new ProductsListViewModel{Products = "stuff"};
ViewBag.Discount = "selected";
return View(model);
}
}
}
View
#model MvcApplication1.Models.ProductsListViewModel
#Html.ActionLink("Discounts", "Index", "Home", null, new { #class = ViewBag.Discount })
#{ Html.RenderPartial("ViewPage1");}
#using (Html.BeginForm())
{
<input type="submit" />
}
Partial
#Html.ActionLink("Discounts", "Index", "Home", null, new { #class = ViewBag.Discount })
When I view the source both links have the class I expected. Also after I click on the link they have the class expected. Thus I believe you are not populating the view bag either on the Get and/or the Post controller method
If you're sure that you have value in the viewbag property instead of
#class = ViewBag.Discount try
#class = #ViewBag.Discount and see if it works
You just try the below code. Change the Viewbag name and try.
#Html.ActionLink("Discounts", "ListDiscounts", "Product", null, new { #class = ViewBag.CssDiscount })
public ViewResult ListDiscounts(int nrProducts = 5)
{
ViewBag.CssDiscount = "selected";
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products
.Where(p => p.Discount != false)
.Take(nrProducts)
};
return View(model);
}

Resources