View is posting a empty object on submit when expecting the modified object - asp.net-core

I have a View that lets users edit data that is fetched from a database and converted into a DataTable (for simplicity since the data can get really complicated and deep).
The issue is that when I POST the data back to the responsible Controller the controller receives the DataTable object but it's empty, e.g. the changes made by a user never get back to the Controller and cannot be saved to Database.
I am at most intermediate at web programming so I appreciate straight answers or direct pointers.
View:
#model System.Data.DataTable
#{
ViewData["Title"] = "Edit";
Layout = "~/Views/Shared/_LayoutAdminlte.cshtml";
}
#using (Html.BeginForm("EditSave", "Recipe", FormMethod.Post, new { #id = "Properties-Form" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<h2>#Html.DisplayFor(m => Model.TableName)</h2>
</div>
<div class="box box-primary">
<table id="Properties" class="table table-condensed">
<thead>
<tr>
#foreach (System.Data.DataColumn col in Model.Columns)
{
<th>
#Html.DisplayFor(m => col.Caption)
</th>
}
</tr>
</thead>
<tbody>
#foreach (System.Data.DataRow row in Model.Rows)
{
<tr>
#foreach (var cell in row.ItemArray)
{
<td>
#if (row.ItemArray.ToList().IndexOf(cell) == 0)
{
#cell.ToString()
}
else
{
<input asp-for="#cell" />
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
<div class="box-footer">
<!-- id="Save" -->
<input class="btn btn-primary pull-right" type="submit" value="Spara" id="Save" />
<i class="fa fa-show"></i> Visa
<i class="fa fa-show"></i> Avbryt
</div>
</div>
}
#section Scripts{
#await Html.PartialAsync("_ValidationScriptsPartial")
}
Controller:
[HttpPost]
public ActionResult EditSave(DataTable model)
{
Debugger.Break();
return View("Edit", model);
}

This <input asp-for="#cell" /> is your problem.
If you run your program and use the developer tools to inspect the html generated for the input fields, you will notice the generated html attributes.
the particular one that you should pay attention to is the name attribute.
The way model binding works in .net core when you send data to the controller.
public IActionResult DoSomething(Model model) { ... }
you would have match the name attribute to the property of the object. Example:
<input name="model.Level" value="8999" />
so then you would get var level = model.Level and level will be 8999.
So you have to take care when you use the asp-for it's not doing as much heavy lifting as you think. Always check the html generated.
PS
Don't use DataTables, other developers will throw rocks at you. Don't be lazy map to an actual object or use an ORM, programs quickly become unmaintainable if you use them to dynamically store data.

Related

How to process forms in ASP.NET Core with unknown fields

I want to create an Http post method that will be able to process html forms of different inputs.
[HttpPost]
public async Task<IActionResult> Create(string content)
{
// process content here.
return View("Success");
}
My method should be able to support this form
<form action="url" method="POST">
<input type="text" name="firstName">
<input type="text" name="lastName">
<button type="submit">Submit</button>
</form>
as well as this form
<form action="url" method="POST">
<input type="text" name="name">
<input type="text" name="surname">
<input type="text" name="age">
<button type="submit">Submit</button>
</form>
How can I modify my method so that the content will be populated for both of these cases?
To process forms with unknown fields, you can use the FormCollection type as a parameter in the Create method. The FormCollection type represents a collection of keys and values that are sent as the HTTP request body when the form is submitted. You can use this type to capture all the form data, regardless of the number or names of the fields.
To access the form data, you can use the formData object. For example, you can access a field with the name "firstName" using the following syntax: formData["firstName"].
PdfController.cs:
public class PdfController: Controller
{
[HttpPost]
public IActionResult Create(IFormCollection formData)
{
// process form data here
var surName= formData["surname"].ToString();
return View("Create", (FormCollection)formData);
}
}
Form:
<form action="/pdf/create" method="POST">
<input type="text" name="name">
<input type="text" name="surname">
<input type="text" name="age">
<button type="submit">Submit</button>
</form>
Create.cshtml:
#model FormCollection
<h1>Form Data</h1>
<table>
#foreach (var key in Model.Keys)
{
<tr>
<td>#key</td>
<td>#Model[key]</td>
</tr>
}
</table>
View:
How can I modify my method so that the content will be populated for
both of these cases?
Well, based on your scenario and requirement,we have two option other than [FormBody] pattern,to deal with completely dynamic value, we can use IFormCollection and a Dictionary.
Our goal is to handle any dynamic data which we will finally add into our Dictionary using its Key and Value.
Completely Dynamic Form Request Example:
[HttpPost]
public IActionResult Create(IFormCollection dynamicData)
{
var dynamicFormDataDictionary = new Dictionary<string, object>();
foreach (var item in dynamicData)
{
dynamicFormDataDictionary.Add(item.Key, item.Value);
dynamicFormDataDictionary.Remove("__RequestVerificationToken");
}
return View("DynamicOutput", dynamicFormDataDictionary);
}
Note: We cannot use FormCollection because it will encounter runtime exception as it has Interface of IFormCollection type. .
Bind Dynamic Data And Display in View:
Controller:
public IActionResult DynamicOutput()
{
return View();
}
View:
#model Dictionary<string, object>
<h4>Dynamic Data</h4>
<table class="table">
#foreach (var key in Model.Keys)
{
<tbody>
<tr>
<th>
#key
</th>
<td>
#Model[key]
</td>
</tr>
</tbody>
}
</table>
Output:
Alternative Way:
We have another way, which is using Request.Form but here we need to define our data fields. In following way we can get the data:
var input1 = Request.Form["firstName"].GetValue();
var input2 = Request.Form["name"].GetValue();
Note: Point to remember, in this way, let's say, you have two request from we would define all the property together and allow null-value. Therefore, we would get our desired value from one request form while other we would remain empty.

ReflectionIt pagination generates empty hrefs

Asp.Net Core 2.2 using ReflectionIT.Mvc.Paging 3.5.0. Generated View has empty pagination hrefs. How to solve this?
Controller:
public async Task<IActionResult> Index(int page = 1, string sortExpression = "-LastUpdated")
{
var qry = _context.Invoice.AsNoTracking();
var model = await PagingList.CreateAsync(qry, 10, page, sortExpression, "-LastUpdated");
return View(model);
}
View:
#model ReflectionIT.Mvc.Paging.PagingList<Admin.Models.Person>
#using ReflectionIT.Mvc.Paging
#addTagHelper *, ReflectionIT.Mvc.Paging
<nav aria-label="Person navigation">
#await this.Component.InvokeAsync("Pager", new { pagingList = this.Model })
</nav>
<table class="table table-striped">
<thead>
<tr>
<th>
#Html.SortableHeaderFor(model => model.Name)
</th>
...
<th>
#Html.SortableHeaderFor(model => model.LastUpdated)
</th>
</thead>
...
</table>
Generated pagination:
<nav aria-label="Person navigation">
<ul class="pagination">
<li class="active">
1
</li>
<li>
2
</li>
...
</ul>
</nav>
Generated code uses glyphicon, which is not supported in Bootstrap 4. Perhaps a wrong version the package? Thanks for any help!
You can for instance call the AddPaging method which you can use to set the PagingOptions. This allows you to specify which View is used for the Pager ViewComponent. By default a pager based on Bootstrap3 is used. But there is also already a Bootstrap4 view available. Bootstrap4 doesn't support glyphs any more so if you switch to it you also have to specify alternatives for the Up/Down indicators used in the sortable headers of the table
services.AddPaging(options => {
options.ViewName = "Bootstrap4";
options.HtmlIndicatorDown = " <span>↓</span>";
options.HtmlIndicatorUp = " <span>↑</span>";
});
Reference : https://reflectionit.nl/blog/2017/paging-in-asp-net-core-mvc-and-entityframework-core ,check the Customize section .

How do you open a kendo drop down popup on page load?

I have several uses for kendo drop-downs in my application (DDL, ComboBox, etc.).
I want them to open up on page load, but Kendo's documentation doesn't indicate that is possible.
I am using the MVC server variables.
This is my view coding:
<script id="itemTemplate" type="text/x-kendo-template">
# var index=FullName.indexOf(" *****");
if (index > 0)
{
#
<span style="font-weight:bold;">
#: FullName.substring(0, index)#
</span>
#
} else {
#
<span style="font-weight:normal;">
#: FullName#
</span>
#
}
#
</script>
<table class="form-horizontal table-condensed" style="width:100%;">
<tr style="height:400px;">
<td style="width:40%;vertical-align:top;">
<h4 style="width:100%;text-align:center;">Available Members</h4>
<h4 style="width:100%;text-align:center;font-size:smaller;">Current Cancer Center Members are highlighted in Bold.</h4>
#(Html.Kendo()
.MultiSelect()
.Name("AvailableWGMembers")
.DataTextField("FullName")
.DataValueField("id")
.ItemTemplateId("itemTemplate")
.TagTemplateId("itemTemplate")
.BindTo((System.Collections.IEnumerable)ViewBag.AvailableWGMembers)
.AutoBind(true)
.Placeholder("Click here to select one or more members to add, ...")
.AutoClose(false)
.HtmlAttributes(new { style = "width:100%;", #class = "Roles" })
.Events(events => { events.Change("doRoles");})
.Value(new int[0])
.Height(650)
)
</td>
<td style="width:20%;text-align:center;vertical-align:top;">
<input id="btnAdd" type="submit" value="Select" class="btn btn-default" disabled="disabled" />
</td>
<td style="width:40%;vertical-align:top;">
<h4 style="width:100%;text-align:center;">#Model.WGTitle</h4>
<h4 style="width:100%;text-align:center;font-size:smaller;">Current Cancer Center Members are highlighted in Bold.</h4>
#(Html.Kendo()
.MultiSelect()
.Name("ExistingWGMembers")
.AutoBind(false)
.DataTextField("FullName")
.DataValueField("id")
.ItemTemplateId("itemTemplate")
.TagTemplateId("itemTemplate")
.BindTo((System.Collections.IEnumerable)ViewBag.ExistingWGMembers)
.Placeholder("Click here to select one or more members to remove, ...")
.AutoClose(true)
.HtmlAttributes(new { style = "width:100%;", #class = "UnusedRoles" })
.Events(events => { events.Change("doRoles"); })
.Value(new int[0])
.Height(650)
)
</td>
</tr>
</table>
I want the lists to both be open when the page loads, and I want to be able to use unobstrusive jQuery or javascript to control it if I have to.
Does anyone have any suggestions?
It took a little digging, but I finally figured out the answer. It was actually pretty simple.
The following should be added to the unobstrusive javascript code file:
function openPopup(e)
{
if (e.sender.list[0].childNodes['1'].childNodes['0'].childElementCount > 0) {
e.sender.popup.open();
}
}
You add the following code to your event listing:
.Events(events => { ...; events.DataBound("openPopup"); })
This can be done with any of the lists that have popups like Kendo DropDownList or ComboBox or MultiSelect.
I would check for the list length to make sure the list has members so you don't get an ugly empty list shown, but otherwise the result is actually pretty simple.
This answer is dependent upon the code example at: http://demos.telerik.com/aspnet-mvc/window/index
I took that example from the Index.cshtml version of their example and simply replaced the Content value of the # with your table template from the original question:
#(Html.Kendo().Window()
.Name("window")
.Title("Your modal popup with dropdown menus")
.Content(#<text>
<table class="form-horizontal table-condensed" style="width:100%;">
<tr style="height:400px;">
<td style="width:40%;vertical-align:top;">
<h4 style="width:100%;text-align:center;">Available Members</h4>
<h4 style="width:100%;text-align:center;font-size:smaller;">Current Cancer Center Members are highlighted in Bold.</h4>
#(Html.Kendo()
.MultiSelect()
.Name("AvailableWGMembers")
.DataTextField("FullName")
.DataValueField("id")
.ItemTemplateId("itemTemplate")
.TagTemplateId("itemTemplate")
.BindTo((System.Collections.IEnumerable)ViewBag.AvailableWGMembers)
.AutoBind(true)
.Placeholder("Click here to select one or more members to add, ...")
.AutoClose(false)
.HtmlAttributes(new { style = "width:100%;", #class = "Roles" })
.Events(events => { events.Change("doRoles");})
.Value(new int[0])
.Height(650)
)
</td>
<td style="width:20%;text-align:center;vertical-align:top;">
<input id="btnAdd" type="submit" value="Select" class="btn btn-default" disabled="disabled" />
</td>
<td style="width:40%;vertical-align:top;">
<h4 style="width:100%;text-align:center;">#Model.WGTitle</h4>
<h4 style="width:100%;text-align:center;font-size:smaller;">Current Cancer Center Members are highlighted in Bold.</h4>
#(Html.Kendo()
.MultiSelect()
.Name("ExistingWGMembers")
.AutoBind(false)
.DataTextField("FullName")
.DataValueField("id")
.ItemTemplateId("itemTemplate")
.TagTemplateId("itemTemplate")
.BindTo((System.Collections.IEnumerable)ViewBag.ExistingWGMembers)
.Placeholder("Click here to select one or more members to remove, ...")
.AutoClose(true)
.HtmlAttributes(new { style = "width:100%;", #class = "UnusedRoles" })
.Events(events => { events.Change("doRoles"); })
.Value(new int[0])
.Height(650)
)
</td>
</tr>
</table>
</text>)
.Draggable()
.Resizable()
.Width(600)
.Actions(actions => actions.Pin().Minimize().Maximize().Close())
.Events(ev => ev.Close("onClose"))
)
I hope this helps!

The Index view does not receive the list partial from the ajax call made from Searchbox partial?

i have the following partial view for the the _searchbox partial
#model ecomm2.Models.MainSearchBoxViewModel
<div id="search-box-wrapper" style="text-align:center">
<div id="search-box-div" style="display:inline-block">
#using (Ajax.BeginForm("GetResults", "Partials",
new AjaxOptions {
HttpMethod="get",
InsertionMode=InsertionMode.Replace,
OnSuccess="updateMainResultsDiv",
UpdateTargetId="divMainResultsList"}))
{
<table>
<tr>
<td>
<fieldset>
<label>Search: </label>
</fieldset>
</td>
<td>
<fieldset>
#Html.DropDownListFor(m => m.ProdCatId, Model.MainSearchDDLCollection, new { style="width:200px;"})
</fieldset>
</td>
<td> </td>
<td>
<fieldset>
<input type="text" name="SearchText" style="width:400px"/>
</fieldset>
</td>
<td> </td>
<td><input type="submit" value="Search" /></td>
</tr>
</table>
}
</div>
</div>
<br />
then i have added this _SearchBox partial view to the layout so each page gets a searchbox with a drown category list to filter the search.
note: _searchbox partial is called by Another action in the "Partials" controller and works fine.
the action method "GetResults" is in the "Partials" controller and this is the method that is called by ajax from _SearchBox partial
[AjaxOnly]
public ActionResult GetResults(int ProdCatId, string SearchText)
{
var Products = db.mt_Products
.Where(e => e.SearchString.Contains(SearchText) & e.ProdCatId == ProdCatId)
.Select(e => new HomeSearchResultsViewModel
{
Id=e.Id,
ProdCode=e.ProdCode.Trim(),
ProductLineName= e.mt_Brands.BrandName.Trim() +" "+ e.ProdName.Trim(),
ListPrice=e.ListPrice,
ProdImage=e.ProdImg,
StockCount=e.Stock,
BrandName=e.mt_Brands.BrandName.Trim(),
CategoryName=e.mt_ProductCategories.CatName.Trim()
}).ToList();
return PartialView("_SearchResults", Products);
}
then i have the following code in Index view of the Home controller
#model IEnumerable<ecomm2.Models.HomeSearchResultsViewModel>
#{
ViewBag.Title = "Index";
}
<h2>Welcome Admin,</h2>
<p>
</p>
<div id="divMainResultsList">
#Html.Partial("_SearchResultsList", Model)
</div>
<script type="text/javascript">
function updateMainResultsDiv() {
$('#divMainResultsList').html()
}
</script>
when i passed a value to the search box and performed a search, it showed that search values were received by the action method GetResults and LINQ generates results but result were not placed on div area. Is this div placed on right spot?
it seems a bit complicated to post all and here is the video

Call post edit action from Razor view mvc4

This question might have been asked several times, however it is not working in my case, so please bear with me.
I have the below actions in my controller:
[HttpPost]
public ActionResult Edit(Organization obj)
{
if (ModelState.IsValid)
{
OrgRepo.Update(obj);
return RedirectToAction("Details");
}
else
return View();
}
public ActionResult Edit(int id)
{
return View();
}
I am trying to update the data into database by calling post edit action.
For this purpose, I am calling the edit action as below:
#foreach (var item in Model) {
var test = item.PartyId;
<tr id="#test">
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.Caption)"/>
</td>
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.NameInUse)"/>
</td>
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.Description )"/>
</td>
<td>
#using (Html.BeginForm())
{
#Html.ActionLink("Edit", "Edit", "Org", null, new { #obj = item })
}
</td>
</tr>
However when I click on edit I am getting exception:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Edit(Int32)' in 'Dwiza.Controllers.OrgController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
My questions:
How can I fix this?
Why it get edit action is getting invoked instead of post edit?
What are better ways to invoke edit action among invoke through jQuery, or ajax or any others, if you can suggest better ways of doing it?
The #Html.ActionLink produces an a tag which can only be used to call GET. Change to a submit button to get a POST.
Normally with an Edit, you are only editing a sinble model rather than a collection on a page but going with what you have, change the cshtml to:
#model ICollection<Organization>
<table>
#foreach (var item in Model)
{
using (Html.BeginForm())
{
var test = item.PartyId;
<tr id="#test">
<td class="txt">
<input type="text" name="Caption" class="txt" value="#item.Caption"/>
</td>
<td class="txt">
<input type="text" name="NameInUse" class="txt" value="#item.NameInUse"/>
</td>
<td class="txt">
<input type="text" name="Description" class="txt" value="#item.Description" />
</td>
<td>
<input type="hidden" name="PartyId" value="#item.PartyId"/>
<button type="submit">Edit</button>
</td>
</tr>
}
}
</table>
Now each table row is wrapped by a form meaning the submit button will post that data. The name attribute on the inputs will cause the MVC model binders to bind your posted values to your model correctly.
This hidden input at the end will ensure your PartyId value gets posted back. The fact that it is in int (and not nullable) was giving the exception with your initial code I think.
HTH
EDIT
Adding controller code (note - I still think this is a little/lot strange as you should be editing only the one Organisation...
public ActionResult Edit(int id)
{
// get your organisations from your orgRepo... I'm mocking that out.
var orgs = new List<Organization> { new Organization { PartyId = 1, Description = "Org 1", Caption = "Caption 1", NameInUse = "Name 1"},
new Organization { PartyId = 2, Description = "Org 2", Caption = "Caption 2", NameInUse = "Name 2"}};
return View(orgs);
}
lordy, that is a mess dude. your form only has a link in it, and that link is to the edit action, that will invoke a get, the form will never post back. are you trying to do a form inside a table row?
#foreach (var item in Model) {
var test = item.PartyId;
<tr>
<td colspan ="4>
#using (Html.BeginForm("Edit", "Org", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item.PartyId)
<table>
<tr id="#test">
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.Caption)"/>
</td>
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.NameInUse)"/>
</td>
<td class ="txt">
<input type="text"class="txt" value="#Html.DisplayFor(modelItem => item.Description )"/>
</td>
<td>
<input type="submit" value="edit" />
</td>
</tr>
</table>
}
</td>
</tr>
}
That code will do an edit inside a row, but i am just guessing at the structure from the code you posted.