MVC unobtrusive validation only being applied to one nested model - asp.net-mvc-3

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;
}

Related

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.

Unobtrusive validation editorfor

I've got a partial View, loaded from an Action, so the parent view contains:
#Html.Action("TourSearch")
The TourSearch View uses a editor as such:
#Html.EditorFor(model => model.ImpersonatedAgentModel, "ImpersonatedAgentView")
where ImpersonatedAgentModel is as such:
[Serializable]
public class ImpersonatedAgentModel
{
[Required(ErrorMessage = "Please provide a User ref")]
public string AgentImpersonatedUserName { get; set; }
[Required(ErrorMessage="Please provide a ABTA/AgencyCode")]
public string AgentImpersonatedBranchCode { get; set; }
[Required(ErrorMessage = "Please provide a User ref")]
public int? AgentImpersonatedBranchID { get; set; }
}
My editor is pretty straight forward:
#model Travel2.WebUI.Models.ImpersonatedAgentModel
<ul id="agencyDetails">
<li>
<label for="AgentImpersonatedBranchCode">ABTA/Agency Code: *</label>
#Html.TextBoxFor(model => model.AgentImpersonatedBranchCode, new {ID="txtBranchCode" })
#Html.ValidationMessageFor(model => model.AgentImpersonatedBranchCode, "*")
<input id="txtBranchId", type="hidden" value="#Model.AgentImpersonatedBranchID" />
<input id="txtUserName", type="hidden" value="#Model.AgentImpersonatedUserName" />
<input id="hidCurrentController" type="hidden" value='#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue' />
</li>
<li>
<label for="AgentImpersonatedUserName">User ref: *</label>
<select id="ddlUser" disabled="disabled" ></select>
<input type="hidden" id="txtUserID" />
#Html.HiddenFor(model => model.AgentImpersonatedUserName, new {ID="AgentImpersonatedUserName" })
#Html.HiddenFor(model => model.AgentImpersonatedBranchID, new {ID="AgentImpersonatedBranchID"})
#Html.ValidationMessageFor(model => model.AgentImpersonatedUserName, "*")
</li>
</ul>
Now in Chrome, all works fine. But when I fill in the form in IE but not the data in the Editor, it passes validation incorrectly!
If I examine the markup, using IEs poor excuse for Firebug, I can see the validation attributes,
<input name="ImpersonatedAgentModel.AgentImpersonatedBranchCode" id="txtBranchCode" type="text" data-val="true" data-val-required="Please provide a ABTA/AgencyCode" jQuery172048066185567747205="94"/>
so why is IE ignoring them!! Stupid IE
Found the answer here:
http://www.tigraine.at/2011/08/26/jquery-validate-and-microsofts-unobtrusive-validation-dont-play-well-together/
We were referencing Jquery validation too and this seems to be causing this error

Mvc3 IEnumerable<QuestionModel> have a List<QuestionOptionModel> property. When I post, I get null list

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()

Updating Entity With A Child Using EditorTemplate

I have an Entity (QuoteSheet) that contains a child entity (QuoteTask), which is loaded using the EntityFramework. However, I am receiving an error when I submit this form.
I have created an edit page for the QuoteSheet entity, which then uses an EditorTemplate to edit the QuoteTask child entity.
The controller code is as follows:
public ActionResult TestEdit(int Id)
{
var quote = DataContext.QuoteSheets.Where(x => x.ID == Id).FirstOrDefault();
return View(quote);
}
[HttpPost]
public ActionResult TestEdit(Models.QuoteSheet quote)
{
return View(quote);
}
A stripped down version of the view is as follows:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(x => x.JobID);
<div class="sectionHeader">Sheet Details</div>
<div class="sectionContent">
<table>
<tr>
<td width="150">Sheet Desc.</td><td>#Html.TextBoxFor(x => x.Description, new { size = "50" })</td>
</tr>
<tr>
<td>Quantity Required</td><td>#Html.EditorFor(x => x.Quantity)</td>
</tr>
</table>
</div>
<div class="sectionHeader">Tasks</div>
<div class="sectionContent">
<table id="Tasks">
<tr>
<th>Labour Group</th>
<th>Task Description</th>
<th>Total Hrs</th>
<th>Rate</th>
<th>Cost</th>
</tr>
#Html.EditorFor(x => x.QuoteTasks)
</table>
<input type="button" name="AddTasks" id="AddTasks" value="Add" />
</div>
<input type="submit" value="Submit" />
#Html.ValidationSummary()
}
And the EditorTemplate is:
#model Ornavi.Models.QuoteTask
<tr>
<td>#Html.EditorFor(x => Model.LabourGroup)</td>
<td>#Html.EditorFor(x => Model.Description)</td>
<td>#Html.EditorFor(x => Model.TotalHours)</td>
<td>#Html.EditorFor(x => Model.Rate)</td>
<td>#Html.HiddenFor(x => Model.ID)</td>
</tr>
When I submit the form, I am getting the following error:
The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.
This only occurs when I use the EditorTemplate - if I remove the editor template and just submit the main entity, it works fine.
I have placed a breakpoint in the [httppost] TestEdit function, but the exception occurs before it reaches this point.
Any ideas on how to successfully use an EditorTemplate to edit a child entity?
The problem is, that the default modelbinder tries to instantiate your EF class and set the navigation properties when binding the form data to your parameter types.
See some similar questions like this one.
You have two options:
Don't use your EF classes as viewmodels but create own viewmodel classes to pass the data between controller and view.
Don't bind directly to the EF class in your Edit controller action but use a FormCollection parameter and bind yourself with UpdateModel as shown in the linked question.

