partial view coming without validation attributes (ASP.NET MVC 3) - asp.net-mvc

A weird thing keeps happening on one of my ASP.NET MVC 3 apps.
I am fetching insert rows via jQuery Ajax api and there is no problem with that. But when I get the necessary partial view back, it is coming without validation attributes and I am unable to rebind the validation for those rows.
Here is what I get as ajax response:
<input type="hidden" name="accommPropertyPeriods.index" autocomplete="off" value="ccaa15b3-76f1-4215-8bb5-a62d700bfc1e" />
<table style="width:100%;">
<tr>
<td>
<div class="editor-field">
<select class="chzn-select-deselect" data-placeholder="Choose an Alias..." id="accommPropertyPeriods_ccaa15b3-76f1-4215-8bb5-a62d700bfc1e__AccommPropertySeasonPeriodAliasID" name="accommPropertyPeriods[ccaa15b3-76f1-4215-8bb5-a62d700bfc1e].AccommPropertySeasonPeriodAliasID" style="min-width:100px;"><option value="302">A</option>
<option value="303">B</option>
<option value="304">C</option>
<option value="305">D</option>
</select>
</div>
</td>
<td>
<div class="editor-field">
<input class="datefield" id="accommPropertyPeriods_ccaa15b3-76f1-4215-8bb5-a62d700bfc1e__PeriodStartsAt" name="accommPropertyPeriods[ccaa15b3-76f1-4215-8bb5-a62d700bfc1e].PeriodStartsAt" type="text" value="" />
</div>
</td>
<td>
<div class="editor-field">
<input class="datefield" id="accommPropertyPeriods_ccaa15b3-76f1-4215-8bb5-a62d700bfc1e__PeriodEndsAt" name="accommPropertyPeriods[ccaa15b3-76f1-4215-8bb5-a62d700bfc1e].PeriodEndsAt" type="text" value="" />
</div>
</td>
</tr>
</table>
Here is what I should be getting :
<input type="hidden" name="accommPropertyPeriods.index" autocomplete="off" value="84ddd0f5-a3e2-4f10-8e67-f32528c6393d" />
<table style="width:100%;">
<tr>
<td>
<div class="editor-field">
<select class="chzn-select-deselect" data-placeholder="Choose an Alias..." data-val="true" data-val-number="The field AccommPropertySeasonPeriodAliasID must be a number." data-val-required="The AccommPropertySeasonPeriodAliasID field is required." id="accommPropertyPeriods_84ddd0f5-a3e2-4f10-8e67-f32528c6393d__AccommPropertySeasonPeriodAliasID" name="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].AccommPropertySeasonPeriodAliasID" style="min-width:100px;"><option value="302">A</option>
<option value="303">B</option>
<option value="304">C</option>
<option value="305">D</option>
</select>
<span class="field-validation-valid" data-valmsg-for="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].AccommPropertySeasonPeriodAliasID" data-valmsg-replace="false">*</span>
</div>
</td>
<td>
<div class="editor-field">
<input class="datefield" data-val="true" data-val-required="The PeriodStartsAt field is required." id="accommPropertyPeriods_84ddd0f5-a3e2-4f10-8e67-f32528c6393d__PeriodStartsAt" name="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].PeriodStartsAt" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].PeriodEndsAt" data-valmsg-replace="false">*</span>
</div>
</td>
<td>
<div class="editor-field">
<input class="datefield" data-val="true" data-val-required="The PeriodEndsAt field is required." id="accommPropertyPeriods_84ddd0f5-a3e2-4f10-8e67-f32528c6393d__PeriodEndsAt" name="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].PeriodEndsAt" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="accommPropertyPeriods[84ddd0f5-a3e2-4f10-8e67-f32528c6393d].PeriodEndsAt" data-valmsg-replace="false">*</span>
</div>
</td>
</tr>
</table>
GUIDs don't have to be the same. I am doing so-called non-sequential binding.
Here is the the action which I am invoking through jquery ajax to get a new insert row:
[HttpPost]
public PartialViewResult accommPropertySeasonPeriodCreatePartialView(int id, int subid) {
//some other stuff going on here. non-related to partial view.
return PartialView("_AccommPropertySeasonPeriodCreatePartialView");
}
I am nearly out of my mind to figure out why this is happening. Any idea?

