#Html.EnumDropDownListFor - Dropdown values not being set - c#

I have a basic form allowing users to input details which then gets posted and saved to a database - this works as expected without any issues:
#model R32.Register.Models.RegisterCar
#{
ViewBag.Title = "Edit Your R32";
}
<h2>Edit R32</h2>
<div>
#using (Html.BeginForm("UpdateCar", "Garage", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Enter details</legend>
<ol>
<li>
#Html.LabelFor(m => m.NumberPlate)
#Html.EditorFor(m => m.NumberPlate, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.NumberPlate)
</li>
<li>
#Html.LabelFor(m => m.Edition)
#Html.EnumDropDownListFor(m => m.Edition, "Select an edition:", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Edition)
</li>
<li>
#Html.LabelFor(m => m.Colour)
#Html.EnumDropDownListFor(m => m.Colour, "Select a colour:", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Colour)
</li>
</ol>
<input type="submit" value="Save Changes" />
</fieldset>
}
</div>
Model snippet:
[Required]
[Display(Name="Edition")]
public MkEnum? Edition { get; set; }
Enum:
public enum MkEnum
{
[Display(Name="Mk4")]
Mk4 = 1,
[Display(Name="Mk5")]
Mk5 = 2
}
The control renders as expected, with the Edition dropdownlist having three values: "Select an edition", "Mk4", and "Mk5".
The user is able to select an edition, control is validated, then posted to the controller.
The Post is successful, and all selected values are sent to the controller - the app then persists the data in a database, and so on, without any problems.
The issue is when I pass this model back into the same View to allow the user to edit the saved data, the saved values for the enums are NOT being set as the selected value in the dropdownlist.
I can confirm that any saved string values, such as NumberPlate in this example, are being passed back into the view and loaded into the UI.
Putting a breakpoint on the viewmodel as it renders I can confirm that my #model contains the saved values for enum properties - Edition for example - but the end result is that the "Select an edition:" dropdown list is rendered containing the expected dropdown values, but it's value is the default "Select an edition:" instead of the actual value passed in via. m.Edition.
I have been able to get this working using DropDownListFor - but am having difficulties in understanding why this is not working using EnumDropDownListFor, as this clearly seems like a more elegant solution.
Does anyone have any help/advice for this?

I just ran into this problem myself. This happens because fields of type enum are being passed back to the browser serialized as their enum names, but #Html.EnumDropDownListFor generates its option values as integers. The browser can't match up the two so the dropdown stays at its default selection.
There are 3 ways to get around this.
Get the view model's enum field to serialize properly as an int.
Write a dropdown generator that uses enum names as option values.
Use javascript to manually select the option (includes razor syntax here)
$("#YourDropdownID option").each(function () {
if ($(this).html() == '#(Html.DisplayFor(o => o.YourEnumFieldName))') {
$(this).attr("selected", "selected");
return;
}
});

Ok, so from what I could see the problem was caused by using an ActionLink to pass back the full model of an item being edited. Everything was being sent back in a Query string, so my Enum values were being passed to the controller in the following way: mkEnum=Mk4.
I was then loading the UpdateCar view as seen above in my example - but the query string values were being persisted in the call back to the View.
EnumDropDownListFor is unable to interpret/convert the text value of enums into their actual values - if I manually edited the Query string to mkEnum=1, then the correct value wasloaded into the ViewModel.
In addition to this problem, it was not a good solution passing the full model back to the controller.
I've modified the code to pass back a single Id of the item being edited - the controller then verifies the user has access to that Id, retrieves the Model from the Database then passes it back to the same View as in my above example.
With this change my dropdowns are now being updated with their values without any issues.
TLDR; If you experience this issue check to make sure you don't have model properties, specifically enum values represented by their string values, in a query string when loading your view and using EnumDropDownListFor.

Related

Default value of DropDownList in an Html.BeginForm block

