Model List property is NULL on Post in controller - c#

So I have tested on a clean project an issue I've been getting, and have done the following code setup for checking if a custom Class object still returns null placed in a List:
VIEW
<div>
<div class="jumbotron">
<h1 class="display-4"><span class="fas fa-user-secret"></span> Babcock Canada - Application Template</h1>
<br />
<div class="alert alert-primary" role="alert">
<span class="fas fa-info-circle"></span>
<span> This is the Babcock Canada MVC Application template for use in developing content-rich web applications.</span>
</div>
<hr class="my-4" />
</div>
<div>
<div class="container-fluid">
#if (!string.IsNullOrEmpty(Model.errorMessage))
{
<div class="alert alert-danger" role="alert">
<span class="fas fa-stop-circle"></span> #Html.DisplayFor(alert => alert.errorMessage)
</div>
}
#if (!string.IsNullOrEmpty(Model.successMessage))
{
<div class="alert alert-success" role="alert">
<span class="fas fa-check-circle"></span> #Html.DisplayFor(alert => alert.successMessage)
</div>
}
<div>
#using (Html.BeginForm("TestAction", "Default", FormMethod.Post))
{
#Html.HiddenFor(m => Model.tester[0].tester)
<button type="submit" class="btn btn-primary">
Submit 1
</button>
}
</div>
<div>
#using (Html.BeginForm("TestAction", "Default", FormMethod.Post))
{
#Html.HiddenFor(m => Model.tester[1].tester)
<button type="submit" class="btn btn-primary">
Submit 2
</button>
}
</div>
</div>
</div>
TestClass.cs
namespace Test.Models
{
public class TestClass
{
public string tester { get; set; }
}
}
MODEL
namespace Test.Models
{
/// <summary>
/// This is the default template model.
/// </summary>
public class DefaultModel : SharedModel
{
public string errorMessage = string.Empty;
public string successMessage = string.Empty;
public List<TestClass> tester { get; set; }
public DefaultModel()
{
}
public void Init()
{
tester = new List<TestClass>
{
new TestClass { tester = "Testing..." },
new TestClass { tester = "Testing2..." }
};
}
}
}
CONTROLLER
[HttpPost]
public ActionResult TestAction(DefaultModel model)
{
return View(model);
}
So the result is that the second one returns NULL in the list, but the first one returns just fine.
In my other project index 0 of a list looped in the same way returns the error: "An item with the same key has already been added."
So what am I doing wrong?

try using the helper Html.Hidden() instead
<div>
#using (Html.BeginForm("TestAction", "Default", FormMethod.Post))
{
#Html.Hidden("tester", Model.tester[0].tester)
<button type="submit" class="btn btn-primary">
Submit 1
</button>
}
</div>
<div>
#using (Html.BeginForm("TestAction", "Default", FormMethod.Post))
{
#Html.Hidden("tester", Model.tester[1].tester)
<button type="submit" class="btn btn-primary">
Submit 2
</button>
}
</div>

Turns out it doesn't work when the form is inside the loop, has to be outside and reference an ID value through the submit button holding the name "ID" and the value of that list item.
Suppose using Html.Hidden() would work, but that isn't what was required for the project.

Related

Data not being passed from Razor page