The Html.* helpers such as Html.TextBoxFor, Html.CheckBoxFor, ... emit validation attributes only if used inside a form. So make sure you wrap them in a Html.BeginForm call. As far as client side validation is concerned, you should call the jQuery.validator.unobtrusive.parse method after you update your DOM in order to reapply client validation. And yet another article.
If you don't have a form you can cheat and put the following in the partial:
#model MyViewModel
#{
ViewContext.FormContext = new FormContext();
}
#Html.EditorFor(x => x.Foo)
Now the helper will emit data-* validation attributes on the input field.

Related

How to temporary store in form data in .NET core

I am learning ASP.Net Core and I am creating a mock Flight booking app. On my home page, I have a search form to find a flight.
On submitting my form it goes to my SearchResultsController and then to my Search() action where I will run a search query.
What's the best way to pass the parameters through to this controller action for use in the Search() action method?
All the methods I've seen this far have involved creating new models but I don't want to save the searches to my database as it's not information that needs retaining. Having come to form a rails background I would use the parms hash which is a temporary data store.
Form:
<form asp-controller="SearchResults" asp-action="Search" method="post">
<div class="form-group">
<label for="fromAirport">Flying from:</label>
<select asp-for="AirportId" asp-items="#(new SelectList(ViewBag.ListOfAirports, "AirportId", "AirportCode"))" class="form-control">
</select>
<div class="form-group">
<label for="toAirport">Flying to:</label>
<select asp-for="AirportId" asp-items="#(new SelectList(ViewBag.ListOfAirports, "AirportId", "AirportCode"))" class="form-control">
</select>
</div>
<label for="fromAirport">Departure Date:</label>
<br />
<input type="date" />
<br />
<label for="fromAirport">No. passengers:</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
<button type="submit" class="btn btn-primary mt-3">Search Flights</button>
</div>
</form>

Applying bootstrap classes prevents DropDownList from opening

I have this page that I recently used Bootstrap to make it responsive. Prior to the responsiveness the page worked correctly but looked bad in different screen sizes. Now, it looks great in multiple screen sizes but the dropdownlists do not work. If I remove the class="col-lg-10 col-md-10 col-xs-12"then the dropdownlist works again. Also, if I pull the dropdownlist out of the div and put it somewhere else on the page it also works.
For clarification when I say it doesn't work I mean if I click on the dropdownlist it does not expand. btw, I have more dropdownlists in the form, I just removed them to be more concise in the code.
Here is the code:
<div row>
<div class="col-lg-10 col-md-10 col-xs-12">
<table class="table table-condensed">
<tr>
<th>#Html.LabelFor(m => m.CategoryId, htmlAttributes: new { #class = "control-label col-md-2" })</th>
</tr>
<tr>
<td>
<span style="font-weight:normal;">#Html.DropDownListFor(m => m.CategoryId, new SelectList(ViewBag.Category, "CategoryId", "CategoryName"))</span>
#Html.ValidationMessageFor(m => m.CategoryId, "", new { #class = "text-danger" })
</td>
</tr>
</table>
</div>
</div>
I found that I can add this link below to the page which will fix the dropdown but it messes up all the formatting.
<link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
and here is the rendered HTML:
<div class="col-lg-10 col-md-10 col-xs-12">
<table class="table table-condensed">
<tr>
<th><label class="control-label col-md-2" for="CategoryId">Category</label></th>
</tr>
<td>
<span style="font-weight:normal;"><select data-val="true" data-val-required="The Category field is required." id="CategoryId" name="CategoryId"><option value="ae159a72-2447-42b6-9693-149964fdc896">Other</option>
<option value="45cf430d-bd7e-4003-ab97-33fb55520551">E-Mail/ Outlook</option>
<option value="dc27d6b2-59d9-46fd-b92e-42cf70b7f732">Administrative</option>
<option value="44124582-e37a-47f4-a98a-5f97049ba5b9">OPS</option>
<option selected="selected" value="c0ed4cd4-400f-479c-aa26-8522667241d5">Automation</option>
<option value="9080fef6-382f-49ad-993b-9f0689f90c95">Network</option>
<option value="d7b2ce5f-f9e6-48f1-a6bb-b4141c8bdc7e">stcusa.com</option>
<option value="d8121018-a401-48a0-9dec-c209b3be3495">Software</option>
<option value="7ba3a1e6-5cd6-4d63-8c09-d54826261441">Reporting</option>
<option value="efe7c637-e210-44d4-83a3-dac3199f5cae">Phones</option>
<option value="f8f35cdd-09a6-459d-9a7c-dd28df0f8776">Hardware</option>
</select></span>
Correct your div tag:
<div row>
should be
<div class="row">
In most of situations where an element does not respond to clicks! there's another div covering your it because of problems in container and row DIVs. It's not about razor, look into your rendered html.

