MVC6 Dropdownlist of Countries - c#

I am trying to use MVC6 Tag Helpers to create a dropdownlist of CountryCode and CountryName so that a user can select their country after registering. The relevant part of the view looks like this so far
<form asp-controller="Manage" asp-action="EditCountry" asp-route-returnurl="#ViewData["ReturnUrl"]">
<div asp-validation-summary="ValidationSummary.ModelOnly" class="text-danger"></div>
<select asp-for="CountryCode" asp-items="#Model.Countries"></select>
The relevant part of the viewmodel looks like this
[Display(Name = "Country")]
public string CountryCode { get; set; }
public IEnumerable<Country> Countries { get; set; }
A Country looks like this
public partial class Country
{
[Key]
public string CountryCode { get; set; }
public string CountryName { get; set; }
public virtual ICollection<ApplicationUser> Users { get; set; }
}
The controller returns a list of countries to the viewmodel
var model = new IndexViewModel
{
CountryCode = user.CountryCode,
Countries =_customersContext.Countries.OrderBy(c=>c.CountryName),
};
return View(model);
but in the view asp-items="#Model.Countries" has a squiggly Cannot convert Country to SelectListItem
Also I cannot find how in the form to specify CountryCode as the property to return and CountryName as the property to display.

The way I make my dropdowns is somewhat similar except that in my ViewModel, my property is of type SelectList instead of an IEnumerable<>.
public class HomeViewModel
{
public string CountryCode { get; set; }
public SelectList CountryList { get; set; }
}
Then in the controller I get the data and convert it to an anonymous list with two properties “Id” and “Value”.
In turn, I create a new SelectList() passing in the anonymous list specifying what is the dataValueField and what is the dataTextField.
public IActionResult Index()
{
var countries = _customersContext.Countries.OrderBy(c => c.CountryName).Select(x => new { Id = x.Code, Value = x.Name });
var model = new HomeViewModel();
model.CountryList = new SelectList(countries, "Id", "Value");
return View(model);
}
Finally, in the View:
<div class="form-group">
<label asp-for="CountryCode" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="CountryCode" asp-items="#Model.CountryList"></select>
</div>
</div>

