Mvc3 IEnumerable<QuestionModel> have a List<QuestionOptionModel> property. When I post, I get null list - asp.net-mvc-3

I'm making a survey application. Survey has Questions and Questions have QuestionOption. haveThere is a example here.
I am trying to use this technique in a large form with a list(List) but when I post back, the Viewmodel.Order that should’ve contained list of items and activities return with the lists empty.
My QuestionModel.cs like this.
public int Id { get; set; }
public string QuestionText { get; set; }
public System.Nullable<bool> OptionType1 { get; set; }
public System.Nullable<bool> OptionType2 { get; set; }
public List<QuestionOptionModel> OptionList = new List<QuestionOptionModel>();
When I post back "IEnumerable questions" List OptionList comes null. How can I do this?
public ActionResult CallSurvey()
{
IEnumerable<QuestionModel> questionModelList = (IEnumerable<QuestionModel>)SessionHelper.GetSessionObject(SessionKeys.SurveyKey);
questionModelList = questionSrv.GetQuestionModel();
return View(questionModelList);
}
questionModelList include all my survey question and question options. When I post it, post back is coming with only null optionList.
[HttpPost]
public ActionResult CallSurvey(IEnumerable<QuestionModel> questions)
{ ..... }
CallSurvey.cshtml
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
#foreach (var item in Model)
{<article>
#Html.Partial("QuestionEditor", item)
</article>
}
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
QuestionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
<div>
#using (Html.BeginCollectionItem("Questions"))
{
<table id="table1">
<tr>
<td>
<div id="#Model.Id" class="hint">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.QuestionText)
#Html.HiddenFor(m => m.OptionType1)
#Html.HiddenFor(m => m.OptionType2)
#for (int i = 0; i < Model.OptionList.Count; ++i)
{
#Html.LabelFor(m => m.OptionList[i].Id)
#Html.LabelFor(m => m.OptionList[i].QuestionId)
#Html.LabelFor(m => m.OptionList[i].Checked)
#Html.LabelFor(m => m.OptionList[i].Description)
#Html.LabelFor(m => m.OptionList[i])
}
<span id="sorular">#Model.Id. #Model.QuestionText</span>
<br />
<br />
</div>
</td>
</tr>
<tr>
<td>
<div class="hint2">
#Html.Partial("QuestionOptionEditor", Model)
</div>
</td>
<td>
<div id="#Model.Id-Img">
<h2 style="top: 200px; right: 0;">
<img src="../../Content/css/img/#Model.Id-Img.png"></h2>
</div>
</td>
</tr>
</table>
and QuestionOptionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
#foreach (var option in #Model.OptionList)
{
<p>
#if (#Model.OptionType1 == false)
{
#Html.Partial("QuestionOptionModel", option)
}
else
{
#Html.Partial("../Shared/DisplayTemplates/QuestionOptionModel", option)
}
</p>
}
Here QuestionOptionModel views like this;
#model LSMM.Business.Model.Survey.QuestionOptionModel
#(Html.RadioButtonFor(m => m.Id, true, new { Id = #Model.Id, Name = #Model.QuestionId })) #Html.Label("Evet")
<br />
<br />
#(Html.RadioButtonFor(m => m.Id, false ,new { Id=#Model.Id, Name = #Model.QuestionId})) #Html.Label("Hayır")

The name attribute on your radio buttons is not "correct" according to the naming rules used by the default ModelBinder. That's why you aren't seeing the values you expect, the ModelBinder couldn't find what it was looking for.
This is easy to fix and you should end up with less code.
Take advantage of the framework and let it do work for you:
MVC can loop IEnumerables for you. Let it. There's little reason to write foreach loops in views these days.
Get rid of those partials. Use MVC's Templates feature instead.
Then your view can be as simple as:
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
<table id="table1">
#* Table header goes here *#
#Html.EditorFor(model => model.OptionList)
</table>
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
When you use an EditorTemplate or a DisplayTemplate, MVC will automatically fix the name attribute on your form fields. It won't do that with a partial.
Another suggestion, also if you don't mind. Get rid of those nulla-bools on your model and write different QuestionOptionModel types. DisplayTemplate/EditorTemplates are wise to the type system and MVC will pick the template that matches the type it is presented.
So you could have another class that derives from QuestionOptionModel:
public class ExtendedQuestionOptionModel : QuestionOptionModel
{
//Stuff goes here.
}
and you could have a List<QuestionOptionModel> hanging off your model that contains:
QuestionOptionModel
ExtendedQuestionOptionModel
QuestionOptionModel
and when you do this:
#Html.EditorFor(model => model.QuestionOptions)
MVC will go through the option list and render the Editor Template for QuestionOptionModel, then the Editor Template for ExtendedQuestionOptionModel, then the Editor Template for QuestionOptionModel again.
The game you want to play is to try to minimize the C# code in the views, because the views cannot be unit tested. Code that isn't unit tested will eventually come back and get you.

Your question is abit ambiguous. Frankly i hope this is what you are loooking for : IEnumerable can be transformed to list using
List<foo>listFoo =iEnumerableEntityFoo.toList()

