Can I send a SelectList through the UIHint Control Parameters? - asp.net-mvc

Can I send a SelectList through a Data Annotation? Something like...
[UIHint("DropDownList", "", new SelectList(new[] {"one","two","three"}))]
public virtual int? OptionID { get; set; }
I don't understand the syntax but this seems possible. If so, how do I access it from an editor template?
If not, how could I dynamically send a SelectList to a DropDownList Editor Template? I specifically would like to avoid making a separate template for every SelectList - I have too many of them. Thanks
EDIT: I'm working on the second option (Reflection) because I thought it might be more direct than overriding that 15-syllable monster, the DataAnnotationsModelMetadataProvider.

You might want to take a look at this blog post:
http://mikevdm.com/BlogEntry/Key/Using-UIHint-With-ControlParameters-in-MVC
Here's a relevant quote:
ControlParameters are limited in that they can only accept (compile-time) constant values, in a somewhat unusual syntax, but they do allow simple vales (true/false, enumerated values, etc.), so your templated helper can behave slightly differently based on parameters. This allows you to combine related functionality, usually found in individual templates (with lots of code duplication), into one single template.
In my app, all my drop down lists were either nullable bools (Yes, No, not set) or enums. I took the route of making a separate template for each one, but using helper methods to make the code within each template very minimal.
For example, I have a template called Level (where Level is an enum):
The code is just a couple usings plus....
#Html.DropDownListFor(model => model, Model.ToSelectList<Level>())
I use these extension methods for ToSelectList:
public static SelectList ToSelectList<TEnum>(this TEnum? value) where TEnum : struct
{
var items = GetSelectListItems<TEnum>().ToList();
items.Insert(0, new SelectListItem { Value = "", Text = LabelIfNull });
return new SelectList(items, "Value", "Text", value.ToString());
}
public static SelectList ToSelectList<TEnum>(this TEnum value)
{
var items = GetSelectListItems<TEnum>();
return new SelectList(items, "Value", "Text", value.ToString());
}
public static IEnumerable<SelectListItem> GetSelectListItems<TEnum>()
{
var values = System.Enum.GetNames(typeof(TEnum));
return values.Select(v => new SelectListItem { Value = v, Text = v.ToFriendlyName() });
}
public static SelectList ToSelectList(this bool isTrue)
{
var items = new[]
{
new SelectListItem { Value = "true", Text = LabelIfTrue },
new SelectListItem { Value = "false", Text = LabelIfFalse }
};
return new SelectList(items, "Value", "Text", isTrue.ToString());
}
public static SelectList ToSelectList(this bool? isTrue)
{
var items = new[]
{
new SelectListItem { Value = string.Empty, Text = LabelIfNull },
new SelectListItem { Value = "true", Text = LabelIfTrue },
new SelectListItem { Value = "false", Text = LabelIfFalse }
};
return new SelectList(items, "Value", "Text", !isTrue.HasValue ? string.Empty : isTrue.Value.ToString());
}
Perhaps you could use control parameters to get down to a single template, but you'll definitely need to write your own DataAnnotationsModelMetadataProvider, as indicated in the blog post.

Related

Using Html.DropDownListFor as part of a paging solution in a razor view

I've spent hours on this, and I'm obviously missing something. Code extracts below are from a cut-down version of my actual app.
My viewmodel contains this:
public IEnumerable<SelectListItem> DisplayPageOptions { get; set; }
public string SelectedPageOption { get; set; }
private static SelectListItem item1 = new SelectListItem() { Text = "25", Value = "25" };
private static SelectListItem item2 = new SelectListItem() { Text = "50", Value = "50" };
private static SelectListItem item3 = new SelectListItem() { Text = "100", Value = "100" };
public SelectList selectList = new SelectList(new[] { item1, item2, item3 }, "Value", "Text", item1);
My razor view contains this:
#Html.DropDownListFor(m => m.SelectedPageOption, Model.DisplayPageOptions, "Pages to display", new { onchange = #"submit()" })
My controller contains this:
SymbolViewModel vm = SymbolViewModel.Instance;
if (vm.SelectedPageOption == null)
{
vm.SelectedPageOption = "25";
}
vm.DisplayPageOptions = vm.selectList;
SymbolDataService service = new();
vm.Load(service);
return View(vm);
My intention is that the user selects 25, 50, or 100 from the dropdown and a corresponding number of rows is displayed. My understanding is that the Html.DropDownListFor should bind to the SelectedPageOption property, which I would then use in the ViewModel to load the correct number of rows.
However, the change in dropdown value has no effect on the SelectedPageOption property, so the page is reloaded always with the original default '25' rows - taken from the value that is set in the controller. I have tried making SelectedPageOption an Int, having seen examples on the web. I hadn't wanted to do this originally because the SelectedListItem Values have to be strings. It makes no difference. I also tried not using a singleton pattern for the viewmodel - also no effect.
The binding is not completely ineffective. Whatever default value I set in the controller, this is the value that appears in the dropdown. But the binding does not work the other way. If anyone can cast some light on what's going on, I'd appreciate it.
EDIT: What I didn't originally point out, and what turned out to be relevant, is that my controller method containing the above code is asynchronous.
I finally worked out what was going on. Because my controller Index method is asynchronous, I need to await the model update before reloading my viewmodel. It seems obvious now!
SymbolViewModel vm = SymbolViewModel.Instance;
await TryUpdateModelAsync(vm);

