Convert List of Enums to a SelectList and maintain display? - c#

We have a view that is driven by a List of options. Depending on the initial conditions, a DropDownList is created with choices for the user's next step. This list is populated with choices from a larger list of options encased within a custom Enumeration.
So we have an Enum like so:
public enum ChangeMode
{
[Display(Name ="Please Select")] InitialState,
[Display(Name = "Change A Thing")] ChangeThing,
//...
[Display(Name = "Do a Dance")] DoADance,
FinishedSuccess,
FinishedFailure
}
And in the controller for the view, we build a List that contains all the valid options for the user, which is NEVER the full list.
if( /*Irrelevant determining characteristic*/ )
{//Valid choices for this option
model.ValidModes = new List<ChangeMode> { ChangeMode.InitialState, ChangeMode.ChangeThing};
}
We then create a DropDownList in the view, based on the model's list of ChangeModes
#Html.DropDownListFor(m => Model.AlterMethod, new SelectList(Model.ValidModes))
All of this is working just fine, except that the generated DDL does not include the user-friendly Name that each mode is assigned, instead showing the Enum's developer-readable value ('InitialState', for example). How do I change this to have the View correctly render the dropdownlist such that it uses the Display(Name) as the Text for the DDL?

You'll need to get the values from your Enum attributes. To do this, you need a helper method that will return the attribute.
here is a helper method to access Enum attributes:
public T GetAttribute<T>(Enum _enum) where T : Attribute
{
return
(T)_enum.GetType()
.GetField(Enum.GetName(_enum.GetType(), _enum))
.GetCustomAttribute(typeof(T));
}
Usage :
var name = GetAttribute<DisplayAttribute>(ChangeMode.InitialState).Name;

ASP.NET Core ships with a helper for generating a SelectListItem for
any enum.
This means that if you have an enum of the TEnum type, you can generate the available options in your
View using asp-items="Html.GetEnumSelectList<TEnum>()".
Change:
#Html.DropDownListFor(m => Model.AlterMethod, new SelectList(Model.ValidModes))
To:
<select asp-for="AlterMethod" asp-items="Html.GetEnumSelectList<Enums.ChangeMode>()"></select>
Or if you only want the Values from ValidModes:
<select asp-for="AlterMethod" asp-items="Html.GetEnumSelectList(typeof(Model.ValidModes))"></select>

Related

c# Razor Pages Select Tag Helper

New to Razor, c# and ASP and with refrence to the following.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/?view=aspnetcore-2.1
Currently I'm able to arrange my rows from my model into a select view using the following code in my Pages code.
public SelectList Ratings { get; set; }
public async Task<IActionResult> OnGetAsync()
{
IQueryable<string> ratingQuery = from m in _context.Ratings
orderby m.MovieRating
select m.MovieRating;
Ratings = new SelectList(await ratingQuery.Distinct().ToListAsync());
return Page();
}
And within my HTML page reference that to produce a nice list of ratings to choose from.
<select asp-for="Movie.Rating" asp-items="Model.Ratings">
</select>
My issue is the options generated do not have a value other than that of the Movie.Rating field (i.e. GOOD, BAD, UGLY), everything works ok but when I inset into the DB I would like to inset the "ID" and not the "MovieRating"
I would like to have the following html created when the page is generated. Where the ID field from within the Table is the Value and the "MovieRating" is the Text.
<select asp-for="Movie.Rating" asp-items="Model.Ratings">
<option value="1">Good</option>
<option value="2">Bad</option>
<option value="3">Ugly</option>
</select>
In order to do this I know I need to select more than the "MovieRating" field within the select statement. So I can change this to also select the ID. However it will just spit out a combined string into the option field and not produce a value field.
Is this the correct method to achieve what I want to do. I can find a few examples online of how to achieve this another way but I do not want to delve into MVC just yet.
Your current LINQ query is SELECTing a single column, MovieRating and you are using the result of executing that query, which is a list of strings, to build the SelectList object. Your select tag helper is using this SelectList object which has only the MovieRating string values as the underlying items.
Assuming your Ratings class has an Id property(int type) and MovieRating property(string type), you may create a SelectListItem in the projection part of your LINQ expression.
List<SelectListItem> ratingItems = _context.Ratings
.Select(a=>new SelectListItem
{
Value=a.Id.ToString(),
Text = a.MovieRating
}).ToList();
Ratings = ratingItems;
return Page();
Here the ratingItems is a list of SelectListItem objects. So change your Ratings properties type to a collection of SelectListItem objects.
public List<SelectListItem> Ratings { get; set; }

Set default for dropdownlist in view

