Is it possible to refer to id/class inside View in ActionLink? - c#

I try to pass value from 'class' in the View to controller and save it to database. Date picked by the user is stored in class 'finalDate' (about 100 rows with such date field and 'Save' button):
View:
<tr>
<td>
#Html.DisplayFor(modelItem => item.custName)
</td>
<td class="choice">
<input class="finalDate" type="date">
</td>
<td>
#Html.ActionLink("Save","Update", new { cust = item.custName, manualDate = "{finalDate.value}" }) //can I pass class (or id) in that way?
</td>
</tr>
Controller:
public ActionResult Update(string cust, DateTime? manualDate)
{
_docs.UpdateData(cust, manualDate); //method UpdateData takes args and saves changes to db
return RedirectToAction("Index");
}
The issue is that no error shows up. Nothing has been changed to db anyway. So I'm wondering if:
I pass class value to ActionLink correctly (and is it possible at all)?
I need to wrap up my View code in <form action="#Url.Action("ActionName", "Home"))" method="post"> to send data to server?
Add [HttpPost] to ActionResult?

Related

Retain value of input type file in a ASP.Net MVC view after page refresh

I am using a input type file in MVC view form whose value is posted to action method for performing some validation. The input file value is binded to a property in model which will be returned after page refresh. But file value is not getting binded automatically after page refresh.Please find below the code.
This is my view code.
#model WebApplication10.Models.Model1
<h2>HtmlToPDF</h2>
#using (Html.BeginForm("HtmlToPDF", "Home", FormMethod.Post, new { enctype =
"multipart/form-data" }))
{
<table>
<tr>
<td>File</td>
<td><input type="file" name="File1" value="#Model.File1"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="ToHTML" /></td>
</tr>
</table>
}
Controller code:
[HttpPost]
public ActionResult HtmlToPDF(Model1 file)
{
//Some validation.
return View(file);
}
and this is my model:
public class Model1
{
public HttpPostedFileBase File1 { get; set; }
}
Any help will be appreciated.
Due to security reason browser will not allow you to set that information. Once Form Submit input of type=file get cleared and it is not set manually by script or other programming stuff.
In your case you can do something like this.
When Form Is Submitted , you can hold Model1 property File1 information in some other object. Like FileName , FileSize and StreamOfData. You pass marker to Form that you have stored that data. So when next time Form submitted you can consider that data if any new data is not passed.
I hope this suggestion will help.

Partial View List returns null when saving the model

I pass a List to a partial view and it works fine, it shows all the data but when I save the Model, the List returns null, what am I missing?
Dont pay attention to the objects, I wrote fake ones for the example.
This the cshtml:
#model ViewModels.StudentVM
#using (Html.BeginForm("SaveStudent", "StudentsView", FormMethod.Post}))
{
#Html.AntiForgeryToken();
<div class="row">
<span>Student name:</span>
#Html.TextBoxFor(s => s.Name)
</div>
<div>
#Html.Partial("StudentsList", Model.Students)
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn">
</div>
}
When loading the view I get all the students to the View Model:
vm.Students = await _studentController.GetAllStudents(); // returned 20 Students.
The partial view:
#model IEnumerable<Entities.Students>
<table class="table-bordered">
#foreach (var item in Model)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.IsSelected)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
</tr>
}
</table>
I would like to get all selected students, so lets say I will select 3 students.
And then click on the save button. Result: the Model.Students is null although it I selected 3 students. How can I get those students?
Your current code of partial view has only the Call to DisplayNameFor helper method which will only render the display name as a label. If you want to submit the data of each item, you need to generate input form fields with matching names with your view model property structure.
Assuming your StudentVM has a Students collection of type IEnumerable<Students>
If your HttpPost action method's parameter is of type StudentVm, you need to make sure that your partial view is generating the input form fields with name like 'Students[0].Name, 'Students[1].Name etc. You can use the Html.TextBox helper method and specify this custom names
#model IEnumerable<Students>
<table class="table-bordered">
#{
var counter = 0;
}
#foreach (var student in Model)
{
<tr>
<th>Name</th>
<td>#Html.TextBox("Students[" + counter + "].Name", student.Name)</td>
</tr>
counter++;
}
</table>
If you are simply displaying the names of existing students, you do not need the text fields, you can simply display those inside the loop. In that case, when form submits,why do you worry about the Students collection being null ? You are trying to save a new Student which will be in .Name property. So if you need the existing students again (but why ? ), you can call the GetAllStudents method.
maybe you used 2 Different model in view ......
you can use 2 model in 1 viewmodel
It is not necessary partial view
ViewModels.StudentVM != IEnumerable<Entities.Students>
you can all data (many model) passed in 1 view model
:
var vm = new TestViewModel();
vm.one = db.one.tolist();
vm.two = db.two.tolist();

set value submit from collection submit mvc

