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>
}
Related
I am new to ASP.net core and have run into an issue with passing a variable form a button to the controller. The examples that I have found seem to be straight forward and simple regarding this but I must be doing something wrong since I cannot get it to work. I am attempting to pass the variable in #Reload.Mail_Id to the method RecallRecord in the controller. If some one could tell me what I'm missing I would really appreciate it.
View
<thead>
<tr>
<th>Mail ID</th>
<th>Process Date</th>
<th>Name</th>
<th>Address</th>
<th>Brochure Order Name</th>
</tr>
</thead>
<tbody>
#foreach (var Reload in Model)
{
<tr>
<td class="text-center">#Reload.Mail_Id</td>
<td class="text-center">#Reload.Created_Date</td>
<td class="text-left">#Reload.Pax_Name_Envelope</td>
<td class="text-right">#Reload.Address_1</td>
<td class="text-right">#Reload.Brochure_Order_Name</td>
<td>
<form asp-action="RecallRecord" method="post">
<input type="number" name="Mail_Id" value="#Reload.Mail_Id" />
<button type="submit" class="btn btn-sm btn-danger">
Recall
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
Contoller
public RedirectToActionResult RecallRecord(int MailID)
{
{
using (var ctx = new BrochureDbContext())
{
var reloadSet = ctx.Reload.FromSql($"usp_OAK_Brochure_Reload {MailID}")
.ToList();
return RedirectToAction("ReloadOrders");
}
}
}
change your method as below
public RedirectToActionResult RecallRecord(int Mail_Id)
{
{
using (var ctx = new BrochureDbContext())
{
var reloadSet = ctx.Reload.FromSql($"usp_OAK_Brochure_Reload {Mail_Id}")
.ToList();
return RedirectToAction("ReloadOrders");
}
}
}
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 been trying to select multiple rows from my table of data(Generated using EF) and then pass all selected rows to the next view to perform some action. On passing the data to the next view i am getting the following error :
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Temporary local of type 'int[]'> was null.
Any help on how to solve this will be appreciated.
Below is my code:
View:
<div class="row">
<div class="col-md-12">
<!-- Advanced Tables -->
<div class="panel panel-default">
<div class="panel-heading">
#using (Html.BeginForm()) {
<form action="#" method="post">
<label>Search by Company Name:</label> #Html.TextBox("SearchString")
<input type="submit" value="Go" placeholder="Search" style="background-color: #0a9dbd; color: white; border-color: #0a9dbd;">
<label>Search by Card Number:</label> #Html.TextBox("searchCard")
<input type="submit" value="Go" placeholder="Search" style="background-color: #0a9dbd; color: white; border-color: #0a9dbd;">
Export to Excel
</form>
}
</div>
<div class="panel-body">
Add Gift Card
Get Card Balance
Load Cards
<br />
<br />
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover" id="dataTables-example">
<thead>
<tr>
<th>Card ID</th>
<th>Company</th>
<th>Card Number</th>
<th>Card Number 2</th>
<th>Date Created</th>
<th>Card Status</th>
<th>Discount Level ID</th>
<th>Loyalty Level ID</th>
<th>Gift Card Enabled</th>
<th>Loyalty Enabled</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td><input type="checkbox" name="ids" value="#item.CardID" /></td>
<td>#item.CardID</td>
<td>#item.Customer.CustomerCompanyName</td>
<td>#item.CardNumber</td>
<td>#item.CardNumber2</td>
<td>#item.CardDate</td>
<td>#item.CardStatus</td>
<td>#item.DiscountLevelID</td>
<td>#item.LoyaltyLevelID</td>
<td>#item.GiftCardEnabled</td>
<td>#item.LoyaltyEnabled</td>
<td>
<i class="fa fa-edit "></i> Edit <br />
</td>
</tr>
}
</tbody>
</table>
Page #(Model.PageCount
< Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount #Html.PagedListPager(Model, page=> Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
</div>
</div>
</div>
<!--End Advanced Tables -->
</div>
</div>
Controller:
public ActionResult PostCards(int[]ids)
{
var myObject = new Card();
foreach(var id in ids)
{
myObject = db.Cards.Single(o => o.CardID == id);
return RedirectToAction("LoadCards", myObject);
}
return View();
}
public ActionResult LoadCards()
{
return View();
}
I need the selected data to be passed to the LoadCards view.
Let us first look at the NullReference you are getting. The problem here is that no correct index is created to bind the checkboxes to an array. Use a for loop instead of foreach. In MVC/Razor, how do I get the values of multiple checkboxes and pass them all to the controller?
To get the desired behaviour:
change the foreach to a for loop so the correct indices for sending the data will be created.
add a checkbox in each row that lets the user select the rows to submit.
your action should recieve a collection of models for each row. This model always transports the CardId and tells us whether it was selected.
public class SelectedCardModel {
public int CardId { get; set; }
public bool IsSelected {get; set;}
}
In the view:
#using (Html.BeginForm("PostCards", "CustomerView", FormMethod.Post) {
// <table> etc ...
<tbody>
#for (var i = 0; i < Model.Count; i++) {
#{ var item = Model.ElementAt(i); }
<tr>
<td>
#Html.Hidden("CardId[" + i + "]")
#Html.CheckBox("IsSelected[" + i + "]")
</td>
// display other properties of item ...
<td>#item.CardID</td>
// ...
</tr>
}
</tbody>
</table>
<button type="submit">Load Cards</button>
}
Action:
[HttpPost]
public ActionResult PostCards(SelectedCardModel[] selectedCards) {
foreach(var card in selectedCards) {
if (card.IsSelected) {
var selectedId = card.CardId;
// ...
}
}
}
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 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.