Related

Switch partial based on radio button in ASP.NET Core MVC

I don't know how to perform this in ASP.NET Core MVC.
I've got two radio buttons of the same group, I wish to switch a partial view based on the current radio button selection. I think I'll have to use js somehow but I don't know how.
Currently I wrote
Html.Kendo().RadioButton().Name("manual").Checked(true).HtmlAttributes(new { #name = "group1" }).Label("Manuale");
Html.Kendo().RadioButton().Name("guidata").Checked(true).HtmlAttributes(new { #name = "group1" }).Label("Guidata");
<div id="dyn_container"/>
But I don't know how to load data and eventually pass a model, any suggestions?
Thanks
The essence of RadioButton is actually an Input, so you can use the method of monitoring Input changes to determine how to continue to perform the following operations.
Below is my test code, you can refer to it:
View:
<table>
<tr>
<td class="Form_Label"><label for="txType">Select List:</label> </td>
<td>
#Html.Kendo().RadioButton().Name("manual").Checked(true).HtmlAttributes(new { #name = "group1",#value = "Manuale" }).Label("Manuale")
#Html.Kendo().RadioButton().Name("guidata").Checked(true).HtmlAttributes(new { #name = "group1",#value = "Guidata" }).Label("Guidata")
</td>
</tr>
</table>
<div id="dyn_container"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script >
$('input:radio[name=group1]').change(function() {
var url = '/Home/Manuale';
if ($(this).val() === 'Guidata') {
url = '/Home/Guidata';
}
$("#dyn_container").load(url);
});
</script>
Then, you can execute the corresponding action in the controller by:
[HttpGet]
public ActionResult Manuale()
{
return PartialView("_Manuale");
}
[HttpGet]
public ActionResult Guidata()
{
return PartialView("_Guidata");
}
Test Result:
Update:
This is caused by inconsistency between your constructor and instantiation, you need to inject ILogger<_Page1Model> into the page.
Add this line of code in View:
#inject ILogger<_Page1Model> logger1
Then modify Html.PartialAsync:
#await Html.PartialAsync("_Page1", new _Page1Model(logger1));
Excuse me, I have a little bit modified your example (I don't have the original code under my hands so I wrote a new one).
I don't have a controller, using the RazorPages, so currently I've done this way
#page
#using Microsoft.AspNetCore.Mvc.Localization
#using Microsoft.AspNetCore.Mvc.RazorPages
#using Test.Localization
#using Test.Web.Pages
#using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Table
#using Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers
#using Volo.Abp.Users
#model Test.Web.Pages.IndexModel
#inject IHtmlLocalizer<TestResource> L
#inject ICurrentUser CurrentUser
#section styles {
<abp-style src="/Pages/Index.css" />
}
#section scripts {
<abp-script src="/Pages/Index.js" />
}
<div class="container">
<table>
<tr>
<td class="Form_Label"><label for="txType">Select List:</label> </td>
<td>
#Html.RadioButton("group1","HotelGrandPlaza", false)
#Html.RadioButton("group1","Test", false)
</td>
</tr>
</table>
<div id="project" style="display:none">
#await Html.PartialAsync("_Page1", new _Page1Model());
</div>
<div id="engineering" style="display:none">
#{await Html.RenderPartialAsync("_Page2", new _Page2Model());}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script >
$('input:radio[name=group1]').change(function() {
if ($(this).val() === 'Test') {
$("#project").hide();
$("#engineering").show();
} else {
{
$("#project").show();
$("#engineering").hide();
}
}
});
</script>
</div>
In this case when I switch it works... but problems arrive when I have a constructor with Dependency Injection....
Just with this simple case
public class _Page1Model : PageModel
{
private readonly ILogger<_Page1Model> _logger;
public _Page1Model(ILogger<_Page1Model> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
I can't use the cshtml I've defined before... any suggestions?
Thanks

MVC 5 dynamic model in partial view

I have a few (around 6) lookup tables like degreetype, categorytype, etc. All these table has similar column id, name and isactive fields. I created CRUD stored procedure for each table.
In my MVC5 project, i created a model, repository and DAL to pass data to sp for each.
I also created viewmodel, controller and crud views for each.
I realized the crud (create/edit/detail/list/delete) view pages uses the same html except for calling the model at the top of the page.
Question: Is there any way i can create a partial view of the html and use a dynamic model in the pages?
eg:
#model Microsoft.myorg.viewmodels.degreetypesVM
<!-- need to use a dynamic model (degreetypesVM, categorytypeVM etc) above and create a partial view for the below html -->
#{
ViewBag.Title = "Degree Type";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<table class="container">
<tbody>
<tr class="row">
<td class="col2" scope="row">
<div class="title-text">
#Html.LabelFor(model => model.Name)
<span title="This field is required." class="warning">*</span>
</div>
</td>
<td class="gray">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</td>
</tr>
..
..
</tbody>
..
..
you can simply write a partial view and pass model in it or just wrap up all common fileds
and write their own model for your partial view

Using hidden fields during HTTP post for the reload of same page postback

I have a View where the User can leave a feedback comment in regards to the content video that is playing on the page. Using the following code I am able to manually enter the UserID and ContentID and it saves to the database without problem.
#using (Html.BeginForm("Screencast", "Media", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div class="row">
<div class="six columns">
<div class="row">
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.UserID)
#Html.TextBoxFor(c => c.FeedbackComment.UserID)
</div>
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.ContentID)
#Html.TextBoxFor(c => c.FeedbackComment.ContentID)
</div>
<div class="row">
<div class="twelve columns">
#Html.LabelFor(c => c.FeedbackComment.FeedbackString)
#Html.TextAreaFor(c => c.FeedbackComment.FeedbackString)
</div>
</div>
</div>
</div>
</div>
<input type="submit" value="Submit button" class="medium button bottom20"/>
}
However, when the user is on the page before the HTTP post I actually have the related variables in my Model called:
Model.User.UserID
Model.SelectedItem.ContentID
I want to pass these in as hidden fields but when I try to do either:
#HtmlHiddenFor(c => c.FeedbackComment.UserID, #Model.User.UserID)
#HtmlHiddenFor(c => c.FeedbackComment.ContentID, #Model.SelectedItem.ContentID)
or
#HtmlHidden("UserID",#Model.User.UserID)
#HtmlHidden("ContentID",#Model.User.UserID)
Then these values are returned null despite the values being populated before the post. I read about a workaround of putting the input tags in manually but when I did this the #Using.HtmlBeginForm was returning an error of not being set to an instance of an object
Can someone please help me to understand how I can pass these values to the same page using the values I have in the model prior to the post.
Given the following view model (partial):
public class YourViewModel
{
public User User { get; set; }
public SelectedItem SelectedItem { get; set; }
}
You can bind the these properties to a hidden form element. When you post back these properties will still contain their values.
#Html.HiddenFor(x => x.User.UserID)
#Html.HiddenFor(x => x.SelectedItem.ContentID)
Your action method:
public ActionResult YourActionMethod(YourViewModel viewModel)
{
// When you debug here you will see that the values are retained
}
I hope this helps.

MVC unobtrusive validation only being applied to one nested model

I have a form made up of nested models as below:
foreach (var item in Model)
{
<h3>
#item.StageDescription
</h3>
<div class="well">
<table id="Item#(item.ID)" class="WizardOption">
<thead>
<tr>
<some headings here />
</tr>
</thead>
<tbody>
#Html.EditorFor(m => item.WizardOptions,"","WizardOptions",null)
</tbody>
</table>
</div>
}
The WizardOption class has a required field call Display Value:
public class WizardOptionMetaData {
[Required]
public string DisplayValue { get; set; }
}
This works fine for the first table, if I leave a DisplayValue field blank I get the error: "The DisplayValue field is required." and the following markup is rendered:
<input class="description-box" data-val="true" data-val-required="The DisplayValue field is required." id="WizardOptions_0__DisplayValue" name="WizardOptions[0].DisplayValue" type="text" value="">
But any tables after the first one don't get the validation rendered properly:
<input class="description-box" id="WizardOptions_1__DisplayValue" name="WizardOptions[1].DisplayValue" type="text" value="">
Where am I going wrong?
Found the answer on a question I didn't find until after I posted the question:
ASP.NET MVC 3: Generate unobtrusive validation when BeginForm is on the layout
#{
var originalContext = ViewContext.FormContext;
ViewContext.FormContext = new FormContext();
}
<!-- This will generate proper HTML5 data-* validation attributes -->
#Html.TextBoxFor(x => x.Prop1)
#Html.ValidationMessageFor(x => x.Prop1)
#Html.TextBoxFor(x => x.Prop2)
#Html.ValidationMessageFor(x => x.Prop2)
#{
ViewContext.FormContext = originalContext;
}