I want to get in controller and Model and specific value from collection which equal line on which I press button
<table id="Products" class="Products">
<tr>
<th>ProductId</th>
<th>Productname</th>
<th>Quantity</th>
<th>UnitPrice</th>
</tr>
<% for(int i=0; i < Model.NorthOrderDetails.Count; i++)
{ %>
<tr>
<td><%: Html.Label(Model.NorthOrderDetails[i].ProductID.ToString()) %></td>
<td><%: Html.Label(Model.NorthOrderDetails[i].ProductName) %> </td>
<td><%: Html.TextBoxFor(m => m.NorthOrderDetails[i].Quantity) %></td>
<td><%: Html.TextBoxFor(m => m.NorthOrderDetails[i].UnitPrice) %></td>
<td><%: #Html.ActionLink("Go to second view", "ViewTwo", "Order", Model, null)%></td>
<input type="submit" title="ads" value =<%: Model.NorthOrderDetails[i].ProductID.ToString()%> name=ssad />
<tr>
<% } %>
</table>
Can I set value in submit from collection, for example
<input type="submit" title="ads" value =<%: Model.NorthOrderDetails[i].ProductID.ToString()%> name=ssad />
And this value will equal 17, for example in controller. This work, but how I can change of text in button from value in collection to any text?
UPDATE
I use code of Stephen Muecke, but I edit table because I use aspx page
<td><button type="button" class="delete" data-id="<%:Model.NorthOrderDetails[i].ProductID %>">Delete</button><td>
<td><input type="hidden" name="<%:Model.NorthOrderDetails[i].ProductName %>" value="<%:i %>" /><td>
And, unfortunately the script doesn't call controller
Rather than doing a full post and regenerating the view each time you want to delete an item, you can use ajax to post the items ID value to a controller method that deletes the item in the database and then remove that item from the DOM. This will greatly improve performance and means you can probably avoid using Session.
Change the view to (sorry, but this is Razor syntax)
#for (int i = 0; i < Model.NorthOrderDetails.Count; i++)
{
<tr>
<td>#Html.LabelFor(Model.NorthOrderDetails[i].ProductID)</td> // ToString not required
<td>#Html.Label(Model.NorthOrderDetails[i].ProductName)</td>
<td>#Html.TextBoxFor(m => m.NorthOrderDetails[i].Quantity)></td>
<td>#Html.TextBoxFor(m => m.NorthOrderDetails[i].UnitPrice)</td>
<td>#Html.ActionLink("Go to second view", "ViewTwo", "Order", Model, null)</td> // This wont work
<td>
<button type="button" class="delete" data-id="#Model.NorthOrderDetails[i].ProductID">Delete</button><td> // change this
<input type="hidden" name="#Model.NorthOrderDetails.Index" value="#i" /> // add this
</tr>
}
</table>
<input type="submit" value="Save" /> // add this
Notes:
Your action link will not work (your cannot pass a collection to a
GET method) I suspect you mean #Html.ActionLink("Go to second view", "ViewTwo", "Order", new { ID = Model.NorthOrderDetails[i].ProductID }, null) so you can pass the productID to the ViewTwo() method
Change the submit button in each row to a normal button, and add one submit button at the end (to save all changes to your textboxes in one post)
Add the special hidden input for an Index property. This is used
by the DefaultModelBinder to match up collections where the
indexers are non-consecutive (which they will be if you delete items
in the middle of the collection)
You don't render any input for the ProductID which means you wont
be able to identify the products on post back. You will need to add
a hidden input for it
Then add the following script
var url = '#Url.Action("Delete", "YourControllerName")';
$('.delete').click(function() {
var id = $(this).data('id'); // Get the product ID
var row = $(this).closest('tr') // Get the table row
$.post(url, { ID: id }, function(data) {
if(data) {
row.remove(); // remove the row from the table
} else {
// oops!
}
});
});
And the controller
public ActionResult View(IEnumerable<YourModel> model)
{
// Save your collection and redirect
}
[HttpPost]
public JsonResult Delete(int ID)
{
// Delete the product in the database based on the ID
return Json(true);
}
Note: If deleting an item could throw and exception of fail in some way, then you should return Json(null); so it can be checked in the ajax method.

Do I need to use the same VM in the POST, as the GET, to reload index?