While a search of "Default value of DropDownList" produces results, this is not a duplicate question because the specific issue is not listed in the examples and answers I have searched.
Here is the main question first, followed by supporting background and details:
When I place a DropdownList in an Html.BeginForm block, how can I have the default DropDownList values be an item other than the first item in the list?
What I have done so far:
SO, Microsoft Virtual Academy, and a general Internet Search using various search terms, with no effective answers that solve this specific issue.
Looking at the overloads of Html.DropDownList on MSDN. The optionLabel parameter inserts an item at the very top of the list, such as "Select an Item", which is not desired behavior.
jQuery, which does work as intended, but I'm really hoping there is a much simpler way that doesn't involve jQuery.
My Working Theory: The method I learned (and shown below) does not allow for default values and it is not a case of not knowing a particular overload of Html.DropDownList.
Background Info:
In the process of learning MVC 5 and gathering instructions from tutorials and SO answers, I learned this style of creating a DropDownList that is placed within a Html.BeginForm() block in the View. It works, if I want the default value to be the first item in the list or if I want to add an item inserted at the top that says "select an item."
However, there are times when it is meaningful to for the default value to be other than the first in the list.
In the controller, the defaults of the parameter are set Index(string campus = "MRA", string fy = "FY16"), and the query returns the correct result, but DropDownLists are not set accordingly when loading the page for the very first time.
Controller
public ActionResult Index(string campus = "MRA", string fy = "FY16")
{
/* The ViewBags feed DropDownLists used to filter the query */
ViewBag.CampusList = new List<string> { "CRA","DRA","MRA","PRA"};
ViewBag.FyList = new List<string> {"FY15","FY16" };
IEnumerable<AssociatedW3SuspensionOrProbation> query =
db.AssociatedW3SuspensionOrProbation
.Where(m=>m.Campus==campus).Where(m=>m.FY==fy)
.OrderBy(m=>m.StudentName).ThenBy(m=>m.LatestEntryDate);
return View(query.ToList());
}
View
The dropdowns function correctly: when the form is submitted, the query results are chosen by the selected dropdown values and the dropdowns load with the selected values.
#using (Html.BeginForm())
{
<div class="panel panel-default">
<div class="panel-body">
<p>
<strong>Campus</strong>: #Html.DropDownList("campus",
new SelectList(ViewBag.CampusList)) ||
<strong>FY</strong>: #Html.DropDownList("fy",
new SelectList(ViewBag.FyList))
</p>
<div><input type="submit" value="Search" /></div>
</div>
</div>
}
Is there a simple answer to this problem, or does this require a totally different approach?
What you have to do is create SelectList in the controller action and there is a constructor overload which can be used to set selected value, but for that you would need a List<T> which is not string but a custom type that contains 2 properties 1 for TextField and 1 for ValueField.
You have to use second Constructor overload listed here
Here is the example code:
ViewBag.CampusList = new SelectList(
new List<string> { "CRA","DRA","MRA","PRA"},
campus // selected value
);
ViewBag.FyList = new SelectList(
new List<string> {"FY15","FY16" },
,fy // selected value
);
and in your view:
#Html.DropDownList("campus",
ViewBag.CampusList as SelectList)
#Html.DropDownList("fy",
ViewBag.FyList as SelectList)

Add prefix to control id and still have it bind MVC Razor

I have a case where I have a page displaying an order and tabs that display the order details. The order details are quite complex and since they are the same layout, I want to use a partial view or editor template that will generate the form.
The problem is the result is multiple duplicate form input id's are generated (one for each order detail. For example, I have:
foreach (var orderDetail in Model.OrderDetils)
{
#Html.EditorFor(model => orderDetail, "WorkOrder", orderDetail)
}
I've read much about this and see solutions where it is recommended to use an editortemplate, but that solution only works when you have the same form to render, but passing it different model properties so the control id's prefixes will differ...ie. like this solution.
In my case, this won't work as the model property I am passing is always the same.
So how else can I create unique Id's in the partial or editor template that will also bind.
I know instead of:
#Html.EditorFor(model => model.WOHdr.Attribute1)
I could do:
#Html.TextBoxFor(model => model.WOHdr.Attribute1, new { id = Model.Id + "_Attribute1" })
But then it won't bind when it passes to the controller.
Thoughts?
Try this
#Html.TextBoxFor(model => model.WOHdr.Attribute1, new { #id = #Model.Id + "_Attribute1" })
Use "#"+dynamic value. Now You will get unique Id's
In EditorFor you can use like this
#Html.EditorFor(model => model.WOHdr.Attribute1, null, "id=" + #Model.Id + "" )
the id will generate like this
id="id_1", id="id_2" and so on..
<input id="Checkbox1_#(test.TestId)" type="checkbox" />
i hope upper code will help you

DropDownListFor Unobtrusive validation shows on empty form