I have a Blazor app that takes user input via a form field and puts it into a database.
However, the data is not being passed from the front end correctly:
Razor file
#using Blogs.Shared.Models
#page "/addpost"
#inject HttpClient Http
#inject NavigationManager NavigationManager
<h2>Create Post</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form>
<div class="form-group">
<label for="Name" class="control-label">Titles</label>
<input for="Name" class="form-control" bind="#posts.title" />
</div>
<div class="form-group">
<label for="Address" class="control-label">Content</label>
<input for="Address" class="form-control" bind="#posts.content" />
</div>
<div class="form-group">
<input type="button" class="btn btn-default" onclick="#(async () => await tas())" value="Save" />
<input type="button" class="btn" onclick="#Cancel" value="Cancel" />
</div>
</form>
</div>
</div>
#functions {
public Posts posts = new();
protected async Task tas()
{
await Http.PostAsJsonAsync("api/Posts/Create", posts);
NavigationManager.NavigateTo("/listposts");
}
void Cancel()
{
NavigationManager.NavigateTo("/listposts");
}
}
What I would expect this to do, is when Save is pressed, the data from Title and Content is assigned to posts and then passed to my POST method:
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Blogs.Shared.Models;
using Microsoft.AspNetCore.Mvc;
namespace Blogs.Server.Controllers
{
public class PostsController : Controller
{
private readonly IDataAccessProvider _dataAccessProvider;
private readonly ILogger _logger;
public PostsController(IDataAccessProvider dataAccessProvider, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger("PostsController");
_dataAccessProvider = dataAccessProvider;
}
[HttpPost]
[Route("api/Posts/Create")]
public void Create(Posts post)
{
_logger.LogCritical("Data 1", post);
_logger.LogCritical("Data 2", post.content);
_logger.LogCritical("Data 3", post.title);
_dataAccessProvider.AddPosts(post);
}
}
}
All of my _logger.LogCritical lines just return blank, and when the write to the DB occurs it complains that title is empty (this field in my DB is set to NOT NULL).
Can anyone help as to why this is not working?
EDIT
I have updated the code to better match Antoine B's suggestions but its still not working:
#page "/newpost"
#using Blogs.Shared.Models
#inject HttpClient Http
#inject NavigationManager NavigationManager
<h1>#Title Post</h1>
<hr />
<EditForm Model="#posts" OnValidSubmit="SaveUser">
<DataAnnotationsValidator />
<div class="mb-3">
<label for="Name" class="form-label">Title</label>
<div class="col-md-4">
<InputText class="form-control" #bind-Value="posts.title" />
</div>
<ValidationMessage For="#(() => posts.title)" />
</div>
<div class="mb-3">
<label for="Address" class="form-label">Content</label>
<div class="col-md-4">
<InputText class="form-control" #bind-Value="posts.content" />
</div>
<ValidationMessage For="#(() => posts.content)" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Save</button>
<button class="btn btn-light" #onclick="Cancel">Cancel</button>
</div>
</EditForm>
#code {
protected string Title = "Add";
protected Posts posts = new();
protected async Task SaveUser()
{
await Http.PostAsJsonAsync("api/Posts/Create", posts);
Cancel();
}
public void Cancel()
{
NavigationManager.NavigateTo("/listposts");
}
}
To bind a value to an input component you must add an # before the bind property, like following #bind="posts.title" (see https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-6.0)
That's what's not working for you.
I also don't recommend you to use the #functions because it is not recommended by Microsoft for razor files (see https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.0#functions)
#code {
public Posts posts = new();
protected async Task tas()
{
await Http.PostAsJsonAsync("api/Posts/Create", posts);
NavigationManager.NavigateTo("/listposts");
}
void Cancel()
{
NavigationManager.NavigateTo("/listposts");
}
}
I hope it helped you

RuntimeBinderException: Cannot perform runtime binding on a null reference

