MVC 3 EditorTemplate - Model properties are empty - asp.net-mvc-3

I have a number of custom EditorTemplates for various model classes. Inside these templates I obviously need to reference the properties of the model. My problem is that when I use the direct syntax of Model.Id (for example), the value is null. Another example is Model.Name which returns an empty string. However, when I reference the model in an expression (eg. #Html.TextBoxFor(i => i.Name)) then the values are there.
To further illustrate, here is a code snippet:
#model Vigilaris.Booking.Services.CompanyDTO
<div>
<fieldset class="editfieldset">
<legend class="titlelegend">Company Details</legend>
<ol>
<li>
#Html.TextBox("tb1", #Model.Id)
#Html.TextBox("tb2", #Model.Name)
</li>
<li>
#Html.LabelFor(i => i.CreatedDate)
#Html.DisplayFor(i => i.CreatedDate)
</li>
<li>
#Html.LabelFor(i => i.Name)
#Html.TextBoxFor(i => i.Name)
</li>
<li>
#Html.LabelFor(i => i.Description)
#Html.TextAreaFor(i => i.Description)
</li>
<li>
#Html.LabelFor(i => i.Phone)
#Html.TextBoxFor(i => i.Phone)
</li>
</ol>
</fieldset>
</div>
In this example, all the code that is using the LabelFor and DisplayFor helper functions displays the data from the model. However, the Html.TextBox code portion returns 0 for Model.Id and empty string for Name.
Why does it not access the actual model data when I reference Model directly?

I am unable to reproduce this. You might need to provide more context (controllers, views, ...). Also shouldn't your textbox be named like this:
#Html.TextBox("Id", Model.Id)
#Html.TextBox("Name", Model.Name)
and also why not using the strongly typed version directly:
#Html.TextBoxFor(x => x.Id)
#Html.TextBox(x => x.Name)

I managed to figure this one out. One thing I left out in my problem description was that I am using Telerik MVC Grid extension and the EditorTemplate is being using for In-form editing. So, the Model properties are not available at this point and this is understandable behaviour. I had to use a client side onEdit event on the Telerik MVC Grid and then set these values as necessary.
How I remember solving this is that I added a ClientEvent in my Telerik MVC Grid as follows:
.ClientEvents(events => events.OnEdit("Users_onEdit"))
This tells the grid to run my javascript function called Users_onEdit when an edit is triggered. Then, in my javascript function I find the field I want and then set its value. Here is an code excerpt:
function Users_onEdit(e) {
if (e.mode == "insert") {
$("#UserName").removeAttr("readonly");
$("#UserName").removeAttr("title");
$("#divNewUserMessage").show();
var formId = String(e.form.id);
var formIndex = formId.indexOf("form");
var companyId = formId.substr(6, formIndex -6);
var hiddenCompanyId = $(e.form).find("#CompanyId");
hiddenCompanyId.val(companyId);
}
}
I hope this helps others out there.

Related

Custom Client Template Telerik Grid

I'm kind of new using Telerik Grid, basically what I'm trying to accomplish here is the following scenario, I have a form that have some fields and via ajax calls another action on my controller to generate the model to a partial view that have the following grid, my problem is that I need to create some actions depending on some business logic, I know I can do that using a column template, the thing is that since im using ajax binding it "loose" my template, looking around on internet I found that you can do that using a js function go generate the links, my question is isn't that kind of messy? im duplicating the same business logic on the server and on the client, there's must be a better way to accomplish this?
Html.Telerik().Grid(Model)
.Name("Grid")
.DataBinding(binding => binding.Ajax().OperationMode(GridOperationMode.Client))
.Columns(column =>
{
column.Bound(c => c.Id).Title("Id").Width(30);
column.Bound(c => c.Status);
column.Bound(c => c.DateReg);
column.Template(
#<text>
<div class="ActionsProvGrid">
<a href="#Url.Action("SomeAction", "Controller", new {id = item.id})">
<img src="../../Content/icons/ViewMore.png" alt="ViewMore" />
</a>
#if (#item.Status.Equals("ACT"))
{
<a href="#Url.Action("SomeOtherAction", "Controller", new {idOportunidad = item.id})">
<img src="../../Content/icons/invoice.png" alt="invoice"/>
</a>
}
</div>
</text>
).ClientTemplate("<#= GenearteIcons(data) #>");
})
.Sortable()
As a solution you can define property on your model that encapsulates the result of your business logic:
public bool DoesStatusEqualToAct {
get
{
return (code that determines if it's true);
}
}
And in JavaScript function that generates a link html you can access this property:
function GenerateIcons(data){
var html = '';
if(data.DoesStatusEqualToAct){
html = 'version 1';
}
else{
html = 'version 2';
}
return html;
}

unobtrusive validation RANGE does not work

I am doing an MVC 3 application. I have a model with [Range(1, 175, ErrorMessage="Invalid")].
On one of the controllers the view renders perfectly with all markup for validation. On a second Controller with the same setup the Range validation which is done on a dropdownlist, does not appear on the html markup. I have validation and unostrusiveValidation true on config.web. I am using LINQTOSQL and I have done a partial class to add the additional metadata. The field does pick up the [Display(Name="State")], but the Range is not.
<tr>
<td>#Html.LabelFor(x => x.carta.INVprovincia)</td>
<td>#Html.DropDownListFor(x => x.carta.INVprovincia, Model.provinciaItems, new { #class = "ddlsmall" }) <br /> #Html.ValidationMessageFor(x => x.carta.INVprovincia)</td>
</tr>
<tr>
<td>#Html.LabelFor(x => x.carta.INVmunicipio)</td>
<td>#Html.DropDownListFor(x => x.carta.INVmunicipio, Model.municipiosItems, new { #class = "ddlsmall" }) <br /> #Html.ValidationMessageFor(x => x.carta.INVmunicipio)</td>
</tr>
The Html.XXX helpers won't generate HTML5 data-* attributes used by the unobtrusive validation framework if they are not inside a form which seems to be your case. I guess that the form is contained within a parent view. This bug (IMHO) is fixed in ASP.NET MVC 4. A possible workaround is to put the following on the to of your partial view to fake a form and make the helpers believe that they are inside a form:
#{
ViewContext.FormContext = new FormContext();
}

Passing a selected value from a partial view to the main view's viewmodel

As ASP.Net MVC3 newbies we have an issue that would like assistance with. (Questions at the bottom of this thread too.)
First of all I am not sure if the following is the best way to go about this so please let me know if we are heading in the wrong direction. We would like to use partial views to do lookups for dropdown lists. In some cases the lookups will be done in multiple places and also, the data is not part of our viewmodel. The data may be coming from a Database or Web Service in our application. Some of the data is loaded at startup and some is based upon other values selected in the form.
We are calling a child action from our main view and returning a partial view with the data we obtained. Once the user selects their choice we are not sure how to store the selected item code in our main view model.
In our main form we call to an action:
#model Apps.Model.ViewModels.AVMApplicationInfo
...
<div class="editor-label">
#Html.LabelFor(m => m.VMResidencyWTCS.DisplayState)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.VMResidencyWTCS.DisplayState)
#Html.DropDownListFor(m => m.VMResidencyWTCS.DisplayState, Apps.Model.Helpers.ResidencyStates.StateList)
#Html.ValidationMessageFor(m => m.VMResidencyWTCS.DisplayState)
</div>
#Html.Action("DisplayCounties", "PersonalInfo")
...
In the PersonalInfo controller:
[ChildActionOnly]
public ActionResult DisplayCounties()
{
IList<County> countiesDB = _db.Counties
.OrderBy(r => r.CountyDescr)
.Where(r => r.State == "WI"
&& r.Country == "USA")
.ToList();
//Create an instance of the county partial view model
VMCounty countyView = new VMCounty();
//Assign the available counties to the view model
countyView.AvailableCounties = new SelectList(countiesDB, "CountyCd", "CountyDescr");
return PartialView("_DisplayCounties", countyView);
}
In the _DisplayCounties partial view:
#model Apps.Model.ViewModels.VMCounty
<div class="editor-label">
#Html.LabelFor(m => m.CountyDescr)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedCountyCd, Model.AvailableCounties)
</div>
How do I assign the SelectedCountyCd to a field in the main form view model (Apps.Model.ViewModels.AVMApplicationInfo )? Are there any issues of when the child action/partial view is called; i.e., is it loaded at start up and can this method be used to include a user choice as a filter for the lookup? If so, how could the value be passed to the child controller; viewbag?
You could pass it as parameter to the child action:
#model Apps.Model.ViewModels.AVMApplicationInfo
...
#Html.Action("DisplayCounties", "PersonalInfo", new {
selectedCountyCd = Model.CountyCd // or whatever the property is called
})
and then have the child action take this value as parameter:
[ChildActionOnly]
public ActionResult DisplayCounties(string selectedCountyCd)
{
IList<County> countiesDB = _db.Counties
.OrderBy(r => r.CountyDescr)
.Where(r => r.State == "WI"
&& r.Country == "USA")
.ToList();
//Create an instance of the county partial view model
VMCounty countyView = new VMCounty();
//Assign the available counties to the view model
countyView.AvailableCounties = new SelectList(countiesDB, "CountyCd", "CountyDescr");
// assign the selected value to the one passed as parameter from the main view
countyView.SelectedCountyCd = selectedCountyCd;
return PartialView("_DisplayCounties", countyView);
}