I get validation message always when I open this page (even first time), even if I choose value in one of dropdown lists, message doesn't go away. If I choose values in both I can submit form but messages still doesn't go away.
Snippet is Linq to sql class and LanguageID and SnippetTypeID are ints, I assume this happens because I pass empty model to View so LanguageID and SnippetTypeID are null and AFAIK Linq to Sql classes have required on non-nullable ints.
How can I fix this so validation messages doesn't appear before user tries to submit form, and if one of dropdown lists get selected to remove validation message.
View
#model Data.Snippet
#using (Html.BeginForm("Save", "Submit", FormMethod.Post))
{
<h1 class="subtitle">Submit new snippet</h1>
<h4>Title</h4>
#Html.TextBoxFor(snippet => snippet.Title, new { #class = "form-field" })
<h4>Language</h4>
#Html.DropDownListFor(snippet => snippet.LanguageID, new SelectList(#ViewBag.Input.Languages, "ID", "Name", #Model.LanguageID), "Choose Language", new { #class = "form-select" })
<p>#Html.ValidationMessageFor(snippet => snippet.LanguageID , "You must choose language", new { #class= "validation-message"})</p>
<h4>Snipet type</h4>
#Html.DropDownListFor(snippet => snippet.SnippetTypeID, new SelectList(#ViewBag.Input.SnippetTypes, "ID", "Name", #Model.SnippetType), "Choose snippet type", new { #class = "form-select" })
<p>#Html.ValidationMessageFor(snippet => snippet.SnippetTypeID,"You must choose snippet type", new { #class= "validation-message"})</p>
<h4>Text</h4>
#Html.TextAreaFor(snippet => snippet.Text, new { cols = "20", rows = "10", #class = "form-field" })
<input type="submit" value="Submit Snippet" />
}
Controller
//Controllers are not finished Save() should have
//code to actually insert to db after I fix validation
// GET: /Submit/
//
public ActionResult Index()
{
Snippet model = new Snippet();
SubmitModel input = new SubmitModel();
ViewBag.Input = input;
return View(model);
}
public ActionResult Save(Snippet snippet)
{
return View();
}
Model
Model is Linq to Sql class.
Snippet
ID (int, identifier)
Title (string)
SnippetType (int, FK on table SnippetTypes)
LanguageID (int, FK on table Languages)
Text (string)
Alright,
So I think the reason it is failing is because of the custom CSS that you are adding. ValidationMessageFor will put a hidden class when the validation is successful.
if you want to add custom colors or something like that with you CSS i would consider applying the style to the wrapping p tag or adding a wrapping div/span and adding it to that.
You could probably define your messages on the view using only #Html.ValidationMessageFor(snippet => snippet.SnippetTypeID, "ErrorMessage"); However a more proper way is to take your Model and create Data Annotations for it.
Take a look at this article http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validation-with-the-data-annotation-validators-cs for more information about how to do model validation with Data Annotations.
Also I would consider passing in custom classes instead of your linq to sql class so that you can do custom validation based on the view. These custom classes are often refereed to as ViewModels.

Editing Date Time Member Variables with a DropDownListFor doesn't save

So, I am trying to create a DropDownList that will edit the various member variables the DateTime class has (which I have an instance of in my model), such as Day, Month, and Year. However, when an item is selected in the DropDownList and the Save input button is clicked, the data does not save. All other edited pieces of data will be changed and saved, but the DateTime field will just not update. I'd rather not make a new model just for my Dates, but it can be done. I can create the SelectList, I do so in an HTML Helper, shown below:
namespace ErrorReport.Helpers
{
public class DateList
{
public static IEnumerable<SelectListItem> DayList
{
get
{
var days = new List<SelectListItem>();
for (int i = 1; i < 32; i++)
{
days.Add(new SelectListItem
{
Value = i.ToString(),
Text = i.ToString()
});
}
return days;
}
}
There's obviously two more Lists that get made, one for Year and one for Month (I'm not bothering with hours, minutes, or seconds), didn't show them since the code is identical. In my View, my editor code looks like the below, and the variable I want to change is CmpD (of DateTime class):
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Edit Report: "#Model.Title"</legend>
#Html.HiddenFor(model => model.ReportId)
#Html.HiddenFor(model => model.SbmD)
#Html.HiddenFor(model => model.UserName)
#Html.HiddenFor(model => model.CmpD)
...irrelevant editor code...
<div class="editor-label">
#Html.LabelFor(model => model.CmpD, "Estimated Completion Date:")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CmpD.Month, DateList.MonthList)
#Html.DropDownListFor(model => model.CmpD.Day, DateList.DayList)
#Html.DropDownListFor(model => model.CmpD.Year, DateList.YearList)
//#Html.ValidationMessageFor(model => model.CmpD.Month)
//Above line was commented out
</div>
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
}
I did have validation in place for CmpD's member variables earlier (only the ones I wanted to change), commented them out because they kept throwing validation errors for every possible SelectList choice (marked in above code). I didn't put arguments in the BeginForm function because they caused save problems with saving the other data. I also added the HiddenFor field for CmpD (which is my DateTime) to get everything to save properly, since without that line of code the return controller did not recognize the Model as valid and didn't save it. I tried adding a HiddenFor field for every member variable in the DateTime class I am not using, and I still get Validation errors if I remove the HiddenFor(model => model.CmpD), even with other Hidden Fields present. I have also tried to make a list this way:
public static IEnumerable<int> YearList
{
get
{
var years = new List<int>();
for (int i = 0; i < 4; i++)
{
years.Add(i);
}
return years.AsEnumerable();
}
}
However, this prevents the Html.DropDownListFor function from working at all, it apparently has to use SelectList items. I have noticed that only strings can be SelectList items, and that the DateTime member variables are ints. Is that causing the problem? Because I don't see where they are recasted to ints and cannot figure out how I would do that. Basically, how do I edit DateTime member variables in a DropDownList?
Also: Is this too much inline code? Thanks in advance!
The DateTime properties (Day, Month, etc.) are all read-only. Hence, it is not possible to set them on a DateTime instance.
You would need settable int properties in your model to be able to update the values from the drop-downs. Then you can construct a DateTime from those values later. I would probably create a separate model class just for this purpose and implement the date editor as separate editor template or partial view that handles all the details (to keep your code clean).