MVC3 radiobuttons do not behave like they should

I have a problem that's starting to drive me crazy.. I've narrowed my problem down to the following test case:
Model:
public class TestModel
{
public bool TestBool { get; set; }
}
Index View:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.Action("Test")
Test View:
#model IEnumerable<Test_GUI.Models.TestModel>
#using (Html.BeginForm()) {
<table>
#Html.EditorForModel()
</table>
<input type="submit" value="OK"/>
}
Editor template;
<tr>
<td>
Yes
#Html.RadioButtonFor(m => m.TestBool, Model.TestBool)
</td>
<td>
No
#Html.RadioButtonFor(m => m.TestBool, !Model.TestBool)
</td>
In the TestController, I create two instances of TestModel with a value of false and pass them to the view. But the radiobuttons are rendered as checked, and they also return as true if I post the form..
I've tried many other ways to display the radiobuttons, but nothing seems to work. This seems like to correct way to do it.
I must be able use the current value of the boolean, so I cannot use fixed true or false values in the view. If I do use a fixed true/false, I do get the correct values on the form and in the controller after posting the form..
Try like this:
<td>
Yes
#Html.RadioButtonFor(m => m.TestBool, true)
</td>
<td>
No
#Html.RadioButtonFor(m => m.TestBool, false)
</td>

Resources