I'm making a create item page, and in this create item page there is a popup modal table where we can choose the type of UoM that we want. And normally when this form is submitted with all of the fields filled in, it saved the values into the database. But when the form is submitted with one or some or all of the fields not filled in, it supposed to give some error message that the fields are required. But it didn't and it shows this error.
These are my code
ItemController
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using CRMandOMS.Models;
using CRMandOMS.ViewModels;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace CRMandOMS.Controllers
{
public class ItemController : Controller
{
private readonly IItemRepository _itemRepository;
private readonly IUoMRepository _uoMRepository;
public ItemController(IItemRepository itemRepository, IUoMRepository uoMRepository)
{
_itemRepository = itemRepository;
_uoMRepository = uoMRepository;
}
// GET: /<controller>/
public ViewResult Index()
{
var model = _itemRepository.GetAll();
return View(model);
}
public ViewResult Details(Guid? id)
{
Item item = _itemRepository.GetById(id.Value);
return View(item);
}
[HttpGet]
public ViewResult Create()
{
ItemCreateViewModel itemCreateViewModel = new ItemCreateViewModel()
{
UoMs = _uoMRepository.GetAll()
};
return View(itemCreateViewModel);
}
[HttpPost]
public IActionResult Create(ItemCreateViewModel model)
{
if (ModelState.IsValid)
{
Item newItem = new Item
{
Name = model.Name,
Price = model.Price,
UoMId = model.UoMId
};
_itemRepository.Insert(newItem);
return RedirectToAction("Details", new { id = newItem.Id });
}
return View();
}
}
}
Create
#model CRMandOMS.ViewModels.ItemCreateViewModel
#{
ViewData["Title"] = "Item Create";
}
<h2>Item Create</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a asp-controller="Item" asp-action="Index">Item</a></li>
<li class="breadcrumb-item active" aria-current="page">Create</li>
</ol>
</nav>
<form enctype="multipart/form-data" asp-controller="Item" asp-action="Create" method="post" class="mt-3">
<div class="form-group row">
<label asp-for="Name" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="Name" class="form-control" placeholder="Name" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<label asp-for="Price" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="Price" class="form-control" placeholder="Price" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<label asp-for="UoMId" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="UoMId" id="uomid" class="form-control" hidden />
<div class="input-group mb-3">
<input id="uomname" type="text" class="form-control" placeholder="UoM" aria-label="UoM" aria-describedby="button-uom" disabled>
<div class="input-group-append">
<button class="btn btn-outline-success" type="button" id="button-uom" data-toggle="modal" data-target="#uoMLookupTableModal">Select UoM</button>
</div>
</div>
<span asp-validation-for="UoMId" class="text-danger"></span>
</div>
</div>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group row">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<a asp-controller="Item" asp-action="Index" class="btn btn-light">Back</a>
<button type="submit" class="btn btn-success">Create</button>
</div>
</div>
</form>
#{
await Html.RenderPartialAsync("_UoMLookup");
}
#section scripts {
<script>
$(document).ready(function () {
var uoMTable = $("#uoMTable").DataTable({
"columnDefs": [
{
"targets": [0],
"visible": false
}
],
"order": [[1, "asc"]]
});
$('#uoMTable tbody').on('click', 'tr', function () {
if ($(this).hasClass('table-success')) {
$(this).removeClass('table-success');
}
else {
uoMTable.$('tr.table-success').removeClass('table-success');
$(this).addClass('table-success');
}
});
$("#getUoM").click(function () {
var uomdata = uoMTable.row('.table-success').data();
//alert(uomdata[0]);
$('#uomid').val(uomdata[0]);
//alert(uomdata[1]);
$('#uomname').val(uomdata[1]);
});
});
</script>
}
_UoMLookup
<div class="modal fade" id="uoMLookupTableModal" tabindex="-1" role="dialog" aria-labelledby="uoMLookupTableModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<table id="uoMTable" class="table table-striped table-bordered table-bordered nowrap" style="width:100%">
<thead>
<tr>
<td>Id</td>
<td>Name</td>
<td>Description</td>
</tr>
</thead>
<tbody>
#foreach (UoM uom in Model.UoMs)
{
<tr>
<td class="uom-id">#uom.Id</td>
<td class="uom-name">#uom.Name</td>
<td>#uom.Description</td>
</tr>
}
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-dismiss="modal">Cancel</button>
<button id="getUoM" type="button" class="btn btn-success" data-dismiss="modal">Select</button>
</div>
</div>
</div>
</div>
ItemCreateViewModel
using CRMandOMS.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace CRMandOMS.ViewModels
{
public class ItemCreateViewModel
{
[Required]
[MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters")]
public string Name { get; set; }
[Required(ErrorMessage = "{0} is required")]
[Range(1000, 999999999)]
public int Price { get; set; }
[Required]
public Guid UoMId { get; set; }
public IEnumerable<UoM> UoMs { get; set; }
public string PhotoPath { get; set; }
}
}
In the HTTP POST Create method (ItemController) if the model is not valid (so ModelState.IsValid == false) you are not passing a model to your View. Ensure passing a valid model, as shown in the controller methods tutorial.
But when the form is submitted with one or some or all of the fields not filled in, it supposed to give some error message that the fields are required. But it didn't and it shows this error.
You do not have a reference to validation scripts, make sure you have _ValidationScriptsPartial.cshtml in Shared folder, then modify your code:
#section scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script>
//...
</script>
}
For the error on your page, just like other community has said, it is likely that the model state is invalid and it execute return View() without returning any data to create view.
However,your partial view does not allow the Model.UoMs to be null.
In your Create Post action, if the model contains UoMs, you could just use
return View(model)
otherwise ,assign UoMs data to model like what you have done in Create Get action, then return it to view.
You could always use a breakpoint on the Post action to debug the result.

