ASP.NET MVC3 Dropdownlist never selects value in Edit view - asp.net-mvc-3

I have a function that fills creates dropdownlist in ASP.NET MVC.
public static MvcHtmlString countryDropDown(this HtmlHelper helper, string name, string optionLabel, object selectedValue)
{
XmlDocument doc = new XmlDocument();
doc.Load(HttpContext.Current.Server.MapPath("~/App_Data/countries.xml"));
StringBuilder b = new StringBuilder();
b.Append(string.Format("<select name=\"{0}\" id=\"{0}\">", name));
if (!string.IsNullOrEmpty(optionLabel))
b.Append(string.Format("<option value=\"\">{0}</option>", optionLabel));
foreach (XmlNode node in doc.SelectNodes("//country"))
{
string selected = string.Empty;
if (node.Attributes["name"].Value == selectedValue as string)
{
selected = "selected=\"selected\"";
}
b.Append(string.Format("<option value=\"{0}\" {2}>{1}</option>", node.Attributes["name"].Value, node.Attributes["name"].Value, selected));
}
b.Append("</select>");
return MvcHtmlString.Create( b.ToString());
}
I use this function in Create and Edit views as:
#Html.countryDropDown("Country"," ", ViewData["Country"])
It shows list of countries perfectly but the problem is that I never selects the saved value in Edit page.
How the code can be modified so that it can select the value in edit page.

Solved using
#Html.countryDropDown("Country"," ", ViewData.Eval("Country"))
instead of
#Html.countryDropDown("Country"," ", ViewData["Country"]

Related

Drop-down not working in ToSelectList extension method

I've got a nullable enum that, unlike others on the same page, doesn't work. I have an enum, Title whereby using the extension method will help to populate a drop-down list on the page. Here's what the ViewBag declaration looks like:
ViewBag.TitleList = EnumExtensions.ToSelectList<Title>("[select]");
Now, perhaps someone could explain it to me, but this is where the black magic happens when it comes to binding in MVC. If the page is invalid when calling if(ModelState.IsValid) then upon re-rendering the screen, the above statement is called again. However this time, the correct drop-down item will be selected (dependent on which one you had selected at the time).
Digging deeper, this is the method declarations:
public static SelectList ToSelectList<TEnum>(string nullEntry = null) where TEnum : struct
{
return ToSelectList<TEnum>(nullEntry, null);
}
public static SelectList ToSelectList<TEnum>(string nullEntry = null, string selectedValue = null) where TEnum : struct
{
var enumType = typeof(TEnum);
var values = Enum.GetValues(enumType).OfType<TEnum>();
List<SelectListItem> items = ToSelectList<TEnum>(values, nullEntry, selectedValue);
SelectList sl = new SelectList(items, "Value", "Text", selectedValue);
return sl;
}
public static List<SelectListItem> ToSelectList<T>(this IEnumerable<T> enumerable, string nullEntry, string selectedValue = null)
{
List<SelectListItem> items;
if ((typeof(T).IsEnum))
{
items = enumerable.Select(f => new SelectListItem()
{
Text = f.GetDescription(),
Value = f.ToString(),
Selected = f.ToString() == selectedValue
}).ToList();
}
else
{
items = enumerable.Select(f => new SelectListItem()
{
Text = f.ToString(),
Value = f.ToString()
}).ToList();
}
if (!string.IsNullOrEmpty(nullEntry))
{
items.Insert(0, new SelectListItem() { Text = nullEntry, Value = "" });
}
return items;
}
There's just some overloads to handle random cases, although presumably some of these won't be needed.
As I say, the correct item will be selected for other enumerations, but for this particular one, Title, it will not. Here's the enum declaration:
public enum Title
{
Mr,
Miss,
Mrs,
Ms
}
And finally, the declaration using DropDownListFor on the page itself;
#Html.DropDownListFor(x => x.Title, (SelectList)ViewBag.TitleList)
The problem is that when I first visit the page, the selected item is always "[select]" (when the provided enum value is null in the model). However, the model property Title definitely has a value set, and the SelectedItem property is set for the drop-down list too, but on screen, it defaults to "[select]" which is unexpected.
Any ideas?
Could it be because of the name Title? Try changing it to another name just to see.
Maybe you should try adding String.Empty so it the drop down list will default to a blank
#Html.DropDownListFor(x => x.Title, (SelectList)ViewBag.TitleList, String.Empty)