Nested edit templates in mvc razor

I have seen many versions of this question but the answers always turn into "you don't need to do that" and never an answer.
I have a list of attributes about a product that I want to show in an unordered list with checkboxes to select particular attributes.
In My Model:
public List<ProductAttribute> ProductAttributes {get;set;}
in my Create.cshtml:
<div Class="ProductAttributes">
#Html.EditorFor(m => m.ProductAttributes, "ProductAttributeSelectorList")
</div>
In my ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
And finally, in my EditLocationAttributeList.cshtml
#model Models.DisplayLocationAttribute
#Html.HiddenFor(m => m.Id)
#Html.CheckBoxFor(m => m.IsSelected)
<a href="#" alt="#Model.Description" >#Model.Name</a>
This all displays on the page perfectly I can style it like I want with CSS, but when the submit returns, my model.ProductAttributes collection is null.
I know I can bind directly to the EditLocationAttributeList and it will display and return a populated model.ProductAttributes if I use this:
#Html.EditorFor(m => m.ProductAttributes, "EditLocationAttributeList")
but now I do not have the unordered list that I would like to have. I could treat the template like an Item Template and have the line item tags embeded in that template but that seems smelly to have a template that is tightly coupled to another template.
Any Ideas?
Thanks in advance,
Tal
model.ProductAttributes is null, because the DefaultModelBinder is not able to reference each DisplayLocationAttribute back to the ProductAttribute property of your model. The simplest solution is to name your list elements as an array, so that for example each IsSelected element is named in the style ProductAttributes[n].IsSelected.
Add the following to ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
#{
var i = 0;
}
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "ProductAttributes[" +
i.ToString() + "]";
i++;
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
#{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
This will give you an indexed array, which the DefaultModelBinder will be able to associate to ProductAttributes. However, it builds a hard dependency to the name ProductAttributes. You can get around the hard dependency by several methods, such as passing the property name in the ViewBag.

