Posted model is empty - asp.net

I have a ViewModel as following
public class MetalStockAddVM
{
public MetalStock MetalStock { get; set; }
public MetalStockAddVM()
{
this.MetalStock=new MetalStock();
}
}
Here is My controller
public class MetalStockController : Controller
{
private readonly IMetalStockRepository iMetalStockRepository;
public MetalStockController(IMetalStockRepository iMetalStockRepository)
{
this.iMetalStockRepository = iMetalStockRepository;
}
// GET: MetalStock
[HttpGet]
public ActionResult AddMetalStock()
{
MetalStockAddVM addVm=new MetalStockAddVM();
return View(addVm);
}
[HttpPost]
public ActionResult AddMetalStock([Bind(Include = "MetalStock")]MetalStockAddVM metalStock)
{
MetalStockDto metalStockDto = new MetalStockDto();
metalStockDto = Mapper.Map<MetalStock, MetalStockDto>(metalStock.MetalStock);
iMetalStockRepository.Insert(metalStockDto);
return RedirectToAction("Index","Home") ;
}
}
Here is my view
#model LearningSpike.Models.ViewModels.MetalStockAddVM
#using (Html.BeginForm("AddMetalStock","MetalStock",FormMethod.Post))
{
<table>
<tr>
<th>
#Html.LabelFor(m => m.MetalStock.MetalId)
</th>
<td>
#Html.EditorFor(m => m.MetalStock.MetalId)
</td>
</tr>
<tr>
<th>
#Html.LabelFor(m => m.MetalStock.GlobalMaterialId)
</th>
<td>
#Html.EditorFor(m => m.MetalStock.GlobalMaterialId)
</td>
</tr>
<tr>
<th>
#Html.LabelFor(m => m.MetalStock.Length)
</th>
<td>
#Html.EditorFor(m => m.MetalStock.Length)
</td>
</tr>
<tr>
<th>
#Html.LabelFor(m => m.MetalStock.ColourCode)
</th>
<td>
#Html.EditorFor(m => m.MetalStock.ColourCode)
</td>
</tr>
<tr>
<th>
#Html.LabelFor(m => m.MetalStock.QuantityInStock)
</th>
<td>
#Html.EditorFor(m => m.MetalStock.QuantityInStock)
</td>
</tr>
</table>
<input type="submit" value="Create"/>
}
When I post the model that is passed to the controller action method is empty .
Please tell me what I'm doing wrong? I am creating an architectural spike for the first time so I can learn a lot.Thus its my first time implementing all heavy stuff (DI , DTO's , REpositories etc.) . I am still struggling with putting things in right places.
Thanks!
Cheers!

First remove [Bind(Include = "MetalStock")] and second rename metalStock to some something like metalStockVm and see does it works or not.
[HttpPost]
public ActionResult AddMetalStock(MetalStockAddVM metalStockVm)

Please try the below one
change your model code like below
public class MetalStockAddVM
{
public MetalStock MetalStock { get; set; }
}
And then change you Action method code for HTTPPOST in AddMetalStock as below
[HttpPost]
public ActionResult AddMetalStock(MetalStockAddVM metalStock)
{
MetalStockDto metalStockDto = new MetalStockDto();
metalStockDto = Mapper.Map<MetalStock, MetalStockDto>(metalStock.MetalStock);
iMetalStockRepository.Insert(metalStockDto);
return RedirectToAction("Index","Home") ;
}

Related

Checkbox in ASP.NET MVC without using HTML helper method

