I have the following cshtml form:
model Scraper.Facade.PlayerRow
#using (Html.BeginForm("Calculate", "Home", FormMethod.Post))
{
<table class="table table-striped table-bordered table-condensed table-responsive table-hover">
#foreach (var player in Model.AttribsPlayerLine)
{
<thead>
<tr class="success">
#foreach (var attrib in player.AttribsPlayerList)
{
//#Html.DisplayNameFor(x => Model.tytul)
<th data-field="#attrib.attribName">#Html.DisplayFor(x => attrib.attribName) </th>
}
</tr>
<tr class="active">
#foreach (var attrib in player.AttribsPlayerList)
{
<td data-field="#attrib.attribValue">#Html.TextBoxFor(x => attrib.attribValue)</td>
}
</tr>
</thead>
}
</table>
<input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}
which is displaying correctly and then I am trying to get the model in the following controller ActionResult
[HttpPost]
public ActionResult Calculate(PlayerRow model)
{
GenericHelper _genericHelper = new GenericHelper();
return View();
}
However the PlayerRow model is always null.
What am I doing wrong?
This is my model definition
public PlayerRow LoadHtmlDoc(string fileLocation)
{
List<Attrib> attribsHeaderList = new List<Attrib>();
var playerRow = new PlayerRow();
playerRow.AttribsPlayerLine = new List<AttribLine>();
var htmlDoc = new HtmlDocument { OptionFixNestedTags = true };
// There are various options, set as needed
// filePath is a path to a file containing the html
htmlDoc.Load(fileLocation);
}
public class PlayerRow
{
public List<AttribLine> AttribsPlayerLine;
}
UPDATE
Hi All, I changed a bit the logic of my application, basically having 2 lists which has the Header Attributes, and the Attributes for all the players, so only 2 classes now, and I changed the cshtml like this, which is working now :-
#using (Html.BeginForm("Calculate", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table class="table table-striped table-bordered table-condensed table-responsive table-hover">
<tr>
#for (int k = 0; k < Model.HeaderAttributes.Count; k++)
{
<td>
#Html.DisplayFor(x => x.HeaderAttributes[k].AttributeName)
#Html.HiddenFor(x => x.HeaderAttributes[k].AttributeName)
</td>
}
</tr>
#for (int i = 0; i < Model.PlayerList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x.PlayerList[i].PlayerName)
#Html.HiddenFor(x => x.PlayerList[i].PlayerName)
</td>
#for (int j = 0; j < Model.PlayerList[i].AttributesList.Count; j++)
{
<td>
#Html.DisplayFor(x => x.PlayerList[i].AttributesList[j].AttributeValue)
#Html.HiddenFor(x => x.PlayerList[i].AttributesList[j].AttributeValue)
</td>
}
</tr>
}
</table>
<input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}
Now my problem is, who to award the Answer, since I can say that most of the Answers were really helpful to arrive to my solution
Here is a working example of what you're trying to do. This is about as a close as I can get you.
Let's start with the simplified models:
public class PlayerRow
{
public List<AttribLine> AttribsPlayerLine { get; set; }
}
public class AttribLine
{
public string attribName { get; set; }
public string attribValue { get; set; }
}
Note that it is IMPORTANT to include the { get; set; } on each model property so the model binder knows it's on the block for binding.
Next is a simplified view looking only at the form() section:
#using (Html.BeginForm("Calculate", "PlayerRow", FormMethod.Post))
{
<table>
#*/*Build out header*/*#
<tr>
#foreach (AttribLine a in Model.AttribsPlayerLine)
{
<th>#Html.Label("title", a.attribName)</th>
}
</tr>
#*/* Add row of our player attributes */*#
<tr>
#foreach (AttribLine a in Model.AttribsPlayerLine)
{
using (Html.BeginCollectionItem("AttribsPlayerLine"))
{
<td>
#Html.TextBox("attribValue", a.attribValue)
#Html.Hidden("attribName", a.attribName)
#*
/* Add any more [AttribLine] properties as hidden here */
*#
</td>
}
}
</tr>
</table>
<input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}
Note that it is IMPORTANT here to make sure that even though a property is not editable by the user, it needs to be included as a hidden element inside our CollectionItem so the model binder can SET it on the [POST]
Hope this helps.
You are correctly passing the model to the view, but once it gets to the view, the model data will essentially be 'cleared' unless you either edit it or pass it on the next controller, in this situation "Calculate".
I'm not sure exactly what parts of the viewmodel you want passed to the controller, but you can always use this:
#Html.HiddenFor(model => model.DataYouWantPassedToController)
Now, you can always use these items also to pass data if you want to edit/change:
#Html.EditorFor(model => model.DataYouWantToEditAndPass)... and so on.
If you simple loop through data without changing or passing it, like you do in the #foreach, the data will be lost during your 'Post' method.
Try use a #Html.EditorForModel() like this:
#model Scraper.Facade.PlayerRow
#using (Html.BeginForm("Calculate", "Home", FormMethod.Post))
{
#Html.EditorForModel()
<input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}
Create a file named PlayerRow.cshtml as PartialView on the folder Views/Shared/EditorTemplates and add the following code:
#model Scraper.Facade.PlayerRow
<table class="table table-striped table-bordered table-condensed table-responsive table-hover">
#foreach (var player in Model.AttribsPlayerLine)
{
<thead>
<tr class="success">
#foreach (var attrib in player.AttribsPlayerList)
{
//#Html.DisplayNameFor(x => Model.tytul)
<th data-field="#attrib.attribName">#Html.DisplayFor(x => attrib.attribName) </th>
}
</tr>
<tr class="active">
#foreach (var attrib in player.AttribsPlayerList)
{
<td data-field="#attrib.attribValue">#Html.TextBoxFor(x => attrib.attribValue)</td>
}
</tr>
</thead>
}
</table>
I think that this will help you.
Related
Here is the Model:
public class TestModel
{
public string Data { get; set; }
public List<string> Datas { get; set; }
}
Here is the view:
#model InventoryWeb.Models.TestModel
#using (Html.BeginForm())
{
#Html.DisplayFor(modelItem => Model.Data)
#Html.HiddenFor(m => m.Data)
<table class="table">
<tr>
<th>
Data
</th>
<th></th>
</tr>
#for (int i = 0; i < Model.Datas.Count; i++)
{
#Html.Hidden("Datas["+ i + "]", Model.Datas[i])
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Datas[i])
</td>
<td>
<input type="submit" value="Submit" formaction="SubmitTest" formmethod="post"/>
</td>
</tr>
}
</table>
}
How to pass the selected row number (the number of row on which user pressed the "Submit" button) to controller? TempData/Viewbag/whatever it is possible to use to pass this info to Controller site when Submitting.
The way you wrote it, it will send all the data back to server for any row.
You can insert a form in each row:
#model InventoryWeb.Models.TestModel
#Html.DisplayFor(modelItem => Model.Data)
<table class="table">
<tr>
<th>
Data
</th>
<th></th>
</tr>
#for (int i = 0; i < Model.Datas.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Datas[i])
</td>
<td>
#using (Html.BeginForm())
{
#Html.Hidden("rowNumber", i)
<input type="submit" value="Submit" formaction="SubmitTest" formmethod="post"/>
}
</td>
</tr>
}
</table>
But I prefer using javascript in this situations.
Edit
With JavaScript:
#using (Html.BeginForm("SubmitTest"))
{
#Html.DisplayFor(modelItem => Model.Data)
#Html.HiddenFor(m => m.Data)
<input type="hidden" id="rowNumber" name="rowNumber" />
<table class="table">
<tr>
<th>
Data
</th>
<th></th>
</tr>
#for (int i = 0; i < Model.Datas.Count; i++)
{
#Html.Hidden("Datas["+ i + "]", Model.Datas[i])
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Datas[i])
</td>
<td>
<input type="button" value="Submit" onclick="SubmitForm(#i, this);"/>
</td>
</tr>
}
</table>
}
<script>
function SubmitForm(i, btn){
$("#rowNumber").val(i);
var form = $(btn).closest("form");
form.submit();
}
</script>
use <button> to replace the <input type="submit" ..>, and set the row number in the value of the <button>
#for (int i = 0; i < Model.Datas.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model.Datas[i])
</td>
<td>
<button name="rowNumber" value="#i">Submit</button>
</td>
</tr>
}
And if the target post Controller/Action is different the current one, instead of setting formaction and formmethod in every submit button, set it in the Html.BeginForm() like
#using(Html.BeginForm("ActionName", "ControllerName"))
{
...
}
I have this code in C# using EF Core 1.2 where I have a form containing two submit buttons. The first button 'upload' invokes my method 'UpLoadMyFile' which compares
each line from my textarea to a pattern and returns a string which tells me if it matches one pattern.
Then I add all my text and its states into a
List <Tuple<string, string>>
that I pass to my View via a ViewModel and display each line plus its state in a table.
Now I'm trying to save each line into my database when I click my second button 'save'. But every time I try to save my lines a NullReferenceException occurs
which tells me my List from my table is null.
I would like to know how to pass all lines and states from my Table 'MyTupleList' to my Post Method since I really don't know how to fix my problem.
My View:
#model Models.ViewModels.CreateSaetzeModelView
<form asp-action="ProcessCreateLines" asp-controller="SoftwareService" method="post" enctype="multipart/form-data">
<div class="div-marginBottom">
<table class="table">
<tbody>
<tr>
<td>
<textarea name="ExpressionTextarea" id="ExpressionTextarea" runat="server" TextMode="MultiLine" asp-for="#Model.LoadExpressions"></textarea>
<div class="col-md-10">
<input type="submit" name="upload" value="upload" /> !--Calls 'uploadmyfile' action-->
</div>
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<br />
<div class="div-ExpressionEingabe">
</div>
#if (Model.MyLinesList != null)
{
<div class="div-marginBottom">
<table id="MyTupleList" class="table table_align">
<thead>
<tr>
<th>
State
</th>
<th>
MyLines
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.MyLinesList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Item2)
</td>
<td>
#Html.DisplayFor(modelItem => item.Item1)
</tr>
}
</tbody>
</table>
<input type="submit" name="save" value="save" />
</div>
}
My Code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ProcessCreateLines(string upload, string save, CreateLinesModelView cmv)
{
//Button 'upload': Checking my lines
if (!string.IsNullOrEmpty(upload))
{
string expressions = Request.Form["ExpressionTextarea"].ToString();
List<Tuple<string, string>> result = new FileUploadController().CheckExpressions(expressions);
cmv.MyLinesList = result;
return View("ProcessCreateLines", cmv);
}
//Button 'save': Saving my lines into a Database
if (!string.IsNullOrEmpty(save))
{
// ****************************MyLinesList is null*******************
var list = cmv.MyLinesList;
...saving my list into database...
}
}
I managed to solve my problem thanks to the comment of #StephenMuecke by using instead of a Tupel a selfmade class
public class MyListModel
{
public string myLine { get; set; }
public string myState { get; set; }
}
}
and creating a List out of it in my ViewModel
public List<MyListModel> LineWithState { get; set; }
Also in my View I replaced the foreach loop with a for loop
#for (int i = 0; i < Model.LineWithState.Count; i++)
{
<tr>
<td>
#Html.TextBoxFor(m=>m.LineWithState[i].myState)
</td>
<td>
#Html.TextBoxFor(m=>m.LineWithState[i].myLine)
</tr>
}
Context
I'm developing a form in ASP.NET MVC 4 with Bootstrap 3 which displays an entity framework derived table. The table is read-only except that there is a button in the first column of each row allowing that row to be deleted.
My ViewModel:
public class SearchQueueViewModel
{
public List<SearchURL> SearchUrls { get; set; }
[Url(ErrorMessage = "Please provide a valid url")]
[MaxLength(500, ErrorMessage = "Url must be 500 characters or less")]
[Required]
public string NewUrl { get; set; }
}
The table is coded like this:
#model SearchQueueViewModel
#foreach (UserInterface.Models.SearchURL row in Model.SearchUrls)
{
<tr>
<td class="col-md-1 text-center">
#using (Html.BeginForm("delete", "home", FormMethod.Post, new { #class = "form-inline" }))
{
#Html.AntiForgeryToken()
<input type="hidden" value="#row" name="searchURL" />
<button type="submit" class="btn btn-xs">X</button>
}
</td>
<td class="col-md-2">#Html.DisplayFor(m => row.URL)</td>
<td class="col-md-2">#row.DateAdded.ToString("d-MMM-yy HH:mm")</td>
<td class="col-md-1">#Html.DisplayFor(m => row.Status)</td>
<td class="col-md-1">#Html.DisplayFor(m => row.SearchStarted)</td>
<td class="col-md-1">#Html.DisplayFor(m => row.SearchCompleted)</td>
<td class="col-md-1">#Html.DisplayFor(m => row.NumberOfProductsSearched)</td>
<td class="col-md-1">#Html.DisplayFor(m => row.NumberOfProductsFound)</td>
<td class="col-md-2">#Html.DisplayFor(m => row.FailErrorMessage)</td>
</tr>
}
In the controller I have:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(SearchURL searchURL)
{
context.SearchURLs.Remove(searchURL);
context.SaveChanges();
viewModel.SearchUrls = context.SearchURLs.ToList();
return View(viewModel);
}
Issue
The controller always receives a null object
Question
How do I get the searchURL object from the button-clicked row passed back to the controller correctly?
Note the for loop insted of foreach to enable model binding and the hidden fields to allow the values to be posted back to the controller. Change your view.
Your View:
#for (int i = 0; i < Model.SearchUrls.Count; i++)
{
<tr>
<td class="col-md-1 text-center">
#using (Html.BeginForm("delete", "home", FormMethod.Post, new { #class = "form-inline" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m=>Model.SearchUrls[i].SearchURLId)
<button type="submit" class="btn btn-xs">delete</button>
}
</td>
<td class="col-md-2">#Html.DisplayFor(m => Model.SearchUrls[i].SearchURLId.URL)</td>
<td class="col-md-2">#Model.SearchUrls[i].DateAdded.ToString("d-MMM-yy HH:mm")</td>
<td class="col-md-1">#Html.DisplayFor(m => Model.SearchUrls[i].Status)</td>
<td class="col-md-1">#Html.DisplayFor(m => Model.SearchUrls[i].SearchStarted)</td>
<td class="col-md-1">#Html.DisplayFor(m => Model.SearchUrls[i].SearchCompleted)</td>
<td class="col-md-1">#Html.DisplayFor(m => Model.SearchUrls[i].NumberOfProductsSearched)</td>
<td class="col-md-1">#Html.DisplayFor(m => Model.SearchUrls[i].NumberOfProductsFound)</td>
<td class="col-md-2">#Html.DisplayFor(m => Model.SearchUrls[i].FailErrorMessage)</td>
</tr>
}
And also change your Delete method.
public ActionResult delete(SearchQueueViewModel sm) // note this line
{
//your code
}
But the job is not finished yet. If I am not wrong now you are going to face issue with multiple submit button with identical property.
The best approach for your requirement is using javascript. You can have a look to this
I have a Web App written in MVC5 which passes a List of an Object(which contains many items) to a Page and renders successfully. I have a button on the form that forces a Post back. When I click the button the model appears to re-initialise the List of Objects, rather than return what was is on the Page.
I have read various posts on SO that cover similar issues that have made many suggestion like ensuring every item in the Object is on the form (at least hidden). Tried many of the options, but so far haven't been successful in solving my issue.
I decided to go back to basics on it, and created a very simple View Model with a List. This again renders ok, but when returned it as System.Collections.Generic.List.
View Model
public class TestVm
{
public List<string> CustomerNames { get; set; }
}
Controller
public ActionResult Index()
{
TestVm testmodel = new TestVm();
testmodel.CustomerNames = new List<string>();
testmodel.CustomerNames.Add("HELP");
testmodel.CustomerNames.Add("Its");
testmodel.CustomerNames.Add("Not");
testmodel.CustomerNames.Add("Working");
return View(testmodel);
}
[HttpPost]
public ActionResult Index(TestVm model)
{
// DO SOME WORK HERE WITH RETURNED model
return View(model);
}
View
#model WebApplication1.Models.TestVm
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>View</title>
</head>
<body>
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
}
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
</body>
</html>
I thought creating a simple app like this would help me understand and solve my issue. But I can't work out why the model in the HttpPost contains "System.Collections.Generic.List", rather than the actual list of string that I would expect.
Initial Loadup
Page when loaded up the first time
After Refresh
Page after I clicked Refresh
You need to enclose every form control under Html.BeginForm brackets
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
}
I am using the following code:
Controller:
public ActionResult Update(int studentId = 0, int subjectId = 0)
{
Engine engine = new Engine(studentId, subjectId);
List<Chapter> chapterList = engine.GetChapters();
return View(chapterList);
}
[HttpPost]
public ActionResult Update(List<Chapter> model)
{
return View(model);
}
Update.cshtml:
#model IEnumerable<Chapter>
#{
ViewBag.Title = "Update";
}
<h2>
Update</h2>
#using (Html.BeginForm("Update", "StudyPlan", FormMethod.Post))
{
<fieldset>
<table>
#foreach (var item in Model)
{
<tr>
<td>
#item.name
</td>
<td>
#Html.CheckBoxFor(chapterItem => item.included)
</td>
</tr>
}
</table>
<input type="submit" value="submit" />
</fieldset>
}
I want when a user selects checkboxes, the response should come in httppost method of controller. But I am getting null value Update method. Am I doing something wrong
You need to use for instead of foreach. In that case checkbox will be rendered as
<input type='checkbox' name='Model[0].included'/>
<input type='checkbox' name='Model[1].included'/>
...
and then ModelBinder will successfully create model
Example:
#model List<Chapter>
#{
ViewBag.Title = "Update";
}
<h2>
Update</h2>
#using (Html.BeginForm("Update", "StudyPlan", FormMethod.Post))
{
<fieldset>
<table>
#for (int i =0;i<Model.Count;i++)
{
<tr>
<td>
#Model[i].name
</td>
<td>
#Html.CheckBoxFor(chapterItem => Model[i].included)
</td>
</tr>
}
</table>
<input type="submit" value="submit" />
</fieldset>
}
PS. in that example Model changed to List<> from IEnumerable
It happens because MVC analyze expression in CheckBoxFor method. And it this expression is array accessor, then it generates different control name. And based on Name ModelBinder successfully creates List<>
As Sergey suggested, use a for loop, but try this:
#for (int i =0;i<Model.Count;i++)
{
<tr>
<td>
#Html.HiddenFor(m => m[i].id)
#Html.DisplayFor(m => m[i].name)
</td>
<td>
#Html.CheckBoxFor(m => m[i].included)
</td>
</tr>
}