How binding this (dynamic) table to IList<IList<string>> or another type
html:
#using (Html.BeginForm())
{
<table>
<thead>
<tr><th>column1</th><th>column2</th></tr>
</thead>
<tbody>
#for (int i = 0; i < 10; i++)
{
<tr><td><input type="text" value="#i" name="column1_#(i)"/></td><td><input type="text" value="#(Guid.NewGuid())" name="column2_#(i)"/></td></tr>
}
</tbody>
</table>
<input type="submit" value ="send"/>
}
I need get columns and rows
Update:
maybe I can take String[][]
My first thought was to use a Dictionary<string, string>, but that's not indexable, so you'd have to write a custom model binder. Not that hard, but still. Then I thought about using a List<KeyValuePair<string, string>>, but KeyValuePairs have private setters, so, again, you'd need a custom binder.
So I think the best way is:
Create a custom type
public class MyItems
{
public string Key { get; set; }
public string Value { get; set; }
}
Now, add a list of this type as a property to your view model
public List<MyItems> MyItems { get; set; }
And, after populating the list and strongly typing your view, of course, you can render your table using the built in html helpers to ensure model binding won't have any hiccups
#for (int i = 0; i < Model.MyItems.Count( ); i++ )
{
<tr>
<td>#Html.TextBoxFor( m => m.MyItems[i].Key )</td>
<td>#Html.TextBoxFor( m => m.MyItems[i].Value)</td>
</tr>
}
Then catch the model in the controller and access you data
[HttpPost]
public ActionResult Index(Model viewModel)
{
foreach (var item in viewModel.MyItems)
{
string columnOneValue = viewModel.MyItems[0].Key;
string columnTwoValue = viewModel.MyItems[0].Value;
}
Related
I have looked at most of the available help in SO and google related to this but I'm not sure what I'm doing wrong.
I have Model with some properties, and one of the properties is a list of another complex object. I'm not able to bind this list!
Pleas help!
Here is my Model classes related:
public class PrivacyModel
{
public int RatingId { get; set; }
public List<RatingPoint> RatingPoints { get; set; }
}
public class RatingPoint
{
public int RatingItemId { get; set; }
public string RatingValue { get; set; }
}
Here is my code:
[HttpPost]
public ActionResult Index(PrivacyModel model)
{
.... my business logic....
}
My view looks like this:
#using (Html.BeginForm("Index", "Privacy"))
{
<input type="hidden" name="RatingId" value="#Model.RatingId" />
for (var i = 0; i < Model.RatingPoints.Count; i++)
{
var ratingPoint = Model.RatingPoints[i];
<input type="hidden" name="PrivacyModel.RatingPoints[#i].RatingItemId" value="#ratingPoint.RatingItemId" />
<input type="hidden" name="PrivacyModel.RatingPoints[#i].RatingValue" #("id=RatingPoints" + ratingPoint.RatingItemId) value="#ratingPoint.RatingValue" />
}
<input class="btn" type="submit" value="Submit" />
}
Please don't mind the value and id fields, they are being updated by jQuery somewhere in my page correctly.
This got me a null list of RatingPoints in my action
I have tried also without the prefix PrivacyModel in PrivacyModel.RatingPoints[#i]., but this got me an empty list of RatingPoints in my action
I have also tried using an Index like in the suggested solution here for non-sequential items
You are making it complex yourself, you can just use HiddenFor() helper for this:
for (var i = 0; i < Model.RatingPoints.Count; i++)
{
#Html.HiddenFor(x=> Model.RatingPoints[i].RatingItemId)
#Html.HiddenFor(x=> Model.RatingPoints[i].RatingValue,new { id= "RatingPoints"+Model.RatingPoints[i].RatingItemId})
}
and this will render the same html, and values will be correctly binded in Model at post.
I'm trying to get data from 2 table using Linq , that one table had FK to the second table but not necessary has data (table review could have for each review comments (many)) what i'm trying to get is: in a single view get all the reviews and if there are any comments display them related to the review Id
trying to use join get me error in my view (model pass is wrong i tried each table model) this is my code :
public ActionResult ttt()
{
var model = from rev in db.reviews
join com in db.Comments
on rev.ReviewId equals com.ReviewId into JoineRevCom
from com in JoineRevCom.DefaultIfEmpty()
select new
{
rev.ReviewBody,
rev.ReviewHeadLine,
Comments = com != null ? com.CommentBody : null
};
return View(model);
}
#model IEnumerable< SiteMvcPro.Models.Review>
As always I would start by writing a view model for this view containing the information that I would like to display and never send anonymous objects to your view like you did in your code.
Let's suppose that you want to display a list of reviews and for each review the list of corresponding comments. So your view model might look something along those lines:
public class ReviewViewModel
{
public int ReviewId { get; set; }
public string ReviewBody { get; set; }
public string ReviewHeadLine { get; set; }
public IList<CommentViewModel> Comments { get; set; }
}
public class CommentViewModel
{
public string CommentBody { get; set; }
}
with this defined you could perform your LINQ query to extract the necessary data and project to this view model:
IEnumerable<ReviewViewModel> viewModel =
from review in db.reviews
join comment in db.Comments
on review.ReviewId equals comment.ReviewId into joinedReviewComment
select new ReviewViewModel // <-- Always project to a view model and never to an anonymous object
{
review.ReviewBody,
review.ReviewHeadLine,
Comments = joinedReviewComment.Select(c => new CommentViewModel
{
CommentBody = c.CommentBody,
}).ToList(),
};
return View(viewModel.ToList()); // <-- Always pass a view model to your view
And now all that's left is to display this information in your strongly typed view:
#model IList<ReviewViewModel>
<table>
<thead>
<tr>
<th>Review id</th>
<th>Review body</th>
<th>Review headline</th>
<th>Review comments</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(x => x[i].ReviewId)</td>
<td>#Html.DisplayFor(x => x[i].ReviewBody)</td>
<td>#Html.DisplayFor(x => x[i].ReviewHeadLine)</td>
<td>
#for (var j = 0; j < Model[i].Comments.Count; j++)
{
<div>
#Html.DisplayFor(x => x[i].Comments[j].CommentBody)
</div>
}
</td>
</tr>
}
</tbody>
</table>
This being said projecting is one thing but filtering your data is another. Suppose that you have millions of reviews and each review has millions of comments. Making the aforementioned query will simply bring your server down pretty quickly. So think about that when designing your application and views. Don't hesitate to use the Where, Skip and Take operators to filter your result-sets down into a meaningful collection of data that is reasonable enough to be displayed on a single view.
My VIEW (success.cshtml) is as below
#model IList<AzSample.Models.Employeelist>
#using (Html.BeginForm("Save","Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<h2>Employees Details</h2>
<div>
<table>
<tr>
<th>Id</th>
</tr>
#foreach (var emp in Model)
{
<tr>
<td>#emp.ID</td>
</tr>
}
</table>
<button type="submit" value="save" onclick="#Url.Action("Save", "Home")">Save</button>
</div>
}
My Home Controller is as below
public class HomeController : Controller
{
public ActionResult Import(HttpPostedFileBase excelfile)
{
//Code to get data from Excel sheet
List<Employeelist> obj = new List<Employeelist>();
for( int row =2; row <=range.Rows.Count; row++)
{
Employeelist emp = new Employeelist();
emp.ID =((Excel.Range)range.Cells[row,1]).Text;
obj.Add(emp);
}
return View("success", obj);
}
[HttpPost]
public ActionResult Save(List<Employeelist> empLIst)
{
// Code fro storing the data got from VIEW.
}
}
My Model is as below
public class Employeelist
{
public string ID { get; set; }
}
I am calling import action method from some other page, and reading the data from Excel sheet and showing it on success.cshtml. Till here it works fine.
When ever i click on Save button, the debug point is coming back to Save action method, but the data (i.e. List<Employeelist>, basically all Employeelist.ID's ) is null ?
What is missing ?
The data for the empList is never submitted with the form. You should render <input> fields to submit your data.
#for(int i = 0; i < Model.Count(); ++i) {
#Html.HiddenFor(m => m[i].Id)
// send other properties
}
It is important that you bind to indexed properties so the MVC modelbinder can bind this as a collection. #Html.HiddenFor(m => m.Id) will not work.
See also Model Binding to a List MVC 4
--Edit--
As Stephen has pointed out, the modelbinder will try to bind this using the index, so your c# model must implement IList<T>.
Another pitfall are non-sequential indices.
You should use this > ASP.NET MVC TempData ?
ASP.NET MVC TempData dictionary is used to share data between controller actions. The value of TempData persists until it is read or until the current user’s session times out. Persisting data in TempData is useful in scenarios such as redirection, when values are needed beyond a single request.
The code would be something like this:
Import Method:
public ActionResult Import(HttpPostedFileBase excelfile)
{
//Code to get data from Excel sheet
for( int row =2; row <=range.Rows.Count; row++)
{
Employeelist emp = new Employeelist();
emp.ID =((Excel.Range)range.Cells[row,1]).Text;
obj.Add(emp);
}
TempData["doc"] = obj;
return View("success", obj);
}
View:
#model IEnumerable<AzSample.Models.Employeelist>
#using (Html.BeginForm("Save","Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<h2>Employees Details</h2>
<div>
<table>
<tr>
<th>Id</th>
</tr>
#{var list = (List<AzSample.Models.Employeelist>)TempData["doc"];}
#{TempData["doc"] = list;}
#foreach (var emp in list)
{
<tr>
<td>#emp.ID</td>
</tr>
}
</table>
<button type="submit" value="save" onclick="#Url.Action("Save", "Home")">Save</button>
</div>
}
Save Method:
[HttpPost]
public ActionResult Save()
{
if (TempData["doc"] != null)
{
// Your code will be here
}
}
I have a problem and I can't find solution.
I'm using Razor and it is my VieModel class.
public class GroupToExport
{
public GroupToExport()
{
ToExport = false;
}
[DisplayName("Export")]
public bool ToExport { get; set; }
public Group Group { get; set; }
}
public class GroupsToExport
{
public GroupsToExport()
{
//refill list
}
public List<GroupToExport> ExportingGroups { get; set; }
}
View:
#using (Html.BeginForm("Export", "ElmahGroup", FormMethod.Post, new { id = "toExportForm" }))
{
//some divs
<input type="submit" id="js-export-submit" value="Export" />
#foreach (var item in Model.ExportingGroups)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => item.ToExport)
</td>
</tr>
}
//some divs
}
Controller:
public ActionResult Export(GroupsToExport model)
{
var groupsToExport = model.ExportingGroups.Where(x => x.ToExport).Select(x => x);
throw new System.NotImplementedException();
}
After submit "ToExport", in Controller, every group always has value 'false'. Even if all groups are checked.
Can somebody help me? What I'm doing wrong?
You cannot use a foreach loop to generate controls for a collection. The html you're generating for each checkbox (and for the associated hidden input) is <input type="checkbox" name="item.ToExport" .../>. Your model does not contain a property which is named item.
Use a for loop
#for(int i = 0; i < Model.ExportingGroups.Count; i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ExportingGroups[i].ToExport)
</td>
</tr>
}
Now your HTML will be
<input name="ExportingGroups[0].ToExport" .../>
<input name="ExportingGroups[1].ToExport" .../>
etc. which will correctly bind to your model
Edit
Alternatively you can use a custom EditorTemplate for typeof GroupToExport. Create a partial view /Views/Shared/EditorTemplates/GroupToExport.cshtml
#model yourAssembly.GroupToExport
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(m => m.ToExport)
</td>
</tr>
And then in the main view
#Html.EditorFor(m => m.ExportingGroups)
The EditorFor() method will generate the correct html for each item in your collection based on the template.
You are using Incorrect syntax to Map the values back when they are posted, since the checked value of a checkbox is initialised to false by default, that is the reason why it is always false,use sysntax
#for(int i = 0; i < Model.ExportingGroups.Count(); i++)
{
<tr>
<td class="js-export-checkbox">
#Html.CheckBoxFor(modelItem => Model.ExportingGroups[i].ToExport)
</td>
</tr>
}
//some divs
This should map back all values you are looking for.
I found this works much better: Leave the foreach loop as is (do not user a counter)
#foreach (var item in Model.GroupToExport)
{
Then use this Razor format to display a checkbox
#Html.CheckBox("Active", #item.ToExport)
Simple to use and does not make you change the typical foreach loop.
I have this model which contains the two following properties:
public int m_ID
public int m_NbrInStock
public int m_QtyToShow
Here is I actually render my view:
<script src="/Scripts/jquery-1.7.1.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#selectAll").click(function ()
{
var chkValue = $(this).is(":checked");
$(".divChckBox").prop("checked", chkValue);
});
});
</script>
<p>
#using (Html.BeginForm("SendItems", "Inventory"))
{
<p>
Select / UnSelet All Items #Html.CheckBox("selectAll", true)
</p>
<table>
<tr>
<th>Obj Name</th>
<th>Number In Stock</th>
(...)
<th>Quantity</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x[i].m_Card.m_CardName)</td>
<td>#Html.DisplayFor(x => x[i].m_NbInStock)</td>
(...)
<td>
<input type="checkbox" name="itemSending" class="divChckBox" checked="true" value="#Model[i].m_ID"/>
</td>
<td>#Html.TextBoxFor(x => x[i].m_QtyToShow</td>
</tr>
}
</table>
<div class="float-right">
<input type="submit" value="Send"/>
</div>
}
</p>
I have many problems here:
First of all, I need to keep the QtyToShow as it will later be used for data managing, but the data does not survive the model because of the HTTPPost;
I also need to validate that QtyToShow is never higher than m_NbrInStock or show an error message if that's the case.
This is not a simple task and since I don't have much experience in MVC development I don't know how I could do this. Can you help me out? Thank you.
Two ways to do this:
Validate the data manually in the controller
Write a custom validator
The first one is the easiest, the second is a better practice.
To get you off the ground and get your app working, you can implement the first way, and then come back and refactor when you're more comfortable with mvc.
So add a property to your viewModel
public string QuantityValidationMsg {get; set}
Then in your view display the message
<td>#Html.TextBoxFor(x => x[i].m_QtyToShow
<span>#Html.DisplayFor(x => x[i].m_QuantityValidationMsg)</span></td>
Then in your post action (which needs to accept a List<yourViewModel>, not a List<int>, btw)
viewModel.QuantityValidationMsg = null;
if (viewModel.QtyToShow > viewModel.NbrInStock)
{
viewModel.QuantityValidationMsg = "Error Message!";
}
For the second approach, you would create a new class that implements ValidationAttribute
and define something along the lines of:
public class ValidateQuantity : ValidateAttribute
{
private const string MESSAGE = "Error Message";
public ValidateQuantity (int qtyInput, int qtyConfirm)
: base( MESSAGE )
{
Input = qtyInput;
Confirm = qtyConfirm;
}
public int Input {get; private set;}
public int Confirm {get; private set;}
public override bool IsValid (object value)
{
var properties = TypeDescriptor.GetProperties(value);
var input = properties.Find(Input, false).GetValue(value);
var confirm = properties.Find(Confirm, false).GetValue(value);
if( input > confirm)
{
return false;
}
return true;
}
}
Then decorate the model property with the new attribute
public int m_ID
public int m_NbrInStock
[ValidateQuantity]
public int m_QtyToShow
And add the validation message in the view
#Html.ValidationMessageFor(m => m.m_QtyToShow)
This kind of thing is terribly easy to express in Mkay, a custom attribute I wrote for ad hoc rules like this. It's available as a nuget package. After installing, all you need to do is annotate your model property like this:
[Mkay(">= m_NbrInStock")]
public int m_QtyToShow
If you're interested, you can read more about how Mkay works at my blog: https://einarwh.wordpress.com/2013/02/15/mkay-one-validation-attribute-to-rule-them-al/