I have this one in View:
<%
foreach (var item in (List<MyType>)ViewData["MyTypeArray"])
{
%><tr>
<td>
<%=Html.Encode(item.Name)%>
</td>
<td>
<%=Html.CheckBox("MyTypeFlags" + item.BitNumber),
/* Model goes here*/,
new {#value = (1 << item.BitNumber)})%> // html attr
</td>
</tr>
<%
}
%>
and I want do smth like this in Controller:
foreach (var item in MyDynamicallyCreatedArray)
{
//if (["MyTypeFlags" + item.BitNumber] != 0) // This shoud be changed
}
Question is how should I declare MyDynamicallyCreatedArray and go through the cycle?
You should look at using a "view model". You basically create "Models" just for your view that contain the data items you need in your view.
I use these quite often and they are really a great way of getting data in and out of your view.
For an example you can view here: http://stephenwalther.com/blog/archive/2009/04/13/asp.net-mvc-tip-50-ndash-create-view-models.aspx
Take a look at Phil Haack's post, it gets a bit tricker with checkboxes as if a box is unchecked then it doesnt submit a value.
Model Binding to a List
http://haacked.com/archive/0001/01/01/model-binding-to-a-list.aspx
Related
In an MVC application, I have a list of exam questions and I want to present a small number of them to the user on the same page but where each answer can be submitted separately.
So my page looks like this ....
The view code is ....
#model List<QuestionResponseVM>
#for (int i = 0; i < Model.Count(); i++)
{
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model[i].QuestionID)
<tr>
<td width="35%">
#Html.Raw(Model[i].QuestionText)
#Html.HiddenFor(model => model[i].QuestionText)
</td>
<td>
#Html.TextAreaFor(model => model[i].Response, new { #name = "DisplayTextEdit", #id = "DisplayTextEdit", #rows = 1, #cols = 80 })
</td>
<td width="30%">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
</td>
</tr>
}
}
My problem is I can only get data returned to the POST method for Question 1.
Here is the Controller Code ....
public class ChecksController : Controller
{
public ActionResult CheckQuestions()
{
return View(LoadQuestions());
}
// POST: Checks
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(List<QuestionResponseVM> questResponses)
{
List<QuestionResponseVM> testList = new List<QuestionResponseVM>();
if (ModelState.IsValid)
{
testList = LoadQuestions(questResponses[0].QuestionID, questResponses[0].Response);
}
return View(testList);
}
private List<QuestionResponseVM> LoadQuestions (int _QuestionID = -1, string _Response = "")
{
List<QuestionResponseVM> thisList = new List<QuestionResponseVM>();
thisList.Add(new QuestionResponseVM()
{
QuestionID = 1,
QuestionText = "Question 1",
Response = (_QuestionID == 1 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 2,
QuestionText = "Question 2",
Response = (_QuestionID == 2 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 3,
QuestionText = "Question 3",
Response = (_QuestionID == 3 ? _Response : "")
});
return thisList;
}
}
If the Controller POST method has a parameter of QuestionResponseVM questResponses which is what I was expecting (hoping for) then null is returned from the view no matter which "Save" button is clicked.
However, if I change the parameter to a list (i.e. List<QuestionResponseVM> questResponses) then the "Save" button for Question 1 returns a list with a single item and correct data. But, any other "Save" button (e.g. Question 2 or Question 3) returns a null list.
The behaviour for scenario 1. seems counter-intuitive to me since the "Begin Form" is set to return a single model item (instance of the model) i.e. "Model[i]".
And in scenario 2., I just don't understand why it works for the first form ("Save" button) but not for the others.
I don't believe I should need to use JScript or AJAX to do this.
But clearly, I am not "connecting some dots" here.
Can someone please explain my observed behaviour and maybe give me a push in the right direction to meet this requirement.?
I would greatly appreciate any help.
Before going through your questions, I don't get what new {questResponses = Model[i] })) is doing in your forms:
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
...
}
Model[i] is a complex object. All you got there was the name of the object:
Q1: If the Controller POST method has just a single parameter
Since you're using a for loop to generate each form and inputs within the form, the name of those inputs will be in the forms of [INDEX].NAME:
By default, the model binding will bind those inputs (QuestionId, QuestionText and Response) to a matching object. QuestionResponseViewModel indeed matches that. The problem is [INDEX]. prefix.
In order for the default model binding to work, the parameter name you declare in the POST method has to be called [INDEX], i.e., [0] for the first form, [1] for the second form and so on:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM [0])
{
...
}
But you know we can't declare anything like that in C#.
The Fix for Q1
Instead of using the regular for loop, you can use foreach to generate each form. In that way, you get rid of the need for naming a parameter that's changing for each form.
Another "GOTYOU" here is that the parameter in the controller has to match the variable you declared in the for loop for each QuestionResponseViewModel:
#foreach (var qrVM in Model)
{
using(Html.BeginForm("..."))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(x => qrVM.QuestionId)
<tr>
<td>
#Html.DisplayFor(x => qrVM.QuestionId)
#Html.HiddenFor(x => qrVM.QuestionId)
</td>
...
</tr>
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM qrVM)
{
// If you name the parameter something else, it won't bind!
...
}
If you think about it, that makes sense, because you know the form will post data with keys like qrVM.QuestionId, qrVM.QuestionText back to the server. The default model binding will search for a model that has those properties and is named qrVM.
Q2: Change parameter to a list
When the first form posts back to the server, the form data in the request body will look like:
[0].RequestionId: 1
[0].RequestionText: Question 1
[0].Response: xxx
MVC model binding is still smart enough and thinks you're posting the first item of the list you declared. Hence you will see List<QuestionResponseVM> questResponses capture the correct data for the first form.
Well what about the second and third form? If you submit the data on the second form for example, the form data in the request body will look like:
[1].RequestionId: 2
[1].RequestionText: Question 2
[1].Response: xxx
MVC model binding sees it as the 2nd item of the list, but where's the 1st item? And it got confused so it couldn't bind the data to the parameter. Hence you will see NULL from the parameter List<QuestionResponseVM> questResponses.
My 2 cents
You actually cannot put a form inside a table or between table rows like this. It's considered as invalid HTML structure. It's never a good idea to use tables as structures to display data on the page anyway. Instead, You can use Bootstrap's row and columns.
I don't know why or what made you think you shouldn't need AJAX. Your case is like the best scenario to go with an AJAX approach! For example, with AJAX, the user can save each question's response individually. The page doesn't have to be refreshed.
Your save button on Question 1 is submitting the form to the controller. You will need to either have one Save/Submit button at the end of a set of questions and utilize the FormCollection object or spend time setting up JQuery/Ajax for click events on each button and removing the form element. You could have a bit of both if the button at the bottom becomes a 'Next' and then submits to a controller to get the next set of related questions.
I'm trying to access the properties of a class dynamically in ASP.NET Razor when generating an HTML Table. This problem is normally easily solved with reflection, but the #Html.DisplayFor method is giving me issues.
I am attempting to generate an HTML table that has 3 cells per row, with the title of the item in bold as the first line of the cell, and the value of the item in the second line of the cell. The contents of the table should not include cells which are on the 'Excluded Fields' list, and I do not want to have to statically reference each column.
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in item.GetType().GetProperties())
{
#if (!Model.ExcludedFields.Contains(#property.Name))
{
dynamic test = #property.GetValue(item);
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.DisplayFor(m => #test)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
I've tried calling #Html.DisplayFor(m => #property.GetValue(item)) but that just creates a runtime error. I can simply call #property.GetValue(item) and the value displays, but this is not ideal because I use display templates to do things like set dates to the ShortDateString format.
I understand that DisplayFor is using reflection to determine the type of the property, and that is why I am trying to use the dynamic variable to facilitate reflection for the method. However, when I run the method, it throws errors indicating the variable is not a generic parameter, and therefore cannot share its attributes. The resulting page has mostly blank values, and some cells filled in with unexpected descriptive information.
I feel like I'm getting close, but I don't know how to proceed. The page won't look right if I don't pass the values into an HTML display method, and I cannot think of any other way to get the type of table I want to be generated. Thoughts?
The issue is solved by creating a Display Template for the Security object, which then allowed me to properly use the #Html.Display Method, because the Model for the Display Template has an entry for the property.
Here is what the page code looks like now:
Display Template
#model Interface.Models.Security
#{int numCols = 3;}
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in Model.GetType().GetProperties())
{
#if (!BLPDLModel.ExcludedFields.Contains(#property.Name))
{
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.Display(property.Name)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
Razor Page
#foreach (var item in Model.Security)
{
<div class="blpSecurityItem">
<button type="button" class="collapsible">{button text}</button>
#{
<div class="collapsible-content">
<hr />
#Html.DisplayFor(m => item)
</div>
}
</div>
Replace #Html.DisplayFor(m => #test) with #Html.Display(property.Name)
and your model property for date should have [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}")] or anything you like
I am trying to make the user update the value of the check box from DB.
The view lists all the available workers in DB.
but when i try to access the list of checkbox, the view passes wrong data
ex. there're only 3 checkbox, and it sends 5 items of true/false value.
I really appreciate any assistance.
View Sample Code
#using (Html.BeginForm("UpdateWorkersForTask", "Tasks", FormMethod.Post))
{
<table>
<tr>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.isChecked)
#Html.CheckBox("WorkersID", item.isChecked)
</tr>
}
and the controler is
[HttpPost]
public ActionResult UpdateWorkersForTask(IEnumerable<bool> WorkersID, IEnumerable<string> hiddens) { }
Because by design #Html.CheckBox and #Html.CheckBoxFor render 2 inputs, a checkbox and a hidden input. If you inspect the html you will see something like
<input name="WorkersID" type="checkbox" value="true">
<input name="WorkersID" type="hidden" value="false">
The reason for this is that unchecked checkboxes do not post back. so the second input ensures a value is posted back. In the case where the checkbox is checked the DefaultModelBinderreads the first value which is true and ignores the second value (because it has the same name).
In your case you are not binding to you model, instead you are just reading all inputs with the name WorkersID. If you have 3 items but 5 values are posted back, it would mean that you checked 2 of the boxes.
To correct this, modify your view and action method as follows (note your model needs to be IList so the for loop works, or alternatively you can use a custom EditorTemplate
View
for(int i = 0; i < Model.Count i++)
{
#Html.CheckBoxFor(m => m[i].WorkersID)
}
Controller
[HttpPost]
ActionResult UpdateWorkersForTask(IEnumerable<YourModelType> model)
{
foreach(YourModelType item in model)
{
// do something with the value of item.WorkersID
I have the following problem - I am developing an ASP.NET MVC 3 application and I have view which is strongly typed. Because of the complexity of the data the model in the view looks like this :
#model List<List<DataAccess.MCS_DocumentFields>[]>
Then I render the view like this :
#using (Html.BeginForm("ActionMethodName", "Forms"))
{
<table border="1">
<tbody>
#for (int i = 0; i < Model.Count(); i++)
{
if (Model[i][0][0].ContentTypeId == 1)
{
#Html.Partial("_PartialHeader", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 2)
{
#Html.Partial("_PartialDrawing", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 3)
{
#Html.Partial("_PartialBody", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 4)
{
#Html.Partial("_PartialFooter", Model[i])
}
}
</tbody>
</table>
<button type="submit">Save</button>
}
and this is one of my partial views :
if (string.IsNullOrEmpty(item.FieldValue))
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
</td>
}
else
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
:
#Html.DisplayFor(y => y[i][0].FieldValue)
#Html.HiddenFor(y => y[i][0].FieldValue)
</td>
}
This is just a snippet, what I want to say is that my table has at least 8-9 rows and almost each row has #Html.HiddenFor so I expect to get data when I'm submiting the form.
In my controller I have this method :
[HttpPost]
public ActionResult ActionMethodName(List<MCS_DocumentFields>[] collection)
{
var test = collection;
List<MCS_Documents> model = DocumentService.All().ToList();
return View("Index", model);
}
I tried a lot of different types for the collection argument. Most of the time I get null, at best I get the first two row (No idea why exactly the first two) but nothing more. If I use FormCollection then I have all my submitted data, but it's not related to my MCS_DocumentFields class in any way.
I don't know what I'm doing wrong. I don't think that the type for the method should be guessed I think it must be determined by something and if there is some problem in code (most possibly in the partial view) because I've posted here almost all of my main view, then any suggestions why my logic is not working and why I can't bind the data to the original type of the model?
I suggest you to look at the BeginCollectionItem package (http://nuget.org/packages/BeginCollectionItem/), allthough I can see that your model is really complex (3 dimensions), sou you'll have to nest them in a really nasty manner.
Also you should consider (due to your model complexity) creating a custom model binder (http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder).
Just one last remark - your model in View differs from your model that you expect in action:
List<MCS_DocumentFields>[] collection != List<List<DataAccess.MCS_DocumentFields>[]>
Ivan
I have a table where each row contains fields for a form.
I iterate in the view using this:
#foreach (var item in Model) { }
Within the foreach, I create a new table row with various fields contained inside. e.g.:
<tr><td> #Html.DropDownList("granny", "") </td></tr>
"granny" is being passed via the controller, and looks a bit like this:
ViewData["granny"] = new SelectList(db.getGrannies(), "grannyid", "grannyname");
Everything's working pretty well. The view is getting its grannies, and everything looks good. I noticed however that the name property of the field { e.g....
<select id="granny" name="granny">
} is the exact same for EVERY row created. This is a problem. I want to toss this data back to the controller in the form of a FormCollection and do fun stuff with all these grannies. I can't do that if they're not all getting passed.
That is, I'd like the selects to read, instead, like this:
<select id="granny1" name="granny1">
<select id="granny2" name="granny2">
I researched the problem a bit, and tried using
new { #Name="xyz" + n }
But Visual Studio didn't like that much.
The short and sweet of it all is this:
How do I give ViewData-generated Html.DropDownLists their own unique ids/names?
How do I give ViewData-generated Html.DropDownLists their own unique ids/names?
You cannot change the name of the generated input field and this is by design. Only the id could be changed. This being said you shouldn't need to do that. You could do the following:
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DropDownList(
"selectedGranny",
(IEnumerable<SelectListItem>)ViewData["granny"]
)
</td>
</tr>
}
It seems like you are either trying to databind a collection, or you just need to manually name your selects (as they are really different controls on the web form).
You can use the following overload to pass html parameters to the drop down :
#Html.DropDownList(
"CategoryID",
(SelectList)ViewBag.CategoryId,
"--Select One--",
new{ //anonymous type
name = "granny1",
#class = "myCssClass",
onchange = "someFunction();"
})