ASP.NET MVC 3 - Validation Question

Good evening everyone I have a question regarding validation of drop-down list values. I have a view that is bound to a view model type called ReservationData.
This object contains a property CustomerVehicles of type List<VehicleData>. VehicleData has two int properties VehicleMakeId and VehicleModelId.
On my view I am trying to loop over the number of items in the CustomerVehicles collection and displaying two dropdowns for each, a vehicle make dropdown and a vehicle model dropdown using DropDownListFor.
When I try to submit and validate I do not see any validation errors displayed on the screen.
Just in case you are wondering I have added a ValidationMessageFor for each dropdown as well. I am not sure if this is an issue with the structure of my view model and its complexity and how the controls need to be named or how the ids need to be set. Any help would be greatly appreciated.
Here is the code for the looping over the collection:
#for (var i = 0; i < Model.CustomerVehicles.Count(); i++)
{
var vehicleNumber = i + 1;
<div class="vehicle-selection-wrapper">
<div class="content-container">
<h3>
Vehicle #vehicleNumber</h3>
<img class="vehicle-image" alt="manufacturer image" src="#Url.Content("~/Content/images/default-vehicle.gif")" /><br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleMakeId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })<br />
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleMakeId)<br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleModelId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleModelId
, new SelectList(new List<CWR.Domain.VehicleModel>(), "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleModelId)
</div>
</div>
}
Ok so I also noticed that in the generated HTML the selects that are generated are missing the HTML5 data-val attributes that are associated to elements to handle validation. Here is the generated HTML
<select class="long-field" id="CustomerVehicles_0__VehicleMakeId" name="CustomerVehicles[0].VehicleMakeId"><option value="">-- Select --</option>
</select><br />
<span class="field-validation-valid" data-valmsg- for="CustomerVehicles[0].VehicleMakeId" data-valmsg-replace="true"></span><br />
<label for="CustomerVehicles_0__VehicleModelId">Model</label>
<select class="long-field" id="CustomerVehicles_0__VehicleModelId" name="CustomerVehicles[0].VehicleModelId"><option value="">-- Select --</option>
</select>
<span class="field-validation-valid" data-valmsg-for="CustomerVehicles[0].VehicleModelId" data-valmsg-replace="true"></span>
Additionally in my VehicleData class the VehicleMakeId and VehicleModelId properties are decorated with a Required attribute.
UPDATE:
Ok so I was testing and noticed that if I keep my code identical except I swap the Html.DropdownListFor calls with Html.TextboxFor calls then the validation works. What could be causing this? Could it be a framework bug with the unobtrusive validation?
UPDATE: Contains Fix
So after posting this same question on the ASP.NET Forums, I was able to get a solution. In the post you will be able to see that there is a bug in the unobtrusive validation framework and how it handles validation of dropdownlists. The user counsellorben does a good job in explaining the problem as well as a solution (including sample code) that will assist others in avoiding this issue in the future, or at least until Microsoft builds in a fix in to the framework.
Thank you everyone for your assistance.
I too have come across this obviously massive oversight regarding client side validation with dropdownlists in MVC 3 and the best solution I can offer is to put the missing HMTL attributes in yourself.
In your view model create a property like this.
public Dictionary<string, object> CustomerVechicleAttributes
{
get
{
Dictionary<string, object> d = new Dictionary<string, object>();
d.Add("data-val", "true");
d.Add("data-val-required", "Please select a Vechicle.");
return d;
}
}
Then in your code, enter
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption,
**Model.CustomerVechicleAttributes** })
Just add the Model.CustomerVechicleAttributes as htmlAttributes to your dropdownlist.
This will inject the necessary attributes that are missing. You will of course need to add any other attributes you may need like your class attribute.
Hope this helps.
This is the simpliest way I found to do it, just adding data-val-*-* attributes in HtmlAttributes of DropDownListFor, inside the view. The following method works with RemoteValidation too, if you do not need remote validation, simply remove the elements containing data-val-remote-*:
#Html.DropDownListFor(m => m.yourlistID, (IEnumerable<SelectListItem>)ViewBag.YourListID, String.Empty,
new Dictionary<string, object>() { { "data-val", "true" },
{ "data-val-remote-url", "/Validation/yourremoteval" },
{ "data-val-remote-type", "POST" }, { "data-val-remote-additionalfield", "youradditionalfieldtovalidate" } })
I hope it may help. Best Regards!
you should try to add data annotations on your view model properties first so you could see the validation messages.
you might find what you need here
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
or create custom ones if needed.
what exactly do you need to validate?
I had exactly the same problem with the field getting correctly validated in TextBoxFor but not in DropDownListFor.
#Html.DropDownListFor(m => m.PaymentTO.CreditCardType, Model.CreditCardTypeList, "Select Card Type", new { style = "width:150px;" })
Since I had another DropDownListFor working on the same page, I knew that it wasn’t a generic DropDownListFor problem. I also have a complex model and parent object PaymentTO wasn’t initialized. When I set viewTO.PaymentTO = new PaymentTO(); in the Controller, the validation for the DropDownListFor started to work. So there is probably a problem with DropDownListFor, but the fix can be as simple as initializing the object in the controller.

Resources