MVC3 Razor Partial view render does not include data- validation attributes

I have a farily straight forward form that renders personal data as a partial view in the center of the form. I can not get client side validation to work on this form.
I started chasing down the generate html and came up with the same model field rendered on a standard form and a partial view.
I noticed that the input elements are correctly populated on the first call, #html.partial, the following only happens when the partialview is reloaded via an ajax request.
First the header of my partial view, this is within a Ajax.BeginForm on the main page.
#model MvcMPAPool.ViewModels.EventRegistration
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function ()
{
$(".phoneMask").mask("(999) 999-9999");
});
</script>
#{
var nPhn = 0;
var dTotal = 0.0D;
var ajaxOpts = new AjaxOptions{ HttpMethod="Post", UpdateTargetId="idRegistrationSummary", OnSuccess="PostOnSuccess" };
Html.EnableClientValidation( true );
Html.EnableUnobtrusiveJavaScript( true );
}
Here is the razor markup from the partial view:
#Html.ValidationMessageFor(model=>Model.Player.Person.Addresses[0].PostalCode)
<table>
<tr>
<td style="width:200px;">City*</td>
<td>State</td>
<td>Zip/Postal Code</td>
</tr>
<tr>
<td>#Html.TextBoxFor(p=>Model.Player.Person.Addresses[0].CityName, new { style="width:200px;", maxlength=50 })</td>
<td>
#Html.DropDownListFor(p=> Model.Player.Person.Addresses[0].StateCode
, MPAUtils.GetStateList(Model.Player.Person.Addresses[0].StateCode))</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(p=>Model.Player.Person.Addresses[0].PostalCode, new { style="width:80px;", maxlength=10 })
</div>
</td>
</tr>
</table>
Here is the rendered field from the partial view:
<td>
<div class="editor-field">
<input id="Player_Person_Addresses_0__PostalCode" maxlength="10" name="Player.Person.Addresses[0].PostalCode" style="width:80px;" type="text" value="" />
</div>
</td>
Here is the same model field rendered in a standard view:
<div class="editor-field">
<input data-val="true" data-val-length="The field Postal/Zip Code must be a string with a maximum length of 10." data-val-length-max="10" data-val-required="Postal or Zip code must be provided!" id="Person_Addresses_0__PostalCode" maxlength="10" name="Person.Addresses[0].PostalCode" title="Postal/Zip Code is required" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Person.Addresses[0].PostalCode" data-valmsg-replace="true"></span>
</div>
Notice that the partial view rendering has no data-val-xxx attributes on the input element.
Is this correct? I do not see how the client side validation could work without these attributes, or am I missing something basic here?
In order to create the unobtrusive validation attributes, a FormContext must exist. Add the following at the top of your partial view:
if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
If you want the data validation tags to be there, you need to be in a FormContext. Hence, if you're dynamically generating parts of your form, you need to include the following line in your partial view:
#{ if(ViewContext.FormContext == null) {ViewContext.FormContext = new FormContext(); }}
You then need to make sure you dynamically rebind your unobtrusive validation each time you add/remove items:
$("#form").removeData("validator");
$("#form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("#form");

Resources