public class MyViewModel //MODEL LAYER
{
public int CountryId { get; set; }
public string CountryName { get; set; }
public List<Employee> EmployeesList { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string FullName { get; set; }
}
public IActionResult Contact1() //CONTROLLER
{
MyViewModel N1 = new MyViewModel();
List<Employee> N2 = new List<Employee>()
{
new Employee { Id=1,FullName="sivaragu" },
new Employee { Id=2,FullName="siva" },
new Employee { Id=3,FullName="SENTHIL" }
};
ViewBag.MovieType = N2;
return View();
}
CSHTML(MVC6)
<select asp-for="CountryId" asp-items="#(new SelectList(#ViewBag.MovieType,"Id","FullName") )">
</select>

I have proposed an alternative way of doing this by extending the SelectTagHelper with some more attributes that can make this type of development more convenient. The issue is discussed here.
It is based on a class SelectListDescriptor that contain the list of items, the property to display the text and value field respectively. Then in the view one simple type
<select asp-descriptor="#ViewBag.CountryDescriptor"><option value="">-- Choose country</option<</select>.
The country descriptor is just new SelectListDescriptor(nameof(Country.CountryCode), nameof(Country.CountryName), countries). This avoid magic strings by utilizing the power of the `nameof-operator

Related

Select tag helper from database ASP.NET Core 3.1

Ok, I'm trying to do a proper dropdown in Core 3.1. In this example https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper
Model has a new list with hardcoded values
public string Country { get; set; }
public List<SelectListItem> Countries { get; } = new List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
I looked for examples where the list is coming from the database but they are very inconsistent. The only way I was able to do the dropdown list is with the ViewBag which is not advised.
I have two models. 1.
public partial class Glossary
{
public int UniqueId { get; set; }
public int Category { get; set; }
public string DisplayText { get; set; }
}
which is my view model
public partial class AdminUser
{
[Key]
public int Id { get; set; }
public string UserName { get; set; }
public string UserLocation { get; set; }
public string UserStatus { get; set; }
//public IEnumerable<Glossary> Glossary { get; set; } //I used this for ViewBag
public List<SelectListItem> UserLocations { get; } = new List<SelectListItem>
{
according to the example my query should go here
};
}
Here is my controller:
public IActionResult Create()
{
// This is the ViewBag that worked with HTML helpers, but I'm trying to use tag-helpers.
/*IEnumerable<SelectListItem> LocationsList = _context.Glossary.Where(x => x.Category == 1).Select(x => new SelectListItem
{
Value = x.UniqueId.ToString(),
Text = x.DisplayText
});
ViewBag.LocationsList = LocationsList;
*/
return View();
}
All examples that found were hardcoded lists and nothing with getting it from the database. What is the proper way to get the data from the Glossary table through the view model with ViewBag? Any pointers are appreciated.
ALSO:
When using this example: Select Tag Helper in ASP.NET Core MVC
When I used
public SelectList Employees { set; get; }
I got error: InvalidOperationException: The entity type 'SelectListGroup' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
Both of my tables have PK and adding [Key] to Glossary model didn't fix it.
If you'd like to retrieve data from db and populate a dropdown with retrieved data through a view model (or ViewBag), you can refer to following code snippet.
In AdminUser view model class, include these properties
public string Selected_Glossary { get; set; }
public List<SelectListItem> Glossary_List { get; set; }
In controller
public IActionResult Create(AdminUser model)
{
var adminuser_model = new AdminUser
{
UserName="test"
//for other properties
};
//retrieve data from Glossary table
var items = _context.Glossary.Where(x => x.Category == 1).Select(x => new SelectListItem
{
Value = x.UniqueId.ToString(),
Text = x.DisplayText
}).ToList();
//pass dropdown items through a view model
adminuser_model.Glossary_List = items;
////pass dropdown items through ViewBag
//ViewBag.Glossary_List = items;
return View(adminuser_model);
}
In view page
#model AdminUser
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<form asp-controller="Home" asp-action="Create" method="post">
<select asp-for="Selected_Glossary" asp-items="Model.Glossary_List"></select>
#*populate it through ViewBag*#
#*<select asp-for="Selected_Glossary" asp-items="ViewBag.Glossary_List"></select>*#
<input type="submit" value="Submit" />
</form>
Test Result

How to know the selected checkboxes from within the HttpPost Create action method?

I have many-to-many relationship between Student and Course. The linking entity set is Enrollment. For the sake of simplicity, they are all defined as follows.
Models
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment
{
public int Id { get; set; }
public int StudentId { get; set; }
public int CourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
ViewModels
public class StudentCourseVM
{
public Student Student { get; set; }
public IEnumerable<Course> SelectedCourses { get; set; }
public IEnumerable<Course> AvailableCourses { get; set; }
}
Controllers
public IActionResult Create()
{
var availableCourses = context.Courses;
return View(new StudentCourseVM { AvailableCourses = availableCourses });
}
[HttpPost]
public async Task<IActionResult> Create(StudentCourseVM sc)
{
if (ModelState.IsValid)
{
// What should I do here?
// ======================
await context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(sc);
}
Views
#model MasterDetails.ViewModels.StudentCourseVM
<form asp-action="Create">
<div>
<label asp-for="#Model.Student.Name"></label>
<input asp-for="#Model.Student.Name" />
</div>
<div>
<label asp-for="#Model.Student.Enrollments"></label><br />
#foreach (var course in Model.AvailableCourses)
{
<input type="checkbox" name="#course.Title" id="#course.Id" /> #course.Title <br />
}
</div>
<input type="submit" value="Create" />
</form>
Questions
How to know the selected check boxes from within the HttpPost Create action method?
You can use Editor Templates to do this.
First, create a new class for the course selection and update your view model to have a collection of that class.
public class SelectedCourse
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class StudentCourseVM
{
public int StudentId { set; get; }
public IEnumerable<SelectedCourse> SelectedCourses { get; set; }
}
You do not need to copy and paste all the properties from your entity model to your view model. View model needs only those properties which the view absolutely need. I am assuming you want to assign courses to a specific student
Now go to your ~/Views/YourControllerName and create a directory called EditorTemplates. Create a new razor file there and give the name SelectedCource.cshtml
Paste this code to the new file
#model SelectedCourse
<label>#Model.Name</label>
<input asp-for="IsSelected"/>
<input type="hidden" asp-for="Id" />
Now in your GET action, create an object of the view model, load the SelectedCourses collection and send it to the view.
public IActionResult Create()
{
// I hard coded the student id and the courses here.
// you may replace it with real data.
var vm = new StudentCourseVM { StudentId = 12 };
//Assuming we are assigning courses to the student with id 12
vm.SelectedCourses = new List<SelectedCourse>()
{
new SelectedCourse {Id = 1, Name = "CSS"},
new SelectedCourse {Id = 2, Name = "Swift"},
new SelectedCourse {Id = 3, Name = "IOS"},
new SelectedCourse {Id = 4, Name = "Java"}
};
return View(vm);
}
Now in your main view(Create.cshtml) which is strongly typed to StudentCourseVM,Use EditorFor helper method on the SelectedCourses property.
#model StudentCourseVM
<form asp-action="Create">
#Html.EditorFor(f=>f.SelectedCourses)
<input type="hidden" asp-for="StudentId"/>
<input type="submit"/>
</form>
The Editor template will execute code in the editor template file for each item in the SelectedCourses collection. So you will have the course name and a checkbox visible to the user.
In your HttpPost action method, you can use the same view model as the parameter. When the form is submitted, you may loop through the items in SelectedCourses property check the IsSelected property value. The courses user selected in the ui will have a true value.
[HttpPost]
public IActionResult Create(StudentCourseVM model)
{
var studentId = model.StudentId;
foreach (var modelSelectedCourse in model.SelectedCourses)
{
if (modelSelectedCourse.IsSelected)
{
//this one is selected. Save to db
}
}
// to do : Return something
}
Pre-selecting some checkboxes on page load
Sometimes you want to pre select some checkboxes when the page loads (Ex : For your edit screen you want to show already saved courses as checked). To do this, you simply need to set the IsSelected property of the corresponding SelectedCourse object to true in your GET action method.
public IActionResult Edit(int id)
{
// I hard coded the student id and the courses here.
// you may replace it with real data.
var vm = new StudentCourseVM { StudentId = id };
//Assuming we are assigning courses to the student with id 12
vm.SelectedCourses = new List<SelectedCourse>()
{
new SelectedCourse {Id = 1, Name = "CSS"},
new SelectedCourse {Id = 2, Name = "Swift", IsSelected = true },
new SelectedCourse {Id = 3, Name = "IOS", IsSelected = true },
new SelectedCourse {Id = 4, Name = "Java"}
};
return View(vm);
}
The above code will pre select the checkboxes for Swift and IOS.

Paging on View with MVC Paged List

I wanna implement MVC paging so on the Index Action its working.
public ActionResult Index(int? page)
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
//model.Customers = db.Customers.OrderBy(m => m.CustomerID).OrderByDescending(m=>m.CustomerID).Take(10).ToList();
model.Customers = db.Customers.OrderBy(m => m.CustomerID).OrderByDescending(m=>m.CustomerID).Take(10).ToList().ToPagedList(page ?? 1,5);
model.SelectedCustomer = null;
var list = new List<int>();
for (int i = 1; i <= 20; i++)
{
list.Add(i);
}
SelectList selectedList = new SelectList(list);
ViewBag.DdList = selectedList;
//model.Countries = db.Countries.ToList();
model.CountryList = new SelectList(BLDDLCountry.GetCountry(), "CountryId", "CountryName");
model.DisplayMode = "WriteOnly";
return View(model);
}
}
Now on the View
#Html.PagedListPager(Model, page => Url.Action("Index", new {page, pagesize = 5 }))
Is accepted only if i decorate my View Model with IPagedList
#model PagedList.IPagedList<SingleCRUD.Models.CustomersViewModel>
Now as I am using
public IEnumerable<Customer> Customers { get; set; }
On My ViewModdel
The View is not accepting the Customers
#{
foreach (var item in Model.Customers)
{
if (Model.SelectedCustomer != null)
{
if (item.CustomerID ==
Model.SelectedCustomer.CustomerID)
{
#:<tr class="SelectedCustomer">
}
else
{
#:<tr>
}
}
else
{
#:<tr>
}
<td>#item.CustomerID</td>
<td>#item.CompanyName</td>
#*<td><input type="submit"
formaction="/home/select/#item.CustomerID"
value="Select" /></td>*#
<td><input type="submit"
formaction="/home/Edit/#item.CustomerID"
value="Edit" /></td>
<td></td>
#:</tr>
}
}
And Go to definition has stopped on Customers after changing the name space.
My View Model
public class CustomersViewModel
{
public int CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public Nullable<int> PostalCode { get; set; }
public string Country { get; set; }
public Nullable<int> Phone { get; set; }
public Nullable<int> Fax { get; set; }
public IEnumerable<Customer> Customers { get; set; }
public Customer SelectedCustomer { get; set; }
public string DisplayMode { get; set; }
public List<Country> Countries { get; set; }
public SelectList CountryList { get; set; }
}
So I am facing issue at the view level how do I correctly fix it.
Tried these changes
Model
public PagedList<Customer> Customers { get; set; }
View
#model SingleCRUD.Models.CustomersViewModel
#using PagedList;
#using PagedList.Mvc;
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new { page, pagesize = 5 }))
Action
model.Customers = (PagedList<Customer>)db.Customers.OrderBy(m => m.CustomerID).ToPagedList(page ?? 1, 5);
Had to explicitly convert it to Paged List as there was a conversion error not sure whether its correct.
Run Time error on View.
'System.Web.Mvc.HtmlHelper' does not contain a definition for 'PagedListPager' and the best extension method overload 'PagedList.Mvc.HtmlHelper.PagedListPager(System.Web.Mvc.HtmlHelper, PagedList.IPagedList, System.Func)' has some invalid arguments
Error
Error 1 Cannot implicitly convert type 'PagedList.IPagedList' to 'PagedList.PagedList'. An explicit conversion exists (are you missing a cast?)
Using
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new { page, pagesize = 5 }))
on View tried writing this in the form tag as well as out side the form tag.
Its a bit unclear what you claiming. #model PagedList.IPagedList<CustomersViewModel> will not work since your model is CustomersViewModel but it will work if your use #model CustomersViewModel.
If you wanting to display a paged list of Customer, then your model property needs to be
public IPagedList<Customer> Customers { get; set; }
and in the view use
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new {page, pagesize = 5 }))

Bind multiselect selections to list of objects

I have a view model like so:
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
}
One property is an object of type ListingPlan here:
public class ListingPlan
{
public int? ListingPlanID { get; set; }
public int DescriptionLinesCount { get; set; }
public List<Directory> Directories { get; set; }
}
The object Directory looks like this:
public class Directory
{
public int DirectoryID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I have a controller that returns a ListingPlanEditorViewModel to the view:
public ActionResult ConfigurePlan(int? listingIdentifier)
{
ListingPlan plan = new ListingPlan()
{
DescriptionLinesCount = 10,
Directories = new List<Directory>()
{
new Directory()
{
DirectoryID = 3
},
new Directory()
{
DirectoryID = 4
}
}
};
ListingPlanEditorViewModel model = new ListingPlanEditorViewModel()
{
Plan = plan,//_listingRepository.GetListingPlan(listingIdentifier, null),
SiteDirectories = _database.GetDirectories()
};
return View(model);
}
I would like to create a multiselect box that will bind the selected values back to the Plan property in the ListingPlanEditorViewModel, setting the DirectoryID property for each selection. So after binding I should have a List of Directory objects. All with their DirectoryID's set.
I'm having some trouble doing this. I can create the multiselectbox with the correct select options in it, but I am unable to retrieve them in my post action which looks like this:
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.Plan.Directories, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
You have to create an [] or List of IDs in the ViewModel that will store selected values.
public class ListingPlanEditorViewModel
{
public ListingPlan Plan { get; set; }
public IEnumerable<Directory> SiteDirectories { get; set; }
public int[] DirectoryIDs {get;set;}
}
The View will change according. The Directories selected will be stored in DirectoryIDs.
#using (Html.BeginForm("ConfigurePlan", "ListingPlan"))
{
<div class="form-body">
#Html.ListBoxFor(model => model.DirectoryIDs, new MultiSelectList(Model.SiteDirectories, "DirectoryID", "Name"))
</div>
<button type="submit">submit</button>
}
Now on POST Action you can query the database and get the Directories that was selected by user.
Note: You can't just get the full objects because the ListBoxFor will generate a <select multiple ... > ... </select> tag won't know how to bind to your object.

Insert into two tables with no relationships

I am learning LINQ and was wondering how can I INSERT into two different tables with no relationship on one click using LINQ. Is it even possible?
I am adding into one table like this. How can I add into the second table as well?
Second Table,
GenreId
Name
Description
Code,
[HttpPost]
public ActionResult Create(Artist artist)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(new Genre { GenreId = 1, Name = "Some Genre", Description = "Trying to add second table" }); // how can i add the genre object here
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
Note that my View is strongly typed to Artist class.
My View,
#model EntityFrameWorkDBFirst.Models.Artist
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model =>model.Genres) // how to get this working
My Model Class,
public partial class Artist
{
public Artist()
{
this.Albums = new HashSet<Album>();
this.Genres = new HashSet<Genre>();
}
public int ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Album> Albums { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
Tuple related suggestion didn't work. Maybe I am missing something.
#model Tuple<EntityFrameWorkDBFirst.Models.Artist, EntityFrameWorkDBFirst.Models.Genre>
<legend>Create a Artist</legend>
#Html.TextBoxFor(model => model.Item1.Name)
<h2>Genre</h2>
#Html.TextBoxFor(model => model.Item2.Name)
#Html.TextBoxFor(model => model.Item2.Name)
<p>
<input type="submit" value="Create" />
</p>
Controller Code:
[HttpPost]
public ActionResult Create(Artist artist, Genre genre)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(genre);
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
You mixed domain model and view model.
It's big mistake, you should work only with viewmodel on view.
You should create view model:
public class CreateArtistViewModel
{
public string ArtistName { get; set; }
public int? Genres { get; set; } // if you provide chooser for user
public string GenresName { get; set; } // if user can enter new genre
public string GenresDecription { get; set; } // if user can enter new genre
public IList<Genre> Genres { get; set; }
}
in the post action you should check view model and create artist and genre if user create new gener.

Categories