I've written this code in View. And the picture is the output of it in Chrome Inspect.
#Html.CheckBox("KYCComplete", new { #name = "KYC", #id = "KYC" })
<label class="form-check-label" for="KYC">
KYC Complete ?
<button id="Submit">KYC Complete ?</button>
</label>
In my controller I'm using this HttpPost to use checkbox an filter:
[HttpPost]
public ActionResult Index(bool KYCComplete)
{
if (KYCComplete)
{
List<BankAccount> b= db.BankAccounts.Where(p => p.KYCComplete == true).OrderBy(b => b.City).ToList();
return View(b);
}
return RedirectToAction("Index");
}
Everything works fine, up to this point. Only name property is not overridden.
Well, I want to change the name property of the checkbox to be "KYC" from "KYCComplete".
So, firstly I look for Is there any ways to override HTML helpers. I found in several websites it's not possible to override those.
Now I tried with writing simple HTML checkbox and I'm getting an error.
Server Error in '/' Application.
The parameters dictionary contains a null entry for parameter
'KYCComplete' of non-nullable type 'System.Boolean' for method
'System.Web.Mvc.ActionResult Index(Boolean)' in
'BankAccountsMgmt.Controllers.BankAccountsController'. An optional
parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
So how can I change the name property of this checkbox to "KYC" and bind its input, to filter the desired result.
Describing the question In a Nutshell
As, you have seen the output of the view of this checkbox has name property "KYCComplete".
I've requirement to change to "KYC", and HttpPost should work along with it, without effecting domain model.
Incase extra info. is Required
model:
namespace BankAccountsMgmt.Models
{
public class BankAccount
{
...
[Display(Name = "KYC Complete?")]
public bool KYCComplete { get; set; }
...
}
}
controller:
using BankAccountsMgmt.Data;
using BankAccountsMgmt.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace BankAccountsMgmt.Controllers
{
[CustomFilters.CustomExceptionFilter]
public class BankAccountsController : Controller
{
BankAccountDBContext db = new BankAccountDBContext();
// GET
public ActionResult Index()
{
//Implement your code
List<BankAccount> bank = db.BankAccounts.OrderBy(b=>b.City).ToList();
return View(bank);
}
//Implement other action methods
[HttpPost]
public ActionResult Index(bool KYCComplete)
{
if (KYCComplete)
{
List<BankAccount> AccKYC = db.BankAccounts.Where(p => p.KYCComplete == true).OrderBy(b => b.City).ToList();
return View(AccKYC);
}
return RedirectToAction("Index");
}
public ActionResult AddBankAccount()
{
return View();
}
[HttpPost]
public ActionResult AddBankAccount(BankAccount bankAccount)
{
if (ModelState.IsValid)
{
bankAccount.CalculateInterest();
db.BankAccounts.Add(bankAccount);
db.SaveChanges();
ViewBag.Message = "Bank Account added successfully!";
return View("Details", bankAccount);
}
return View(bankAccount);
}
}
}
full view:
#model List<BankAccountsMgmt.Models.BankAccount>
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Bank Accounts List</h2>
#using (Html.BeginForm("Index", "BankAccounts"))
{
<div class="form-check col-md-offset-8" align="center">
#Html.CheckBox("KYCComplete", new { #name = "KYC", #id = "KYC" })
<label class="form-check-label" for="KYC">
KYC Complete ?
<button id="Submit">KYC Complete ?</button>
</label>
</div>
<table class="table">
<tr>
<th>
Account Holder Name
</th>
<th>
PAN Number
</th>
<th>
City
</th>
<th>
Gender
</th>
<th>
Amount
</th>
<th>
Interest Upto 30 Aug
</th>
<th>
Opening Date
</th>
<th>
KYC Complete?
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AccountHolderName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PAN)
</td>
<td>
#Html.DisplayFor(modelItem => item.City)
</td>
<td>
#Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
#Html.DisplayFor(modelItem => item.Amount)
</td>
<td>
#Html.DisplayFor(modelItem => item.Interest)
</td>
<td>
#Html.DisplayFor(modelItem => item.OpeningDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.KYCComplete)
</td>
</tr>
}
</table>
<span id="total" class="form-check col-md-offset-6" align="center"><b>Interest Total = </b>#Model.Sum(model => model.Interest).ToString("#.##") </span>
}
You could try Html Checkbox instead of using html helper method:
Example:
<input type="checkbox" id="KYC" name="KYC">
<label for="KYC"> KYC Complete</label>
The name of your form control must match the name of the parameter to your action method for model binding to work.
If you change the name of the checkbox to "KYC", but leave the action parameter as "KYCComplete", you will get the exception mentioned in your question.
Change the name in both places.
[HttpPost]
public ActionResult Index(bool KYC)
{
if (KYC)
{
List<BankAccount> b= db.BankAccounts.Where(p => p.KYCComplete == true).OrderBy(b => b.City).ToList();
return View(b);
}
return RedirectToAction(nameof(Index));
}
#Html.CheckBox("KYC", new { #id = "KYC" })

