Model binding nested collections in editor templates - c#

I have an editor template that I use for a view model, EditFeeContentViewModel. This view model holds a collection that's added/removed through JavaScript. I'm adding the following HTML via microtemplating for whenever a new item is added so the model binding will work:
<script id="feeContentTemplate" type="text/html">
<tr id='RowContentId<%= ContentId%>'>
<td>
<input type="hidden" name="ContentFeeViewModels.Index" value="<%= ContentId%>"/>
<input type="hidden" name="ContentFeeViewModels[<%= ContentId%>].ContentId" value="<%= ContentId%>" />
<%= ContentId%>
</td>
<td><input type="hidden" name="ContentFeeViewModels[<%= ContentId%>].ContentName" value="<%= ContentName%>" />
<%= ContentName%>
</td>
<td><input type="text" name="ContentFeeViewModels[<%= ContentId%>].Allocation" value="<%= Allocation%>" /></td>
<td><input type="text" name="ContentFeeViewModels[<%= ContentId%>].Comments" value="<%= Comments%>"/></td>
<td>Remove</td>
</tr>
</script>
Now, this works fine for when my view model isn't nested. It submits to the controller, has all of the data intact, etc.
However, when it's nested, obviously the name is no longer correct and the model binding fails. Instead of ContentFeeViewModels.Index, I might need FeeContents.ContentFeeViewModels.Index.
Since this is being done through JavaScript, I don't know how I could use the HTML helpers to generate the names correctly. How can I find out if/where the view model is nested and generate a string to represent its location so the modelbinding happens correctly?
I thought about manually setting a string in EditFeeContentViewModel that a parent view model could set to represent the property name, but that seems cumbersome and error prone. Is there a better approach to this?

I figured out that you could use ViewData.TemplateInfo.HtmlFieldPrefix to get the path value.
So my code now looks like:
var contentFeeModelPrefix = "";
if ("#ViewData.TemplateInfo.HtmlFieldPrefix" != "") {
contentFeeModelPrefix = "#ViewData.TemplateInfo.HtmlFieldPrefix" + ".";
}
I can then use that variable in my JavaScript as the prefix to the various fields.

Related

Get table cell value and submit to a delete function

I have a table which displays data from my database. I want to provide a button which will delete data from a table based on the row selected. Specifically based on the value of a specific cell of that table.
I've tried wrapping a button inside a form element as an extra element inside of each row, but this doesn't seem to work for 2 reasons. 1, it triggers the OnGet() function in my controller and 2 it doesn't seem to bind the cell data value to my 'SourceTable' property.
The following is my front-end table:
<table class="table">
<thead>
<tr>
<td>OrderIdentifier</td>
<td>Step Number</td>
<td>CreateDate</td>
<td>Step Status</td>
<td>Source Table</td>
</tr>
</thead>
#foreach (var order in Model.order.Steps)
{
<tr>
<td>#order.OrderIdenfitier</td>
<td>#order.Name</td>
<td>#order.CreateDate</td>
<td>#order.StepStatus</td>
<td>#order.SourceTable</td>
<td>
<a class="btn btn-dark"
asp-page="./OrderDetail"
asp-route-orderId="#order.OrderIdenfitier">
<i class="glyphicon glyphicon-zoom-in"></i>
Details
</a>
</td>
<td>
<form method="delete">
<input type="hidden" class="form-control" asp-for="SourceTable" />
<button type="submit" class="btn btn-dark">
<i class="glyphicon glyphicon-zoom-in"></i>
Delete
</button>
</form>
</td>
</tr>
}
And on the backend, I'd like to be passing in the SourceTable cell value of whatever row the button is clicked on into this function:
public IActionResult OnDelete(string SourceTable)
{
//Take SourceTable parameter, pass to data access layer, and delete from database.
return Page();
}
I would like to have the value of the SourceTable cell passed to my OnDelete function, but this doesn't occur. Additionally, my 'OnGet()' function is triggered when I click my delete button, not the 'OnDelete()' function. The I form I embedded the button in has action="delete", so I would think that it would go to the OnDelete() function, but that doesn't seem to be the case.
HTTP DELETE method is currently not supported by <form> element. That's why you hit OnGet() in your Razor page. You are still able to use OnPost() for that purpose with binding form values (e.g. hidden inputs) to action method parameters.
The second issue is with your asp-for input tag helper. By default it evaluates its expression against PageModel. You can use # to override that behaviour, especially when you're in a foreach loop. Your form then could look like this:
<form method="post">
<input type="hidden" asp-for="#order.SourceTable"/>
<input type="submit" vaue="Delete"/>
</form>
and action method:
public IActionResult OnPost(string SourceTable)
{
// delete record here
return RedirectToPage();
}
Maybe you don't like the idea to delete records in OnPost handler, which you would like to reserve for some other action.Then you can use named handler method like OnPostDelete(string SourceTable). All you have to do is to add attribute asp-page-handler="delete" to the <form> element:
<form asp-page-handler="delete" method="post">
<input type="hidden" asp-for="#order.SourceTable"/>
<input type="submit" value="Delete"/>
</form>
And maybe you don't like the idea of having a separate form for each row in the table. Then wrap the form around the table and use asp-route- prefix on each delete button to specify parameter binding:
<form method="post">
<table class="table">
<thead>
<!-- ... -->
</thead>
<tbody>
#foreach (var order in Model.order.Steps)
{
<tr>
<!-- ... -->
<td>
<input type="submit" value="delete" asp-page-handler="delete" asp-route-sourcetable="#order.SourceTable" />
</td>
</tr>
}
</tbody>
</table>
</form>
You can find more valuable information in artice Introduction to Razor Pages in ASP.NET Core.