How to remove model state error for dropdown with selected option label in mvc?

I am working on a MVC project.I have a view having a dropdownlist with an option label "Select Task".Now the integer property bound with this dropdown is not a required field.
But then too after I try to save, I get the dropdown having a red border showing that it is required. When I analysed, I found that the option label has value null.
Using firebug when I entered 0 for the value of option label, the view was saved with no model state error. Am I doing something wrong ? How to avoid it ?
View
#Html.DropDownListFor(model => model.projecttaskid, new SelectList((IList<SelectListItem>)ViewData["MyTasks"], "Value", "Text"),"Select Task", new { #class = "span2" })
Model public int projecttaskid { get; set; } Controller It doesn't reach the controller action.
projecttaskid is not nullable. Unless you provide a default value for the drop down list, the model will not bind properly, which is why you are getting validation error even though the model field is not marked as [required].
Edit: by default value I mean an item in the list with a value of 0, since the default value for projecttaskid is 0.
For instant, you use an overload of DropDownListFor with the optionLabel argument (your "Select Task" argument.
If no item is selected, this will be taken as the "selected option", and return a null value for model.projecttaskid.
The easiest way would be to add an element with a 0 value when you create your list (ViewData["MyTasks"])
With, for example :
Value = 0;
Text = "Select Task;
And use an overload of DropDownListFor without the optionLabel.
[By the way, usage of ViewModels instead of ViewData would be a good thing, but that's another problem]
*EDIT *
We do use some extension methods to manage these cases :
public static IEnumerable<SelectListItem> ToSelectListItem<T, TValue, TText>(
this IEnumerable<T> enumerable,
Func<T, TText> text,
Func<T, TValue> value)
{
return enumerable.Select(item => new SelectListItem
{
Text = text(item).ToString(),
Value = value(item).ToString()
}).AsEnumerable();
}
public static IEnumerable<SelectListItem> WithDefaultZeroValue(this IEnumerable<SelectListItem> selectListItems, string chooseText/* = ChooseText*/)
{
IList<SelectListItem> items = selectListItems.ToList();
items.Insert(0, new SelectListItem { Value = "0", Text = chooseText });
return items.AsEnumerable();
}
usage
var myList = mySourceForDropDown.ToSelectListItem(m => m.TextField, m => m.ValueField)
.WithDefaultZeroValue("SelectTask")
use this:
#Html.DropDownListFor(model => model.Type, new SelectList(Enum.GetNames(typeof(Enums.TenderType))), new Dictionary<string, object>() { { "data-val", "false" } })

Problem with DropDownListFor SelectedItem

This has totally puzzled me.
Here's my View:
#Html.DropDownListFor(model => model.ScoreDescription,
Model.RatingOptions,
"--",
new { #id = clientId })
And the model:
public decimal? Score { get; set; }
public SelectList RatingOptions
{
get
{
var options = new List<SelectListItem>();
for (var i = 1; i <= 5; i++)
{
options.Add(new SelectListItem
{
Selected = Score.HasValue && Score.Value == Convert.ToDecimal(i),
Text = ((decimal)i).ToRatingDescription(ScoreFactorType),
Value = i.ToString()
});
}
var selectList = new SelectList(options, "Value", "Text");
// At this point, "options" has an item with "Selected" to true.
// Also, the underlying "MultiSelectList" also has it.
// Yet selectList.SelectedValue is null. WTF?
return selectList;
}
}
As the comments suggest, i can't get the selected value to happen.
Is it something to do with the fact i'm using a nullable decimal ? After that loop, options is correct in that it has exactly 1 item with select to true, so it seems i'm doing the right thing.
Now, if i use a different SelectList overload:
var selectedValue = Score.HasValue ? Score.Value.ToString("0") : string.Empty;
var selectList = new SelectList(options, "Value", "Text", selectedValue);
It works. Why? At first i thought it might be a LINQ-trick (e.g deferred execution), but i tried forcing a .ToList() and there is no difference.
It's like setting the Selected property as you create the SelectListItem has no effect, and you have you set it at the end using the SelectList ctor parameter.
Can anyone shed some light on this?
If you look at the implementation of the SelectList class it never actually uses the fact that you are passing a SelectListItem. It works with an IEnumerable. So the Selected property of a SelectListItem is not used. Personally I prefer setting the selected value of a dropdown by setting the value of the corresponding property that you are binding the ddl to.
Example:
public int? Score { get; set; }
public SelectList RatingOptions
{
get
{
var options = Enumerable.Range(1, 5).Select(i => new SelectListItem
{
Text = ((decimal)i).ToRatingDescription(ScoreFactorType),
Value = ((decimal)i).ToString()
});
return new SelectList(options, "Value", "Text");
}
}
and then in the controller action simply set the Score property to the necessary value and in the view use this Score property to bind to:
#Html.DropDownListFor(
model => model.Score,
Model.RatingOptions,
"--",
new { #id = clientId }
)

Creating a SelectListItem with the disabled="disabled" attribute

I'm not seeing a way to create, via the HtmlHelper, a SelectListItem that will spit out the following HTML:
<option disabled="disabled">don't click this</option>
The only properties SelectListItem has are:
new SelectListItem{
Name = "don't click this",
Value = string.Empty,
Selected = false
}
The only option I see is to
Subclass the SelectListItem to add an Enabled property to get the value to the view
Not use the HTML helper for DropDownList
Create a new HtmlHelper extension that accepts my new EnablableSelectList and adds my disabled attribute.
The Disabled property is supported since ASP.NET MVC 5.2:
new SelectListItem {
// ...
Disabled = true
}
See the API reference.
This is something I might try before recreating the helper completely. The basic idea is that the Html you get from the helper should be well formed, so it should be safe to parse. So you can build on that idea by making your own extension that uses the existing extension but adds the functionality to disable the items.
Something like this might do (totally untested)
public class CustomSelectItem : SelectListItem
{
public bool Enabled { get; set; }
}
public static class CustomHtmlHelpers
{
public static MvcHtmlString MyDropDownList(this HtmlHelper html, IEnumerable<CustomSelectItem> selectList)
{
var selectDoc = XDocument.Parse(html.DropDownList("", (IEnumerable<SelectListItem>)selectList).ToString());
var options = from XElement el in selectDoc.Element("select").Descendants()
select el;
foreach (var item in options)
{
var itemValue = item.Attribute("value");
if (!selectList.Where(x => x.Value == itemValue.Value).Single().Enabled)
item.SetAttributeValue("disabled", "disabled");
}
// rebuild the control, resetting the options with the ones you modified
selectDoc.Root.ReplaceNodes(options.ToArray());
return MvcHtmlString.Create(selectDoc.ToString());
}
}
Clientside option: if you for example give your dropdownlist a class 'custom' and the items that should be unselectable the value -1 (for example), then you can do something like:
$('select.custom option[value=-1]').each(function () {
$(this).attr("disabled", "disabled");
});
If all you are trying to do is prevent a user from selecting a certain value from the list, it seems like the simpler and more time-efficient way to do it is to use input validation. Which you may quite possibly be doing anyways, if you want to verify they've made a selection to begin with.
-----Option 1
Controller:
var ExpectedShipmentsRange = new List();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "Selected number of shipments", Value="0", Disabled = true, Selected = true });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = ExpectedShipmentsRange;
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (IEnumerable<SelectListItem>)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
-----Option 2
Controller:
ViewBag.citiesSa = _dbContext.Countries.ToList();
View:
#Html.DropDownListFor(m => m.City, new SelectList(#ViewBag.citiesSa, "Id", "Name"), "Select your city", new { #class = "form-control" })
-----Option 3 does not support disabled option:
List<SelectListItem> ExpectedShipmentsRange = new List<SelectListItem>();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = new SelectList(ExpectedShipmentsRange, "Value", "Text");
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (SelectList)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
I've noticed that while using SelectList to populate the DropDownListFor() method the Disabled & Selected parameters are not respected. They are only honored when populating using List<SelectListItem>. However I've run into other odd bugs when populating the DropDownListFor() using List<SelectListItem> and found that using SelectList is the correct option for populating a DropDownListFor() list. Here's an example of how to create a SelectList list:
public static SelectList States = new SelectList(new[]
{
new SelectListItem { Text = "AL", Value = "AL" },
new SelectListItem { Text = "AK", Value = "AK" },
...
}, "Value", "Text", 1);
In my case I needed to disable the first item of the select list, the only way I was able to do so using the SelectList was by creating an extension method for the DropDownListFor() method. Here is the class I used:
public static class HtmlHelperExtensions
{
private static MvcHtmlString DisableFirstItemDropDownListFor(MvcHtmlString source, string sourceItemName, string sourceItemValue = "", string targetItemValue = "")
{
string htmlString = source.ToHtmlString();
if (string.IsNullOrEmpty(sourceItemValue))
{
htmlString = htmlString.Replace("value=\"\"", "value=\"\" disabled=\"disabled\" selected=\"selected\"");
}
return new MvcHtmlString(htmlString);
}
public static MvcHtmlString DisableFirstItemDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
{
return DisableFirstItemDropDownListFor(htmlHelper.DropDownListFor(expression, selectList, htmlAttributes), string.Empty);
}
}
You can then use this method in your .cshtml file like so:
#Html.DisableFirstItemDropDownListFor(x => x.YourFieldType, Model.YourModel, new { #class = "YourClass" })

Adding a default SelectListItem

public IEnumerable<SelectListItem> GetList(int? ID)
{
return from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
};
}
I return the above to a view and populate a DropDownList. I would like to add a default SelectListItem (0, "Please Select..") to the above linq result before it is returned to the view. Is this possible?
return new[] { new SelectListItem { Text = ... } }.Concat(
from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
});
As you are using ASP.NET MVC, you can do this in the view by specifying a value for the optionLabel parameter of the DropDownField method of the HtmlHelper - e.g:
htmlHelper.DropDownList("customerId", selectList, "Select One");
Putting this type of code in your UI layer is probably more appropriate than having it in the data layer. One downside to doing this is that your select box will have an empty string value, not a "0" for the 'Select One' option, but that is not really a problem as you can treat this as a null value if your controller action method can accept a nullable int for the relevant parameter - e.g.
public ActionResult DoSomething(int? customerId)
{
if(customerId != null)
{
// do something with the value
}
}
var list = from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
};
list.Insert(0, new SelectListItem { Text = "Please Select...", Value = string.Empty });
return list;
firt put your default value in the list
list.add(your default list item)
and then do list.addrange(linq select query)
cheers
Here is what I did, I read my values from an XML file into an IList. I then inserted a new record into the IList at position 0. Then make a select list from the IList.
IList< MY_DATA > mydata = (from tmp in myXML.Descendants("R").ToList()
select new MY_DATA
{
NR = tmp.Attribute("NR").Value,
NA = tmp.Attribute("NA").Value
}).ToList<MY_DATA>();
mydata.Insert(0, new My_DATA() { NR = "", NA = "--Click to Select--" });
SelectList mylist = new SelectList(mydata, "NR", "NA");
After I reviewed many questions, and I don't find what i'm looking for.I don't need many lines of code to just have a simple drowpdown. so I want to share with you what I use and it easy and simple.. ( specially if you don't want to use and Entity Framework..
using System.Web.Mvc;
SelectList(IEnumerable items, string dataValueField, string dataTextField);
For Example:
in Controller add :
SelectList slTitle = new SelectList(Query.SelectAllTitle(), "TitleID", "TitleName");
ViewBag.TitleSelectList = slTitle;
in View add :
#Html.DropDownList("TitleSelectList", "--please select--")
This is just for people who are using Webpages framwork (not MVC and webform) with Razor V2 and C#, you need do
#Html.DropDownList("category", "Please select", listData)
Note: OptionalLabel need to be the middle parameter.
Equivalent would be add a SelectListitem to your list data:
var list=db.Query("select id, name from dbtable");
List<SelectListItem> listData = new List<SelectListItem>();
listData.Add(new SelectListItem
{
Text = "Please select",
Value = "",
});
foreach(var item in list) {
listData.Add(new SelectListItem {
Text = item.Name,
Value = item.id,
Selected = isSelected
});
}
#Html.DropDownList("play" , listData)

Resources