Using MVC, in the GET function in the controller I am creating the VM and passing it to the view.
[Themed]
public ActionResult OrderManufacturedProducts(int id)
{
QBProductRecord QBproduct = _qbproductService.GetById(id);
OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
return View(model);
}
Then The View:
#model System.ViewModels.OrderManufacturedProductsVM
#{
Script.Require("ShapesBase");
Layout.Title = T("Manufactured Orders").ToString();
}
#using (Html.BeginFormAntiForgeryPost())
{
<fieldset>
<table class="items" summary="#T("This is a table of the manufactured products to be ordered")">
<colgroup>
<col id="Col1" />
<col id="Col2" />
<col id="Col3" />
</colgroup>
<thead>
<tr>
<th scope="col"> ↓</th>
<th scope="col">#T("Name")</th>
<th scope="col">#T("Description")</th>
</tr>
</thead>
<tbody>
<tr>
<td>#Model.QBProduct.ProductName</td>
<td>#Model.QBProduct.StockLevel</td>
<td><input id="Order" name="NoToOrder" type="text" value="0" onchange=""/></td>
</tr>
</tbody>
</table>
<div class="align right"><button type="submit" name="command" value="Save">Order</button></div>
</fieldset>
}
So the user enters a order no. in the input field and clicks submit which returns to the post.
[HttpPost, ActionName("OrderManufacturedProducts")]
public ActionResult OrderManufacturedProductsPOST(int id, int NoToOrder)
{
// OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
// return View(model);
return Index();
}
I want to return to the Index page, but it is telling me
Server Error in '/OrchardLocal' Application.
The model item passed into the dictionary is of type 'System.ViewModels.ManufacturedProductsVM', but this dictionary requires a model item of type 'System.ViewModels.OrderManufacturedProductsVM'.
So do I need to use the same VM in my post? all I want to do is reload the index page after the update is made.
NOTE: the update on the record is working fine, I just cant get the correct page to display after.
In your OrderManufacturedProductsPOST action, you should redirect to the action you want to return. Like so:
[HttpPost, ActionName("OrderManufacturedProducts")]
public ActionResult OrderManufacturedProductsPOST(int id, int NoToOrder)
{
// OrderManufacturedProductsVM model = new OrderManufacturedProductsVM(QBproduct);
// return View(model);
return RedirectToAction("Index");
}

How can I create a list of object based on checkboxfor created for the model

I have this view based on a list of a model where I create strongly-typed checkboxes for each items of the model based on a boolean.
Here's my view:
#using MyApp.Models
#model IList<MyApp.Models.ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm())
{
(Many search filters which are non-relevant)
<p>
Send Items: #Html.ActionLink("Click Here", "SendItems")
</p>
}
<table>
<tr>
<th>
Obj Name
</th>
<th>
Number In Stock
</th>
(...)
<th>
Select Item
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(modelItem => item.m_IsSelected)
</td>
</tr>
}
</table>
The whole process works fine and I can actually generate a view with checkboxes for each item of my list of model.
Now my question is that I want to create a list which would regroup only the items in the list which are checked and send them to the controller. How could I do that? Can anyone help me or suggest me a way to work?
Thank you!
* EDIT *
Here is the HttpPost Method used to get the List of items as mentioned below:
//
// GET: /Inventory/SendItems
[HttpPost]
public ActionResult SendItems(IList<ObjInfo> listToSend)
{
m_ListObjToSend = new List<ObjInfo>();
foreach (var item in listToSend.Where(item => item.m_IsSelected))
{
m_ListObjToSend .Add(item);
}
return View(m_ListObjToSend );
}
However I have encountered many problems:
This method does NOT work if I put the [HttpPost] attribute (it will show as "Not Found");
The list I am supposed to receive is null;
Each hiddenfield linked with the checkbox has default value as false even if the checked value shows true;
I am using an actionlink because I do not want to use a button, there is already one that is doing another job.
I am open for any comments / help available, thank you!
If you use the CheckBoxFor helper to generate checkboxes you will notice that it generates an additional hidden field along with each checkbox. This means that all values will be sent to the controller and you will have to filter in your controller those that are checked.
Also I would recommend you using indexes to ensure proper model binding. You just need to use an IList<ObjInfo> or ObjInfo[] which is trivially easy achievable by calling .ToList() or .ToArray() extension methods on your view model before passing it to the view:
#using MyApp.Models
#model IList<ObjInfo>
...
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
...
And now your controller action could directly take the list of items:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
...
}
and if you wanted to find the selected values, you could simply get them through LINQ:
[HttpPost]
public ActionResult SomeAction(IEnumerable<ObjInfo> model)
{
var selectedItems = model.Where(x => x.m_IsSelected);
...
}
Remark: m_Name and m_IsSelected is a disastrously bad naming convention for a properties in C#.
UPDATE:
Another issue you have with your code is that your Html.BeginForm doesn't contain any input field. It has only a single ActionLink which obviously only does a GET request. If you want to submit the values you should wrap your entire table with the form and use a submit button and not some action links:
#using MyApp.Models
#model IList<ObjInfo>
#{
ViewBag.Title = "Obj Inventory";
}
<h2>Search Inventory</h2>
<p>
#using (Html.BeginForm("SendItems", null, FormMethod.Post))
{
(Many search filters which are non-relevant)
<table>
<tr>
<th>Obj Name</th>
<th>Number In Stock</th>
(...)
<th>Select Item</th>
</tr>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
<!--
This will not be sent to your controller because it's only a label.
You will need a corresponding hidden field if you want to get that value back
-->
#Html.DisplayFor(x => x[i].OtherObj.m_Name)
</td>
(...)
<td>
#Html.CheckBoxFor(x => x[i].m_IsSelected)
</td>
</tr>
}
</table>
<p>
Send Items: <button type="submit">Click Here</button>
</p>
}
</p>
So really, 2 things you should learn:
The naming convention that the default model binder expects when binding to a list
How to use a javascript debugging tool (such as FireBug and/or Chrome Developper Toolbar) which will allow you to inspect all the values that are sent to your server and immediately recognized whether you respected the convention you learned in 1.

Categories

Resources