ASP.Net MVC 4 C# login form with PhantomJS and Selenium

I have a specific question, as i can't figure out how to submit form without "id" or "name" in selenium.webdriver (phantomjs) there is html logim form:
<div class="ui-helper-child-2 ui-page-column-last ui-page-column-span-12">
<div class="ui-page-column-block ui-widget-signin">
<h1>Sign In</h1>
<form action="/glo/en-us/session,create/view.html" method="post">
<div class="ui-helper-child-1">
<label class="ui-state-required" for="username">Customer ID:</label>
<input id="username" name="username" maxlength="9" type="text" value="" />
</div>
<div class="ui-helper-child-2">
<label class="ui-state-required" for="password">Password:</label>
<input id="password" name="password" maxlength="20" type="password" value="" />
</div>
<div class="ui-helper-child-3">
<label class="ui-state-required" for="companyId">Company:</label>
<select id="companyId" name="companyId">
<option selected="selected" value="glo">Choose company</option>
<option value="asp">new to asp</option>
<option value="aeg">who is it</option>
<option value="asus">who use this brand name</option>
</select>
</div>
<div class="ui-helper-child-4">
<input id="callback" name="callback" type="hidden" value="/glo/en-us/home,show/view.html?error=1" />
<input type="submit" value="Sign In" />
</div>
</form>
</div>
and one more time how to login? I can add information to by webdriver.FindElement(username) and so on, but not know how to post data as i don't have id or name in submit element.
I think this will work for you:
driver.FindElement(By.CssSelector(".ui-helper-child-4>[type='submit']")).Click();

Steve Sanderson's BeginCollectionItem helper won't bind correctly

I am using Steve Sanderson's BeginCollectionItem helper and ran into a problem. I have a form that has an option to add unlimited reward fields. I am using his helper since it solved this problem with how to keep generating the fields and not have to worry about how to bind it when the form gets submitted.
I have in this same form some checkboxes that there is an unknown amount. The difference with this one versus the rewards is the unknown amount will become known after a database call and will be known by the time the code gets to the view.
So my code looks like this
public class FrmVm
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; } // this is the checkbox ones.
public IList<RewardVms> RewardVms { get; set; } // this is the dyanmic one that I needed the helper for
public CbCreditCardFrmVm()
{
Active = true;
WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
RewardVms = new List<RewardVms>();
}
}
// view
#foreach (var tier in Model.RewardVms)
{
#Html.Partial("GenerateReward", tier) // in this partial view in the BeginCollectionItem
}
#foreach (var warranties in Model.WarrantyFeaturesVm)
{
using (Html.BeginCollectionItem("WarrantyFeaturesVm"))
{
<span>#warranties.Name:</span>
#Html.TextBoxFor(x => warranties.FeatureId)
#Html.CheckBoxFor(x => warranties.HasFeature)
}
}
I am using jquery to submit the data by using serializeArray(). When it goes to the server it bind all the dynamic ones correctly and even binds the Warranty to the Collection(the collection count is 1). Yet it never binds anything insides the WarrantyFeaturesVm, everything is left as default.
if I remove using (Html.BeginCollectionItem("WarrantyFeaturesVm")) then it won't even bind the collection.
Anyone know why it is not binding anything in the collection?
Edit
// for loop (works)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: none;">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[0].FeatureId" id="WarrantyFeaturesVm_0__FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[0].HasFeature" id="WarrantyFeaturesVm_0__HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[0].HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</form>
//foreach loop beginItemCollection(does not work)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: inline;">
<input type="hidden" value="68ba9241-c409-4f4b-96da-cce13b127c1e" autocomplete="off" name="WarrantyFeaturesVm.index" class="ui-wizard-content ui-helper-reset ui-state-default">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.FeatureId" id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.HasFeature" id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</span>
</form>
//for loop beginItemCollection (does not work)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: none;">
<input type="hidden" value="fe3fbc82-a2df-476d-a15a-dacd841df97e" autocomplete="off" name="WarrantyFeaturesVm.index" class="ui-wizard-content ui-helper-reset ui-state-default">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].FeatureId" id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].HasFeature" id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</span>
<span id="adminSettings" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: inline;">
</form>
Ok I think I see what is going on here.
In the second sample, where you did the foreach, it looks like your cshtml was something like this (# symbols may be incorrect):
foreach (var war in Model.WarrantyFeaturesVm) {
using (Html.BeginCollectionItem("WarrantyFeaturesVm")) {
Html.HiddenFor(m => war.FeatureId)
<span>#Html.DisplayFor(m => war.Name)</span>
Html.HiddenFor(m => war.HasFeature)
}
}
Because BeginCollectionItem uses its context to derive the HTML names and id's, this is why you end up with "war" in the id's and names. The model binder is looking for a collection property named "WarrantyFeaturesVm", which it finds. However it is then looking for a property named "war" on the WarrantyFeaturesVm viewmodel, which it cannot find, and thus does not bind.
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.FeatureId"
id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_FeatureId" .../>
In the 3rd scenario, it is similar. It is looking for the WarranyFeaturesVm collection property, which it finds. It however looks for another collection item.
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].FeatureId"
id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__FeatureId" .../>
In order to bind correctly, your HTML has to look similar to your first HTML example:
<input type="hidden" value="68ba9241-c409-4f4b-96da-cce13b127c1e"
name="WarrantyFeaturesVm.index" .../>
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].FeatureId"
id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__FeatureId" .../>
Like I hinted in my comment, you can achieve this by putting the BeginCollectionItem and everything it wraps into a partial view. The partial view will then receive its own context, since your helpers will use the view's #Model property with the stongly-typed helpers like so: #Html.WidgetFor(m => m.PropertyName).
On the other hand, if you really need the collection to be rendered in the outer view, I don't see any problem using normal indexing (integer-based) with a for loop and without BeginCollectionItem.
Update
I dug up this old post from Phil Haack. An excerpt:
...by introducing an extra hidden input, you can allow for arbitrary
indices. In the example below, we provide a hidden input with the
.Index suffix for each item we need to bind to the list. The name of
each of these hidden inputs are the same, so as described earlier,
this will give the model binder a nice collection of indices to look
for when binding to the list.
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
BeginCollectionItem uses this indexing method to make sure the model binding happens. The only difference is it uses Guids instead of ints as the indexer. But you could manually set any indexer like in Phil's example above.