Reloading an ASP.NET MVC3 Partial View without using AJAX

I have an MVC3 application with Razor and I created a View that inside renders a Partial View. This is how the main View looks like:
#{Html.RenderPartial("_SearchFilters", Model.SearchFilters);}
#* Other HTML elements *#
Inside the _SearchFilters Partial View I have the following DropDownLists inside a Form element:
Choose Year
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Choose Month
#Html.DropDownListFor(m => m.Month, new SelectList(Model.MonthsList, "Value", "Text"), Model.Month.ToString(), new { #disabled = "disabled" })
<input type="submit" value="Display" />
I would like that upon Submit the two DropDownLists keep their status, namely the value selected by the user, when the View is reloaded with the filtered data.
Is there any way to do it without using AJAX?
UPDATE
The ViewModel is as follows:
public class TableSearchFiltersViewModel
{
public bool YTM { get; set; }
public int? Month { get; set; }
public int? Year { get; set; }
public IEnumerable<SelectListItem> YearsList
{
get
{
return Enumerable.Range(2011, (DateTime.Now.Year - 2011 + 4)).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString(),
}).OrderBy(m => m.Value);
}
}
public IEnumerable<SelectListItem> MonthsList
{
get
{
return Enumerable.Empty<SelectListItem>();
}
}
}
Thanks
Francesco
When you submit the form to the corresponding controller action, this action should take as input parameter some view model. This view model's properties will be bound from the input fields contained in the form including the selected value of the two dropdowns. Then the controller action could return the same view which will preserve the selected values of the dropdown boxes.
I would recommend you to use Editor Templates though instead of rendering partials as this will ensure proper naming of the dropdowns and eventually preserve selected values:
#Html.EditorFor(x => x.SearchFilters)
I don't have IDE at this time so couldn't test but this might work:
Choose Month
EDIT:
#Html.DropDownListFor(m => m.Month,
Model.MonthsList.Select(
t => new SelectListItem {
Text = t.Name,
Value = t.Value,
Selected = t.Value == Model.Month,
},
Model.Month.ToString(), new { #disabled = "disabled" })
Without ajax not, or you will have to repost the whole form. MVC is a web framework which is not dynamic like a winforms application. You will have to post the changes to your controller and reload the page with the necessary changes, or use ajax to reload these changes.
You could provide the default values for Year and Month properties (to be selected at the first request) and bind those instead of the hardcoded startup values you provided.
So instead of:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Which btw seems erroneous, as selected value (which I suppose DateTime.Now.Year is in your example) should be provided as SelectList's constructor (instead of DropDownListFor method's) argument. DropDownListFor method doesn't have a 'selected value' argument.
You could write:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text", Model.Year))
and analogously in the second dropdown.
This will make dropdowns keeps the selected values when rendered using the posted model (as Model.Year and Model.Month would hold those). So you should make sure those values won't get overwritten with default ones after subsequent submits.

Categories