I have a dropdownlist that I want to set a default value for but the way I have it set up does not work with all the examples I have seen on the web. Here is my controller code for getting the string:
ViewBag.RoleId = new SelectList(db.Roles, "RoleId", "RoleDescription");
Then here is the code of my dropdownlist in the view:
#Html.DropDownList("RoleId", null, "Select Role", htmlAttributes: new { #class = "form-control" })
I want to set that dropdownlist to be defaulted to a User Role but I cannot figure it out.
Here is the code for my roleID in my model trying to set the default value:
[Display(Name = "Role")]
[Required]
[DefaultValue('B2323674-45DC-45E7-91D2-E635CF63C04A')]
[ForeignKey("Role")]
public Guid RoleID { get; set; }
problem is I am getting an error on the Guid Default saying that it is too long for a string literal.
You can use this overload of the SelectList that accepts default value:
ViewBag.RoleId = new SelectList(db.Roles, "RoleId", "RoleDescription", selectedValue);
Where selectedValue is an object of whatever type is in your list.
Initializes a new instance of the SelectList class by using the
specified items for the list, the data value field, the data text
field, and a selected value.
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField,
object selectedValue
)
So in your case:
ViewBag.RoleId = new SelectList(db.Roles, "RoleId", "RoleDescription", "B2323674-45DC-45E7-91D2-E635CF63C04A");
more info.
Also there's another solution that personally I recommand you that, Using jQuery in view for selection for example the first item of the drop down list:
$("#target").val($("#target option:first").val());
The problem is that your ViewBag property and your model property are named the same. When Razor renders the values for your fields it uses the data from ModelState. ModelState is composed from values from ViewBag, ViewData, Request and Model as a last resort. In other words, since the name of the ViewBag property and Model property are the same, you're essentially saying that the selected value of RoleId is the entire SelectList, which obviously is not what you want.
The simplest solution is to simply name your ViewBag property something else, like ViewBag.RoleIdChoices. Then, you'll be fine.
There's a couple things going on here.
You won't be able to bind a dropDownList to a ViewBag collection "Extension methods cannot be dynamically dispatched." Meaning, you need to include your SelectList into your Model.
You can set a SelectedValue in your SelectList
ViewBag.RoleId = new SelectList(db.Roles, "RoleId", "RoleDescription", <DefaultRoleObj>);
You need to include the reference to you selectList in your DropDownList
#Html.DropDownList("RoleId", <Model.SelectList>)

How do I get access to the model property's name from a child view

If my model is defined as follows:
public class GreaterModel
{
public IList<LesserModel> MyLesserModelNumber1 {get;set;}
public IList<LesserModel> MyLesserModelNumber2 {get;set;}
}
public class LesserModel
{
public string MyString {get;set;}
}
and my view like this:
#model GreaterModel
<h1>My First Editable List</h1>
#Html.EditorAndAdderFor(m=>m.MyLesserModelNumber1)
<h1>My Second Editable List</h1>
#Html.EditorAndAdderFor(m=>m.MyLesserModelNumber2)
EditorAndAdderFor is a custom extension method that works in a similar way than EditorFor. It uses the following partial view to add the markup for editing the list items as well as adding new ones: (simplified for clarity)
#model IEnumerable<object>
/*There's a lot of markup left out here to do with the editing of items (basically uses #Html.EditorForModel()
)*/
/*Then there is input fields to add new items to the list. */
<input type='text' id='addnew-mystring' />
Some JS to handle the new entry:
$('#addnew-mystring').focus(function(){
var value = $(this).val();
//now I need to add the new value into the list of existing
//LesserModel items using the correct form name.
//I.e. "MyLesserModelNumber1[0].MyString"
})
I would like to know which property is calling my editor template from within the editor template view file so that I can give the new entry the correct HTML form name and id for model binding to work. So maybe something like this from the partial view
string name = Html.ViewData.ModelMetadata.PropertyName;
//should contain "MyLesserModelNumber1",
//but currently is null
I agree with david's suggestions.
If you need a completely different html for different property use different model and view for each property.
If you need same view and only a slight change depend on the property you should have a property within LesserModel that Distinguishes between the two.

Getting value from DropDownList in MVC3.. better way than binding the ID to another field?