How to create an ASP.NET Core MVC form with a list of check boxes and a submit button? Issue with model binding

I am trying to create a form where a user can select check boxes in a table of products. I want the boxes checked to be stored but there seems to be an issue with the model binding. When I run the debugger, the parameter products for the Submit action is always empty. Here is what I have right now:
Model:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
public string ID { get; set; }
public string Description { get; set; }
public bool Used { get; set; }
public ICollection<ProductCVE> CVE { get; set; }
Controller:
private readonly IProductRepository _repo;
public ProductsController(IProductRepository repo)
{
_repo = repo;
}
// GET: ProductsController
public ActionResult Index()
{
var products = _repo.FindAll().ToList();
return View(products);
}
// POST: ProductsController
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Submit(IEnumerable<Products> products)
{
foreach(Products prod in products)
{
_repo.Update(prod);
}
return RedirectToAction(nameof(Index));
}
View:
#model IEnumerable<KB_Collector_API.Models.Products>
#addTagHelper*, Microsoft.AspNetCore.Mvc.TagHelpers
#{
ViewData["Title"] = "Index";
}
<h1>Products</h1>
<p>
<a asp-action="RunScript">Import Monthly Update</a>
</p>
#using (Html.BeginForm("Submit", "Products", "POST"))
{
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th>
#Html.DisplayNameFor(model => model.CVE)
</th>
<th>
#Html.DisplayNameFor(model => model.Used)
</th>
</tr>
</thead>
<tbody>
#foreach (var product in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => product.Description)
</td>
<td>
<a asp-controller="ProductCVE" asp-action="Index" asp-route-id="#product.ID">Details</a>
</td>
<td>
#Html.CheckBoxFor(modelItem => product.Used)
</td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Submit" />
}
Firsly,you need know that for each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.Your backend wants to receive IEnumerable<Products> which is a list model. so what you pass should be [index].PropertyName.
What you did will render checkbox with name product.Used, it does not match the Products model. Change your html:
#model List<Products> //change here....
#using (Html.BeginForm("Submit", "Products", "POST"))
{
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model[0].Description)
</th>
<th>
#Html.DisplayNameFor(model => model[0].CVE)
</th>
<th>
#Html.DisplayNameFor(model => model[0].Used)
</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].Description)
</td>
<td>
<a asp-controller="ProductCVE" asp-action="Index" asp-route-id="#Model[i].ID">Details</a>
</td>
<td> //importance here...
#Html.CheckBoxFor(modelItem => Model[i].Used)
</td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Submit" />
}
If you want to post all the properties displayed in the html, you also need to add hidden input for Description property, because DisplayFor jsut render a text in html, it cannot be passed to backend:
<td>
#Html.DisplayFor(modelItem => Model[i].Description)
<input hidden asp-for="#Model[i].Description"/>
</td>

#Html.DisplayNameFor() respects [Display] attribute but #Html.DisplayFor() ignores [DisplayFormat] attribute