Mvc doesnt validate input type file

I am using strongly type view model for my view , Validation works for all the text fields but it doesnt work for fileupload , below is the code:
<div class="bg-content-inner">
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Create", "Track", FormMethod.Post, new { enctype = "multipart/form-data" }))
{ %>
<%: Html.ValidationSummary("Please Correct the errors and try again")%>
<table cellpadding="2" cellspacing="2" border="0">
<tr>
<td style="width:100px;">
<div class="editor-label">
<%: Html.LabelFor(model => model.Name) %>
</div>
</td>
<td colspan="2">
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Name, new { style = "width:300px;" })%>
<%: Html.ValidationMessageFor(model => model.Name,"Circuit Name Required") %>
</div>
</td>
</tr>
<tr>
<td>
Main Image
</td>
<td>
<div class="editor-field">
<input type="file" name="files" id="file1" style="color:White" />
<%:Html.ValidationMessageFor(model => model.ImageLarge,"Required") %>
</div>
</td>
</tr>
<tr>
<td>
Detail Image
</td>
<td>
<div class="editor-field">
<input type="file" name="files" id="file2" style="color:White" />
<%:Html.ValidationMessageFor(model => model.ImageSmall,"Required") %>
</div>
</td>
</tr>
<tr></table>
If you are using unobtrusive validation, the HtmlHelpers will insert some data-XXXX attributes to enable client-side validation... since MVC does not have a HtmlHelper for INPUT[FILE] and you have to manually insert the INPUT element... you can also add the data-XXXX attributes yourself... them and the client-side validation will work (well... at least in FF and Chrome... I have not tested it in others)... so...
replace:
<input type="file" name="files" id="file2" style="color:White" />
with:
<input type="file" name="files" id="file2" style="color:White" data-val="true" data-val-required="File is required" />
Hope it helps you.
I think the validation message is looking for ImageLarge and ImageSmall to validate against.
If you change the name and id attributes to match the model image names does it work? e.g
name="ImageLarge" id="ImageLarge"
You can't client-side validate an <input type="file" />; it must be POSTed to the server and the upload examined, there just isn't a way around this.
May be AjaxSubmit helps you.

Resources