I am wondering if there is another way to express the following bit of code. The code works as expected, but I have an issue with how it defines my naming conventions.
//Model.cs:
[DisplayName("Device Manufacturer")]
public List<KeyValuePair<int, string>> DeviceManufacturer { get; set; }
public int SelectedManufacturerID { get; set; }
//Model.ascx:
<%= Html.DropDownListFor(model => model.SelectedManufacturerID, new SelectList(Model.DeviceManufacturer, "Key", "Value"))%>
Now, whenever the selected list item is changed -- the new value is stored in SelectedManufacturerID. I am able to receive the value in my controller like so:
int selectedID = model.SelectedDataCenterID;
However, I am unhappy with the fact that MVC gives my select list the ID 'SelectedManufacturerID' when it is not an ID field -- it is a select list. Clearly this is occurring to support the binding to the SelectedManufacturerID field.
If I wish to work with my select list client-side, I now have code such as:
$('#SelectedManufacturerID').change(function(){
console.log("OnSelectedIndexChanged!");
});
This jQuery is very unclear. There is no indication that SelectedManufacturerID is a select DOM element. It seems impossible to have proper naming conventions using the MVC conventions at the top of this post. Does anyone else feel this way? Do I have other options?
If you want,you can update the ID or other html attributes of the dropdown by using a different overload of DropDownListFor helper method. Here you can pass html attributes as the last parameter.
<%= Html.DropDownListFor(model => model.SelectedManufacturerID,
new SelectList(Model.DeviceManufacturer, "Key", "Value"),
new { #id="ManufacturerList"})%>
Use select#SelectedManufacturerID instead.

ASP.NET MVC DropDownListFor with model of type List<string>

I have a view with a model of type List<string> and I want to place a drop down list on the page that contains all strings from the list as items in the drop down. I am new to MVC, how would I accomplish this?
I tried this:
#model List<string>
#Html.DropDownListFor(x => x)
but that threw an error.
To make a dropdown list you need two properties:
a property to which you will bind to (usually a scalar property of type integer or string)
a list of items containing two properties (one for the values and one for the text)
In your case you only have a list of string which cannot be exploited to create a usable drop down list.
While for number 2. you could have the value and the text be the same you need a property to bind to. You could use a weakly typed version of the helper:
#model List<string>
#Html.DropDownList(
"Foo",
new SelectList(
Model.Select(x => new { Value = x, Text = x }),
"Value",
"Text"
)
)
where Foo will be the name of the ddl and used by the default model binder. So the generated markup might look something like this:
<select name="Foo" id="Foo">
<option value="item 1">item 1</option>
<option value="item 2">item 2</option>
<option value="item 3">item 3</option>
...
</select>
This being said a far better view model for a drop down list is the following:
public class MyListModel
{
public string SelectedItemId { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
and then:
#model MyListModel
#Html.DropDownListFor(
x => x.SelectedItemId,
new SelectList(Model.Items, "Value", "Text")
)
and if you wanted to preselect some option in this list all you need to do is to set the SelectedItemId property of this view model to the corresponding Value of some element in the Items collection.
If you have a List of type string that you want in a drop down list I do the following:
EDIT: Clarified, making it a fuller example.
public class ShipDirectory
{
public string ShipDirectoryName { get; set; }
public List<string> ShipNames { get; set; }
}
ShipDirectory myShipDirectory = new ShipDirectory()
{
ShipDirectoryName = "Incomming Vessels",
ShipNames = new List<string>(){"A", "A B"},
}
myShipDirectory.ShipNames.Add("Aunt Bessy");
#Html.DropDownListFor(x => x.ShipNames, new SelectList(Model.ShipNames), "Select a Ship...", new { #style = "width:500px" })
Which gives a drop down list like so:
<select id="ShipNames" name="ShipNames" style="width:500px">
<option value="">Select a Ship...</option>
<option>A</option>
<option>A B</option>
<option>Aunt Bessy</option>
</select>
To get the value on a controllers post; if you are using a model (e.g. MyViewModel) that has the List of strings as a property, because you have specified x => x.ShipNames you simply have the method signature as (because it will be serialised/deserialsed within the model):
public ActionResult MyActionName(MyViewModel model)
Access the ShipNames value like so: model.ShipNames
If you just want to access the drop down list on post then the signature becomes:
public ActionResult MyActionName(string ShipNames)
EDIT: In accordance with comments have clarified how to access the ShipNames property in the model collection parameter.
I realize this question was asked a long time ago, but I came here looking for answers and wasn't satisfied with anything I could find. I finally found the answer here:
https://www.tutorialsteacher.com/mvc/htmlhelper-dropdownlist-dropdownlistfor
To get the results from the form, use the FormCollection and then pull each individual value out by it's model name thus:
yourRecord.FieldName = Request.Form["FieldNameInModel"];
As far as I could tell it makes absolutely no difference what argument name you give to the FormCollection - use Request.Form["NameFromModel"] to retrieve it.
No, I did not dig down to see how th4e magic works under the covers. I just know it works...
I hope this helps somebody avoid the hours I spent trying different approaches before I got it working.

Categories