How to combine nested model lambdas in razor syntax

My View Model
public class PostViewModel
{
public Post Post { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
In my razor view I am trying to get data for my comment
#using (Html.BeginForm("Comment", "Post"))
{
<div class="form-group">
#Html.LabelFor(m => m.Comments.Data);
#Html.TextAreaFor(m => m.Comments.Data, new { #class = "form-control" })
</div>
#Html.HiddenFor(m => m.Comments.Id)
<button type="submit" class="btn btn-primary"> Comment </button>
}
But I am getting errors
then I tried the following syntax
#using (Html.BeginForm("Comment", "Post"))
{
#foreach(var comment in Model.Comments)
{
<div class="form-group">
#Html.LabelFor(comment.Data);
#Html.TextAreaFor(comment.Data, new { #class = "form-control" })
</div>
#Html.HiddenFor(comment.Id)
<button type="submit" class="btn btn-primary"> Comment </button>
}
}
But I am still getting errors
actually I am doing a blog project
So, I am hoping to have Post, all old comment and new comment button in a Details page
Your lambda syntax is wrong. The following will compile and work but the values won't be posted back to the controller action:
#foreach(var comment in Model.Comments)
{
#Html.LabelFor(x=> comment.Data)
}
Secondly, for posting collection back to action it should be done in a for loop with index named controls as the model binder binds it back to collection using the names of the input control that will not be generated in the format which model binder needs.
Do like:
#for(int i=0; i < Model.Comments.Count(); i++)
{
<div class="form-group">
#Html.LabelFor(x => Model.Comments[i].Data);
#Html.TextAreaFor(x => Model.Comments[i].Data, new { #class = "form-control" })
</div>
#Html.HiddenFor(x => Model.Comments[i].Id)
<button type="submit" class="btn btn-primary"> Comment </button>
}
Ehsan Sajjad Made me understand that we can write lambda expression like this
#Html.LabelFor(x=> comment.Data)
later I solved my problem, actually my approach was quite wrong
for solving my problem I add another component NewComment in my ViewModel
public class PostViewModel
{
public Post Post { get; set; }
public IEnumerable<Comment> Comments { get; set; }
public Comment NewComment { get; set; } // this is new addition
}
Then My New Comment area is like the following in the razor syntax
#using (Html.BeginForm("Comment", "Post", Model.Post))
{
var comment = Model.NewComment;
<div class="form-group">
#Html.LabelFor(m => comment.Data);
#Html.TextAreaFor(m => comment.Data, new { #class = "form-control" })
</div>
#Html.HiddenFor(m => comment.Id)
<button type="submit" class="btn btn-primary"> Comment </button>
}
I am doing a project where in Details view
Firstly, Appeared a Post
Secondly, It's comment
Thirdly, A section for it's new comment
Full code for it's details page
#model SimpleBlog.Models.PostViewModel
#{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#*Post Section*#
<div class="jumbotron">
<h1> #Model.Post.Title </h1>
<p class="lead"> #Model.Post.PostedBy </p>
<p> #Model.Post.PostDate.ToString("d MMM yyyy") </p>
</div>
<br />
<div class="jumbotron">
<p class="lead"> #Model.Post.Body </p>
</div>
#* Old comments section *#
#foreach (var comment in Model.Comments)
{
<h4> #comment.CommentBy </h4>
<h4> #comment.CommentDate.ToString("d MMM yyyy") </h4>
<h4> #comment.Data </h4>
<br />
<br />
}
#* New Comment section *#
#using (Html.BeginForm("Comment", "Post", Model.Post))
{
var comment = Model.NewComment;
<div class="form-group">
#Html.LabelFor(m => comment.Data);
#Html.TextAreaFor(m => comment.Data, new { #class = "form-control" })
</div>
#Html.HiddenFor(m => comment.Id)
<button type="submit" class="btn btn-primary"> Comment </button>
}

Return Partial View Via Controller Method

I have a Search Partial View that I want to return but I want to return it by running it through an exisiting Partial View Result in the Controller instead of loading the view directly.
So, in the controller I have:
public ActionResult Index()
{
return View();
}
public PartialViewResult _GetSearch(List<Search> model)
{
return PartialView("_Search", model);
}
[ValidateAntiForgeryToken()]
public PartialViewResult _BeginSearch(string search)
{
var results = SearchModels(search).ToList();
return PartialView("_GetSearch", results);
}
And in the search view itself I have:
<div class="col-md-4">
<div id="modelSearch" class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-search"></i> Search by Model / Manufacturer</h3>
</div>
<div class="panel-body">
#using (Ajax.BeginForm("_BeginSearch", "Home", new AjaxOptions() { UpdateTargetId = "modelSearch" }))
{
#Html.AntiForgeryToken()
<div class="input-group">
#Html.TextBox("search", null, new {id = "name", #class = "form-control", placeholder = "Please enter a manufacturer or model"})
<span class="input-group-btn">
<button id="search" class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
if (Model != null)
{
<div class="searchResults fade">
#foreach (var s in Model)
{
<div class="result">
#switch (s.ResultType)
{
case "Man":
#s.Manufacturer
break;
case "Mod":
#s.Manufacturer #s.Model
<img src="~/Images/General/(#s.TierId).png" alt="Tier #s.TierId"/>
break;
}
</div>
}
</div>
}
}
</div>
</div>
</div>
When I try and run the code it tell me that it cannot find the _GetSearch view, which yes technically is right, but I'm not looking for a view I'm looking for a method in the controller.

When I press submit button there is no postback

I am new to MVC and I am stuck in creating a submit form.
Model Email.cs:
using System.Web;
namespace MySite.Models
{
public class Email
{
public string From { get; set; }
public string Subject { get; set; }
public string body { get; set; }
}
}
Controller CommunicationController.cs:
namespace MySite.Controllers
{
public class CommunicationController : Controller
{
public ActionResult SendEmail() {
Email email = new Email();
return View(email);
}
[HttpPost]
public ActionResult SendEmail(Email email)
{
if (ModelState.IsValid)
{
}
return View(email);
}
}
}
View SendEmail.cshtml:
#model MySite.Models.Email
#{
ViewBag.Title = "SendEmail";
}
<h2>#Html.Label("Send email")</h2>
#using(Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.Label("From")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.From)
</div> <div class="editor-label">
#Html.Label("Subject")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Subject)
</div> <div class="editor-label">
#Html.Label("Body")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.body)
</div>
<input id="btnSubmit" type="submit" value="SendEmail" />
}
When I press submit, the event never gets fired. In the controller if I press 'go to view' then it goes to SendEmail view. I have no idea whats happenning. I tried to debug but the [HttpPost] controller never gets fired.
Here is what I get from browser, I don't see action
<form method="post" action="/" novalidate="novalidate">
<input type="hidden" value="ZktM_I7fzdlcNme4YVEcNNpnFFmQu1cpAuTXarO_V4w-7bPmpHkaLRfNY3cXGMYy7wkRgSJWW‌​SkS8lp5vdRimFrNCgqk0Jfdr4v7Zc3V2pg1" name="__RequestVerificationToken">
<div class="editor-label">
<div class="editor-field">
<input id="From" class="text-box single-line" type="text" value="" name="From">
</div>
<div class="editor-label">
<div class="editor-field">
<div class="editor-label">
<div class="editor-field">
<input id="btnSubmit" type="submit" value="SendEmail">
</form>
Try this,
#using (Html.BeginForm("ActionName", "ControllerName",
FormMethod.Post))
{
//form UI
<input id="btnSubmit" type="submit" value="SendEmail" />
}
Edit
You can also try this:
<a onclick="$('#formId').submit();">Submit</a>
or
<button type="submit">Submit</button>
I rarely use the basic helper, so I could be wrong, but I believe the default method you get from
Html.BeginForm()
is a GET. So, you probably want to use
Html.BeginForm("SendEmail", "Communication", FormMethod.Post, new { /* html attributes */ })
Then, to test whether you're actually hitting the controller, add this inside the action:
ModelState.AddModelError("", "Action was called!");
That will show up as an error in your ValidationSummary, which is okay, since we're just debugging here.
I'm a little late to the party...
Try replacing:
<input id="btnSubmit" type="submit" value="SendEmail" />
With:
<button id="btnSubmit" type="submit" name="submitButton value="SendEmail" />
Let us know if this works, or if you found another solution! :)

Categories

Resources