I have this view model:
public class ConceptViewModel
{
public int Id { get; set; }
[Required(ErrorMessageResourceName = "Message_Required", ErrorMessageResourceType = typeof(Resources.Web.ValidationsViewModels))]
[Display(Name = "ConceptViewModel_Name", ResourceType = typeof(Resources.Web.Contracts))]
public string Concept { get; set; }
[Required(ErrorMessageResourceName = "Message_Required", ErrorMessageResourceType = typeof(Resources.Web.ValidationsViewModels))]
[Display(Name = "ConceptViewModel_TotalUnits", ResourceType = typeof(Resources.Web.Contracts))]
[DisplayFormat(DataFormatString = "{0:###,###}")] // Ignored by #Html.DisplayFor()
public decimal TotalUnits { get; set; }
}
That view model is inside a collection property of another view model that happens to be the one passed to the view:
public class ContratConceptsViewModel : BaseClass
{
public int ContratId { get; set; }
// This collection
public List<ConceptViewModel> Concepts { get; set; }
public ContratConceptsViewModel()
{
Concepts = new List<ConceptViewModel>();
}
}
And then, inside the view, I have this code:
#model ContratConceptsViewModel
#foreach (var concept in Model.Concepts)
{
<thead>
<tr>
<td> #Html.DisplayNameFor(model => concept.Concept) </td> #* Works as expected *#
<td> #Html.DisplayNameFor(model => concept.TotalUnits) </td> #* Works as expected *#
</tr>
</thead>
<tbody>
<tr>
<td> #Html.DisplayFor(model => concept.Concept) </td>
<td> #Html.DisplayFor(model => concept.TotalUnits) </td> #* Doesn't show the correct format *#
</tr>
</tbody>
}
When I execute this code, the [Display] attribute is taken into account, but the [DisplayFormat] attribute is totally ignored. What am I doing wrong?
NOTE: I have searched for a question like this one, and despite there are many others with a similar title, they are different from this one.
Ok, it was all my fault. One of my coworkers created a template for decimal inside the folder Views\Shared\DisplayTemplates\Decimal.cshtml. That's why #Html.DisplayFor() was ignoring the attribute, it was because it was respecting the template created by my coworker.
You should amend you expressions inside DisplayFor and DisplayNameFor.
It must be #Html.DisplayFor(model => model.TotalUnits), not #Html.DisplayFor(model => concept.TotalUnits).
UPDATE
Because you're displaying items from the list #Html.DisplayFor will not work properly: it's supposed to work with single model. I would suggest to extract your template for each item into separate file(it's called display template in MVC):
Your display template(it must be placed in DisplayTemplates folder):
#model ConceptViewModel
<thead>
<tr>
<td> #Html.DisplayNameFor(model => model.Concept) </td>
<td> #Html.DisplayNameFor(model => model.TotalUnits) </td>
</tr>
</thead>
<tbody>
<tr>
<td> #Html.DisplayFor(model => model.Concept) </td>
<td> #Html.DisplayFor(model => model.TotalUnits) </td>
</tr>
</tbody>
Your main page:
#model ContratConceptsViewModel
#foreach (var concept in Model.Concepts)
{
#Html.DisplayFor(model => concept, "name of your display template, optional if you follow conventions")
}
Here is the link about display templates.

ASP.NET MVC - Order By Date

I am trying to sort table (columns: ID, Name, SermonDate, SermonTitle, BibleReading, VideoLink, BulletinLink) by date (SermonDate) so the data with the newest date comes on top and display it in my MVC application.
After some researching, I've tried putting the below with reference to this and this but nothing worked for me - probably because I misunderstood and put wrong code though.
public async Task<IActionResult> Index()
{
return View(await _context.Sermon.OrderBy(sermon =>
DateTime.Now).Take(5).ToListAsync());
}
My current controller (SermonsController.cs):
public SermonsController(BKPCContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.Sermon.OrderBy(sermon => DateTime.Now).Take(5).ToListAsync());
}
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var sermon = await _context.Sermon
.SingleOrDefaultAsync(m => m.ID == id);
if (sermon == null)
{
return NotFound();
}
return View(sermon);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Name,SermonDate,SermonTitle,BibleReading,VideoLink,BulletinLink")] Sermon sermon)
{
if (ModelState.IsValid)
{
_context.Add(sermon);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(sermon);
}
And the html table in /Sermons/Index.cshtml as below:
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.SermonDate)
</th>
<th>
#Html.DisplayNameFor(model => model.SermonTitle)
</th>
<th>
#Html.DisplayNameFor(model => model.BibleReading)
</th>
<th>
#Html.DisplayNameFor(model => model.VideoLink)
</th>
<th>
#Html.DisplayNameFor(model => model.BulletinLink)
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.SermonDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.SermonTitle)
</td>
<td>
#Html.DisplayFor(modelItem => item.BibleReading)
</td>
<td>
#Html.DisplayFor(modelItem => item.VideoLink)
</td>
<td>
#Html.DisplayFor(modelItem => item.BulletinLink)
</td>
<td id="aspButton">
<a role="button" id="btn-primary" class="btn btn-primary" asp-action="Edit" asp-route-id="#item.ID">Edit</a>
<a role="button" id="btn-danger" class="btn btn-danger" asp-action="Delete" asp-route-id="#item.ID">Delete</a>
</td>
</tr>
}
</tbody>
Any help would really be appreciated.
public class Sermon{
public int ID {get;set;}
public DateTime DateAdded {get; set}
}
mySermonList.OrderByDescending(x => x.DateAdded);
i think the issue
sermon => DateTime.Now
you are sorting by DateTime.Now?
if sermon has date added field
sermon => dateadded for example