Passing parameter from view to controller when button is clicked

I have created my own extension as:
public static MvcHtmlString hSearch(this HtmlHelper helper, string labelName, string labelCaption, string textName, string textValue, string tableName, string buttonId,
string actionName, string controllerName, object routeValues, object htmlAttributes)
{
var textbuilder = new TagBuilder("input");
textbuilder.MergeAttribute("id", textName);
textbuilder.MergeAttribute("name", textName);
textbuilder.MergeAttribute("value", textValue);
textbuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
ModelMetadata metadata = ModelMetadata.FromStringExpression(labelName, helper.ViewData);
String innerText = labelCaption ?? (metadata.DisplayName ?? (metadata.PropertyName ?? labelName.Split('.').Last()));
if (String.IsNullOrEmpty(innerText))
{
return MvcHtmlString.Empty;
}
TagBuilder labelbuilder = new TagBuilder("label");
labelbuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(labelName)));
labelbuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
labelbuilder.SetInnerText(innerText);
//return new MvcHtmlString(textbuilder.ToString());
var buttonBuilder = new TagBuilder("button");
buttonBuilder.MergeAttribute("id", buttonId);
buttonBuilder.SetInnerText(buttonId);
var formBuilder = new TagBuilder("form");
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
formBuilder.Attributes.Add("action", urlHelper.Action(actionName, controllerName, routeValues));
formBuilder.Attributes.Add("method", "Post");
formBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
formBuilder.InnerHtml = labelbuilder.ToString() + textbuilder.ToString() + buttonBuilder.ToString();
return new MvcHtmlString(formBuilder.ToString());
}
I used the extensions in view as:
#Html.hSearch("lblSrch", "Company", "companyName", (string)TempData["cName"], "CHComp", "Search", "Fetch", "Home", null, null)
Now I want to pass tableName when I click the button to the controller.. my controller looks like this:
public ActionResult Fetch(string search, string tablename)
{
var c = cbo.fetchData(search, tablename);
return PartialView(c.ToList());
}
Waiting for reply.. Thanks..
You haven't given us the code for your helper, but at a guess it writes out a label, a text field (textName), and a button. If this is the case, it will post / get companyName=someValue via HTTP back to your controller.
You would typically need to add a FormCollection to your controller if the fields are dynamically sent from the view. Alternatively, why not keep the name of the text search input static, e.g. name="search", which will bind to your controller's parameter of the same name.
Edit
You can pass tableName back to the controller in a hidden field (<input type='hidden' name='tableName' value='{tableNameGoesHere}')
But as per above, your search string will have different names- the model binder isn't going to recognise it as string search.

MVC SelectList Drop down default

In the SelectList drop down, I like if the Count is 1, I like to default the value of what is there in the drugfamilylist which in this case is just 1 value. I cannot figure out how to do this.
I had the following below:
var drugfamilylist = (from dt in DataContext.Drugs
select dt.Drugvalue).Distinct().ToList();
if (drugfamilylist.Count == 1)
{
ViewBag.DrugFamily = new SelectList(drugfamilylist);
}
I tried but that idd not work either :
var drugfamilylist = (from dt in DataContext.Drugs
select dt.Drugvalue).Distinct().ToList();
if (drugfamilylist.Count == 1)
{
ViewBag.DrugFamily = new SelectList(drugfamilylist,drugfamilylist);
}
Typically I would have View strongly-typed to a ViewModel. The ViewModel would contain both the list of available items and the corresponding value of the currently selected element.
public class MyViewModel
{
public IList<string> DrugFamilyList;
public string SelectedDrugFamily;
}
In the controller you could then populate both the list of available items and the Selected items when the list of available items was a single element.
public ActionResult MyAction()
{
var vm = new MyViewModel();
vm.DrugFamilyList = (from dt in DataContext.Drugs
select dt.Drugvalue).Distinct().ToList();
vm.SelectedDrugFamily = (vm.DrugFamilyList.Count==1) ?
vm.DrugFamilyList[0] : null;
return View(vm);
}
And then use the HtmlHelper to build the select box:
#Html.DropDownListFor(m => m.SelectedDrugFamily, new SelectList(Model.DrugFamilyList, Model.SelectedDrugFamily))
However, if you didn't want to use the recommended ViewModel pattern, you could always accomplish the same thing with ViewBag.
ViewBag.DrugFamilyList = (from dt in DataContext.Drugs
select dt.Drugvalue).Distinct().ToList();
ViewBag.SelectedDrugFamily = (ViewBag.DrugFamilyList.Count==1) ?
ViewBag.DrugFamilyList[0] : null;
And then in the View use a similar helper:
#Html.DropDownList("desiredfieldname", new SelectList(ViewBag.DrugFamilyList, "", "", ViewBag.SelectedDrugFamily ))
This was written free-hand and not tested. I hope it helps.

