I'm trying to put data form two tabels (AcademicDegrees, Lecturers) conneted by one to many relation into one ViewData to generate options to field (with label and id as value). It shoud be somthing like this where id are used as values nad other field as label.
ViewData["ClassroomsId"] = new SelectList(_context.Classroom, "ClassroomsID", "Number");
When all the date for field was in one table I used getter form field to get it.
[NotMapped]
public string toStringVar => FirstName + " " + LastName;
When I added tabel with academic degrees I moved to different solution.
var lecturers = _context.Lecturer;
var degree = _context.AcademicDegrees;
var lecturersList = new List<SelectListItem>();
foreach (Lecturers l in lecturers)
{
_context.AcademicDegrees.Where(d => d.AcademicDegreeId == l.AcademicDegreesId).Load();
foreach(AcademicDegrees d in degree)
{
lecturersList.Add(new SelectListItem(
$"{d.Name} {l.FirstName} {l.LastName}", l.LecturersID.ToString()
));
}
}
ViewData["LecturersId"] = new SelectList(lecturersList);
The problem is that it isn't interpreted as I want it to be.
I also can't put it directly in to SelectList because it doesn't have empty constructor or add method. Is there any other way to implement a SelectList?
In my opinion, it is like redundant work as you have the IEnumerable<SelectListItem> instance which can be used to build the select option.
And you pass IEnumerable<SelectListItem> instance to create the SelectList instance.
Would suggest to pass IEnumerable<SelectListItem> value to ViewData.
Solution for IEnumerable<SelectListItem>
Controller
ViewData["LecturersId"] = lecturersList;
View
#Html.DropDownListFor(model => model./*YourProperty*/, (IEnumerable<SelectListItem>)ViewData["LecturersId"])
Updated
Since you are using ASP.NET Core MVC, with tag helper:
<select asp-for="/*YourProperty*/"
asp-items="#((IEnumerable<SelectListItem>)ViewData["LecturersId"]))">
</select>
Solution for SelectList
If you are keen on the SelectList, make sure that you have provided the dataValueField and dataTextField according to this constructor
public SelectList (System.Collections.IEnumerable items, string dataValueField, string dataTextField);
as below:
ViewData["LecturersId"] = new SelectList(lecturersList, "Value", "Text");
Besides, the query part would be suggested to optimize by joining both tables as below:
var lecturersList = (from a in _context.Lecturer
join b in _context.AcademicDegrees on a.AcademicDegreeId equals b.AcademicDegreeId
select new SelectListItem($"{b.Name} {a.FirstName} {a.LastName}", a.LecturersID.ToString())
).ToList();
Related
I'm still pretty new to .NET, but I think I've read everything there is to read on this subject (including similar questions on SO, which is where I got some of the things I've tried). I feel like I've tried everything possible and I still can't get it to work.
I have a Note class and a Category class. Pretty straightforward, each note has a Category property, so I want to have a dropdown list in my Create view that displays categories. I can get a list to display the category names correctly, but that's it. It keeps telling me there's no IEnumerable in my ViewData called "Categories" when there definitely, 1000% for sure is...
The Create action in my NoteController looks like this:
// GET: Create
public ActionResult Create()
{
SelectList items = (new CategoryService()).GetCategories().Select(c => new SelectListItem
{
Value = c.CategoryId.ToString(),
Text = c.Name
}) as SelectList;
ViewData["Categories"] = items;
return View();
}
And I've tried a few variations in the view:
#Html.DropDownListFor(e=>e.CategoryId , (IEnumerable<SelectListItem>) ViewData["Categories"])
#Html.DropDownList("Categories", "Select a Category")
My Create view uses a NoteCreate model, which has this:
public class NoteCreate {
...
[Display(Name = "Category")]
[Required]
public string CategoryId { get; set; }
And my NoteService has a CreateNote method like so:
public bool CreateNote(NoteCreate model)
{
using (var ctx = new ApplicationDbContext())
{
bool isValid = int.TryParse(model.CategoryId, out int id);
if (!isValid)
{
id = 0;
}
var entity =
new Note()
{
OwnerId = _userId,
Title = model.Title,
Content = model.Content,
CreatedUtc = DateTimeOffset.Now,
Status = model.Status,
CategoryId = id
};
ctx.Notes.Add(entity);
return ctx.SaveChanges() == 1;
}
}
I figured I have to turn the ID into a string for the sake of the dropdown list (because SelectListItem's Value and Text are strings), which is why I parse it back into an int here
I tried attaching the list to the ViewBag instead, and I've tried variations of both DropDownListFor and DropDownList
One of those combinations resulted in a dropdown list actually showing, and I don't remember what it was, but selecting an item resulted in a null being passed to the NoteCreate method (model.CategoryId)
Can anyone help me, and potentially many others who will struggle with this in the future because the documentation is so terrible?
UPDATE:
My controller has been refactored to:
// GET: Create
public ActionResult Create()
{
List<SelectListItem> li = new List<SelectListItem>();
List<Category> Categories = (new CategoryService()).GetCategories().ToList();
var query = from c in Categories
select new SelectListItem()
{
Value = c.CategoryId.ToString(),
Text = c.Name
};
li = query.ToList();
ViewBag.Categories = li;
return View();
}
and my view has been refactored to:
#Html.DropDownList("Categories", ViewBag.Categories as SelectList, new { #class = "form-control" })
This is closer, as I can now load the view and see the Category names in the dropdown. However, when I save, model.CategoryId in my CreateNote method is null, so the CategoryId value isn't actually being passed from the dropdown into the model.
If ViewModel is used in the view then its better to paa the data through model properties to the view. No need to put the collection for Dropdownlist in ViewData or ViewBag.
For the detail way of using Dropdownlist through SelectList and pass to the view through, I would refer an answer I had posted:
MVC C# Dropdown list Showing System.Web.SelectListItem on the model and can not blind to controller
The model passed to your view needs a property for CategoryId.
Your Html Helper is looking for CategoryId here:
#Html.DropDownListFor(e=>e.CategoryId
Ok... I figured it out.
It's so stupid.
The key you use to store the SelectList in your ViewData HAS to be the same as the name of the property on the model, even though you can explicitly tell it to use the list using a different key....
So even if you wanted to use the same SelectList for a few different properties (but process them differently in your service, say), you'd have to pass it to the ViewData redundantly for each property
So instead of passing my SelectList through as ViewBag.Categories, I passed it in as ViewBag.CategoryId, and that worked.
I'm going to go drink a lot of alcohol now.
In Controller
List<SelectListItem> li = new List<SelectListItem>();
var query = from of in your_context.Categories
select new SelectListItem()
{
Value = of.CategoryId.ToString(),
Text = of.Name
};
li = query.ToList();
ViewBag.Category_ = li;
View
<div class="col-md-10">
#Html.DropDownList("Categories", ViewBag.Category_ as List<SelectListItem>, new { #class = "form-control" })
</div>
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; }
I feel that the answer for this has to be out there in several places as variations of this question seem to get asked a lot. Unfortunately I cannot grasp exactly how to do what I am trying to achieve, so your help is greatly appreciated once again.
I have a list of usergroups, which are each assigned to a staff member. This is done by iterating through the list of usergroups and showing a dropdown of available staff members beside each one. It should be possible to not assign a staff member also, so a null value select option should be available.
When a new group is being created, having the null value as the default is fine, but where I am just updating an existing record, I want the dropdown to default to the option with the matching staff member ID.
So I query for available staff members:
var rtrnStaff = (from st in db.PrmTbl_Staffs
join sal in db.PrmTbl_Salutations on st.SalutationID equals sal.ID
where st.Active == true
select new { st.ID, Name = sal.Desc + ". " + st.Name });
To insert a blank value into this array:
List<SelectListItem> staff = new SelectList(rtrnStaff, "ID", "Name").ToList();
staff.Insert(0, (new SelectListItem { Text = "None", Value = "0" })); //can value be = null?
In my view, for the form to create a new user group, I can provide a dropdown like so:
#Html.DropDownList( "staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"])
This provides a dropdown, with the "None" option first, which is fine. However, when I try the same thing for my update form, with the addition of a default value argument, it doesn't work:
#Html.DropDownList( "staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"], item.StaffID)
The intention being that when placed within a foreach loop, the option matching the relevant staffID would show as default. Instead, "none" is still the first option.
I did try to just query the table in my controller, not build a selectlist there but pass the results directly via ViewData to the View, and then in the View do the following:
#Html.DropDownList("staffID", new SelectList(
(System.Collections.IEnumerable) ViewData["Staff"], "ID", "Name", item.StaffID),
new { Name = "staffID" })
That works no probs, but without a "none" option. Clearly I need some middle ground! Between DropDownList, DropDownListFor, List, SelectList, etc., I'm confused.
EDIT
(To show current state of code)
Controller:
var rtrnStaff = (from st in db.PrmTbl_Staffs
join sal in db.PrmTbl_Salutations on st.SalutationID equals sal.ID
where st.Active == true
select new { st.ID, Name = sal.Desc + ". " + st.Name });
List<SelectListItem> staff = new SelectList(rtrnStaff, "ID", "Name").ToList();
ViewData["StaffwNull"] = staff;
View:
//show dropdown of all staff,
//defaulting to "None" value (works)
#Html.DropDownList("staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"], "None")
//show dropdown of all staff,
//defaulting to value matching item.staffID (doesn't work)
//default selection is first list item
//and list doesnt include "None" option
#foreach (var item in Model)
{
...
var thisStaffID = item.StaffID;
....
#Html.DropDownList( "staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"], thisStaffID)
}
There is no overload where you can specify a selected value. MVC searches in the ViewBag object for an item called staffID and use that as selected value. You can use this overload of the DropDownList method which allows you to specify an option label:
#Html.DropDownList("staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"], "None")
This renders an extra option to the select list at the top so you don't have to do this manually.
Side note: you should look into MVC model binding.
You can give an ID to the dropdown list (this overload) and then use jQuery to update it.
View
#Html.DropDownList("staffID", (IEnumerable<SelectListItem>)ViewData["StaffwNull"], new { id = "dropdown1" })
jQuery
<script>
$(document).ready(function () {
$('#dropdown1').val('theValueYouWantSelected');
});
</script>
I have got two different tables. User and ProjectDetails. There are two different controllers as well to do CRUD operations on these tables. Now, I have a case where, in the User CREATE operation, I have to select the Project from the List of Projects in ProjectDetails. I tried the following:
In the user model, I created this line:
public IEnumerable<ProjectDetail> ProjectDetail { get; set; }
And in the controller, in the create Action, I have added the following code:
public ActionResult Create()
{
var model = new UserDetail
{
ProjectDetail = db1.ProjectDetails
};
return View(model);
}
And in the create view, I am trying to get the list of Project IDs as follows:
#Html.DropDownListFor( x => x.ProjectDetail, new SelectList(Model.ProjectDetail, "Project ID"))
However, wen i run, i get the number of lines (as equal to the number of projects) as
System.Data.Entity.DynamicProxies.ProjectDetails_F########### (Some numbers)..
Please can someone help me?
Regards,
Hari
[EDIT] - I checked in the debug mode and found the following.. Tried attaching the image..
I drilled down that Proxy things and found ProjectID there. How can I get that?
You are using a wrong overload, use this instead:
#Html.DropDownListFor( x => x.ProjectDetail,
new SelectList(Model.ProjectDetail, "ProjectId","ProjectName"))
// where ProjectId is the unique identifier field of `ProjectDetail`
// and `ProjectName` is the text you want to show in the dropdown
In your code you are not telling the html helper what properties to use for the datavalue and the datatext. The overload you use is the one where you tell the htmlhelper which value is selected.
You can do something like
var projection = db1.ProjectDetails.Select(t => new ProjectDetailsViewModel
{
Prop1 = t.Prop1,
Prop2 = t.Prop2
});
Can you try
public ActionResult Create()
{
var model = new UserDetail
{
ProjectDetail = db1.ProjectDetails.ToList()
};
return View(model);
}
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.