Can I call .cs file from form action?

There is a code in my site as follows(html page):
<form action="form.php" method="post">
<table>
<tr>
<td>
<input type="text" class="email1" name="name" maxlength="20" class="bg_in" />
</td>
</tr>
</table>
<input type="submit" name="posted" value="" class="bg_in2" />
</form>
I want to change it that instead of action="form.php" it will be action="form.cs". Is that possible? if so, what should be the method name that catches the action and how do I catch the inputs?
The form is always submitted to the page itself. If you specify an action attribute, it is ignored. If you omit the method attribute, it will be set to method="post" by default. Also, if you do not specify the name and id attributes, they are automatically assigned by ASP.NET
If you select view source in an .aspx page containing a form with no name, method, action, or id attribute specified, you will see that ASP.NET has added these attributes to the form. It looks something like this:

Extracting Data out of a input box inside a table cell using C#

I have some table that looks like this:
<table width="650" id="myTable" runat="server">
<tbody>
<tr>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /> </td>
</tr>
<tr>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /> </td>
</tr>
<tr>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /></td>
<td><input type="text" /> </td>
</tr>
</tbody>
</table>
Now, I want to rip data out of that and store it in an XML file. The problem is that the number of rows is unknown. A user can create anywhere between 3 and n rows. To solve this, I wrote a little loop that pulls data out of the table by row.
// Assume the relevant items are declared and assigned a proper value.
while (i < myTable.Rows.Count)
{
tRow = myTable.Rows[i];
tNameNode.InnerText = tRow.Cells[0].InnerText;
tUnitsNode.InnerText = tRow.Cells[1].InnerText;
tValueNode.InnerText = tRow.Cells[2].InnerText;
tNewEntryNode.AppendChild(tNameNode);
tNewEntryNode.AppendChild(tUnitsNode);
tNewEntryNode.AppendChild(tValueNode);
tIntermediateNode.AppendChild(tNewEntryNode);
i++;
}
Now the problem with this that I realized only after I ran it is that it's not going to fetch what is inside the input box, just the actual input box code, which is not really what I want.
How can I get the content inside the Input boxes?
You could develop some script to grab all of the text values of the generated textboxes by assigning them all the same ID and then have the script grab the values by ID. Then store it to a variable that you can access in your C# code
The HTML for your textboxes and including a hidden TextBox that could be filled with the value of the rest of your textboxes using a script:
<input type="text" class="userTxtBox" />
<input type="hidden" Id="hiddenTxtBox" />
Personal preference for JQuery at this point (but I'm pretty sure you could accomplish something similar with any script):
$('body').live('input','.userTxtBox', function () {
$('#hiddenTxtBox').val($('.userTxtBox').val());
});
Then, in your C# code you can grab the text from the hiddenTxtBox.
This is a very rough sketch of another way to do what you're doing, but probably is more maintainable in the long run than adding straight html. It looks like the rows you are adding all follow some common pattern and even represent some common functionality. .NET webforms has something expressly made for this purpose: usercontrols. Make a user control like
<% # Control Language="C#" ClassName="InputRow" %>
<table>
<tr>
<td><asp:textbox ... /></td>
<td><asp:textbox ... /></td>
<td><asp:textbox ... /></td>
<td><asp:textbox ... /></td>
</tr>
</table>
Register it in your main page. Then, instead of adding input boxes, use
myTable.Controls.Add(new InputRow(...
My webforms is admittedly a little rusty, you may not actually be able to add directly to the Controls property of a table class. If that fails, you can always add it to the Controls property of an Asp:Panel object instead.
The reason this will be more maintainable is that you can then encapsulate the fetching logic of the input boxes in your usercontrol, and also use it on other pages. Keeping track of a set of these controls in a list will let you be able to object-oriented-ly iterate over all of them and pull out their data in a structured manner.
The solution I employed looked like this:
while (i < myTable.Rows.Count)
{
tRow = tblCropsAndFeed.Rows[i];
string tInputBox0 = tRow.Cells[0].Controls[0].ClientID;
tContent = (HtmlInputText)tRow.FindControl(tInputBox0);
tNameNode.InnerText = tContent.Value;
string tInputBox1 = tRow.Cells[1].Controls[0].ClientID;
tContent = (HtmlInputText)tRow.FindControl(tInputBox1);
tUnitsNode.InnerText = tContent.Value;
string tInputBox2 = tRow.Cells[2].Controls[0].ClientID;
tContent = (HtmlInputText)tRow.FindControl(tInputBox2);
tValueNode.InnerText = tContent.Value;
tNewEntryNode.AppendChild(tNameNode);
tNewEntryNode.AppendChild(tUnitsNode);
tNewEntryNode.AppendChild(tValueNode);
tCropsAndFeedNode.AppendChild(tNewEntryNode);
i++;
}
The huge problem I encountered was the C# was treating the input boxes as a DirectControl object, which made it pretty impossible to get anything out of it. After adding
runat="server"
To the input boxes, all was fine however.

Can I pass a different value as a input from using button but display something else in MVC3

I have a list of button which has to display the number of comments as the value of the button. But when I click the button I want to send/pass the comment Id to the method that is submitted. Is there a way to achieve this kind of thing?
Edited:
What I did was, In the table That I have list of buttons
#foreach(var s in Model.List)
{
<tr>
<td style="text-align:left;">#s.ID</td>
<td style="text-align:center;">#s.CD</td>
<td style="text-align:center;">#s.DE</td>
<td style="text-align:center;">
<input class="xxxxx" style="text-align:center;" type="submit" name="count" value ="#s.k" />
<input type="hidden" name="SupportID" value="#s.ID" />
</td>
</tr>
}
This is passing the S.ID to the controller method, But it is not intuitive and I feel that is not the best method to do it.
Do you need to pass anything else apart from the ID ?
If not then you can just use a simple anchor and set the URL to be something like
href="/Page/Method/#s.ID"
and create the method that accepts the ID.
Alternatively you can use jQuery to do an AJAX call to the method, you will then have access to any attributes on the button element to then use in the AJAX Call.

Passing HTML table information from MVC view to controller?

Is there a "best" (or preferred) way to pass information from an HTML table in an ASP.Net MVC view to a controller? I am working with MVC2, and if I stick with using the Context objects (mainly Request.Form[::variables::], I'm at a loss as to how to retrieve information presented in the view using the table/table cell structure. The table I am working with has a check box corresponding to each row of data, and I want to make the data from "checked" rows available to the controller. The following quick fixes come to mind:
1) Since the table in question has a check box, and HTML elements of type "input" have the "id" attribute, the data values could be stored in some kind of concatenated string in the "id" attribute for the check box.
2) Similar to the above misuse of the check box "id" attribute, the data values could be stored in the "text" attribute for a text (textbox) input element.
...but that all seems really klugey, even if it does work. I am relatively new to the field of web programming/development (although not new to programming), so if anyone can suggest alternate methods and/or technologies (I'm open to trying stuff via JavaScript, etc) I would appreciate it. I would appreciate even just links to relevant tutorials (or, related StackOverflow posts I may have missed). :-p
If you have a form like this:
<form action="." method="POST">
<table>
<tbody>
<tr>
<td>
<input type="checkbox" name="checkedValues" value="1" />
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="checkedValues" value="2" />
</td>
</tr>
<tr>
<td>
<input type="checkbox" name="checkedValues" value="3" />
</td>
</tr>
</tbody>
</table>
</form>
That will map to this parameter in your controller action when a post occurs, where the values of the checked boxes are stored in the checkedValues array:
public ActionResult MyAction(int[] checkedValues)
{
}
The values of the check boxes can be strings as well, if that's preferred.

Categories