Not show label for an empty property with LabelFor?

I'm using MVC3 w/ Razor and I have a model that has quite a few properties that are sometimes empty.
Other than a custom htmlHelper, or using an if/then in the view for every LabelFor/DisplayFor pair, is there a way to not display the LabelFor/DisplayFor for a property that is empty or null?
No.... You need the above mentioned solutions or additional view models. Sorry!
I created my own helper: LabelAndDisplayFor that checks for null/empty and then chooses to display the field.
public static MvcHtmlString LabelAndDisplayFor<tModel, tValue>(this HtmlHelper<tModel> html, System.Linq.Expressions.Expression<Func<tModel, tValue>> field,
bool hideIfEmpty = false) {
if (hideIfEmpty) {
var v = field.Compile()(html.ViewData.Model);
if (v == null || string.IsNullOrWhiteSpace(v.ToString())) {
return MvcHtmlString.Empty;
}
}
StringBuilder result = new StringBuilder();
result.Append("<div class='display-line'>");
result.Append("<div class='display-label'>");
result.Append(html.LabelFor(field));
result.Append("</div>");
// ... etc ...

ViewBag property value in DropDownListFor instead of Model property value

We found strange behaviour in DropDownListFor (ASP.NET MVC3 release). It selects ViewBag property value instead of Model property value in dropdown.
Model:
public class Country {
public string Name { get; set; }
}
public class User {
public Country Country { get; set; }
}
Controller Index action:
ViewBag.CountryList = new List<Country> { /* Dropdown collection */
new Country() { Name = "Danmark" },
new Country() { Name = "Russia" } };
var user = new User();
user.Country = new Country(){Name = "Russia"}; /* User value */
ViewBag.Country = new Country() { Name = "Danmark" }; /* It affects user */
return View(user);
View:
#Html.EditorFor(user => user.Country.Name)
#Html.DropDownListFor(user => user.Country.Name,
new SelectList(ViewBag.CountryList, "Name", "Name", Model.Country), "...")
It will show text box with "Russia" value and dropdown with "Danmark" value selected instead of "Russia".
I didn't find any documentation about this behaviour. Is this behaviour normal? And why is it normal? Because it is very hard to control ViewBag and Model properties names.
This sample MVC3 project sources
I'm not so sure why this decision was made, but it was happened because MVC framework tried to use the ViewData-supplied value before using the parameter-supplied value. That's why ViewBag.Country override parameter-supplied value Model.Country.
That was how it was written in MVC framework in the private method SelectInternal.
object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
// If we haven't already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.
if (!usedViewData) {
if (defaultValue == null) {
defaultValue = htmlHelper.ViewData.Eval(fullName);
}
}
if (defaultValue != null) {
IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
List<SelectListItem> newSelectList = new List<SelectListItem>();
foreach (SelectListItem item in selectList) {
item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
newSelectList.Add(item);
}
selectList = newSelectList;
}
This code defaultValue = htmlHelper.ViewData.Eval(fullName); tried to get the value from ViewData and if it can get the value, it will override the supplied parameter selectList with new list.
Hope it can help. Thanks.
side-node: ViewBag is just a dynamic wrapper class of ViewData.
The following line from your action method is what is confusing the code:
ViewBag.Country = new Country() { Name = "Danmark" }; /* It affects user */
That's because the html helpers look into a few different places to pick up values for the generated controls. In this case ViewData["Country"] is clashing with ModelState["Country"] Rename that property to something else and everything should work.

Resources