ASP.Net MVC Posted form values not mapping back onto viewmodel in controller

My ViewModel is:
public class ObjectiveVM
{
public string DateSelected { get; set; }
public List<string> DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
This is passed to the view, populated as expected - and displays correctly in the view.
My problem is when it is posted back to the controller. My controller code to accept it back is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Analyst(ObjectiveVM ovm)
ovm.obList is always showing as null:
My View html is:
#model Objectives.ViewModels.ObjectiveVM
#{
ViewBag.Title = "Analyst";
}
<h2>Copy Objectives for Analyst</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Objective</legend>
#Html.DropDownListFor(model => model.DateSelected, new SelectList(Model.DatePeriod))
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.obList[0].Include)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].AnalystName)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].Title)
</th>
<th>
#Html.DisplayNameFor(model => model.obList[0].Description)
</th>
</tr>
#foreach (var obList in Model.obList)
{
<tr>
<td>
#Html.HiddenFor(modelItem => obList.ObjectiveId)
#Html.HiddenFor(modelItem => obList.AnalystId)
#Html.HiddenFor(modelItem => obList.Title)
#Html.HiddenFor(modelItem => obList.Description)
#Html.CheckBoxFor(modelItem => obList.Include)
</td>
<td>
#Html.DisplayFor(modelItem => obList.AnalystName)
</td>
<td>
#Html.DisplayFor(modelItem => obList.Title)
</td>
<td>
#Html.DisplayFor(modelItem => obList.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Copy Selected Objectives" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Looking in Developer Tools at the Posted form values, they appear to be ok:
Can anyone see any reason the posted form values, are not mapping back onto my viewmodel in the Controller HTTP post?
Thank you, Mark
You need to use a for...loop here, not a foreach....loop.
#for (int idx = 0;idx < Model.obList.Count;idx++){
#Html.HiddenFor(_ => Model.obList[idx].ObjectiveId)
// ... etc....
}
Without the indexer (idx), the model binder will not know how to bind the values back to the right collection item.
When working with collections in my views, I typically write out my markup without the use of helpers:
#for (int i = 0;i < Model.obList.Count();i++){
<input type="hidden" name="ObList[#i].ObjectiveId" id="ObList[#i].ObjectiveId" value="#ObList[i].ObjectiveId" />
<input type="hidden" name="ObList[#i].AnalystId" id="ObList[#i].AnalystId" value="#ObList[i].AnalystId" />
...
}
This will conform to the wire format the model binder expects, and will slot your values into your ViewModel: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Resources