Bind Dropdownlist with TagHelpers - asp.net-core

I am trying to create a dropdownlist and bind with viewbag using ASP.NET Core tag helpers.
I can bind the dropdownlist using:
#Html.DropDownList("Area_id",
new SelectList(ViewBag.Areas, "Value", "Text"),
"Select an Area")
But I have hard time to use ASP.NET Core tag helper on HTML "Select":
<select asp-for="" asp-items="" />
My code always has red line under asp-for="Area_id" saying Area_id is not accessible. I tried to use asp-for="Id" and still doesn't work. The GetAreas() function works fine. Please help!
Model classes:
public class DegreeViewModel
{
public int Degree_id { get; set; }
public int Area_id { get; set; }
public List<SelectListItem> Areas { get; set; }
}
public IActionResult Index()
{
var vm = new DegreeViewModel();
vm.Areas = GetAreas();
return View(vm);
}
Controller:
private MyContext _context;
[BindProperty(SupportsGet = true)]
public int Area_id { get; set; }
[BindProperty(SupportsGet = true)]
public int? Degree_id { get; set; }
public IActionResult Index()
{
var vm = new DegreeViewModel();
vm.Areas = GetAreas();
return View(vm);
}
public List<SelectListItem> GetAreas()
{
var Areas = (from a in _context.Degrees
select new SelectListItem { Text = a.Area_name, Value = a.Area_id.ToString() }).ToList();
Areas.Add(new SelectListItem("Select", "0"));
return Areas;
}
Index.cshtml
#model DegreeViewModel
<form asp-action="GetResult" asp-controller="Home" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label class="control-label">Areas</label>
<select asp-for="Area_id" asp-items="#(new SelectList(Model.Areas,"Value","Text"))" />
<span asp-validation-for="Area_id" class="text-danger"></span>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>

The model's type you passed to page is IEnumerable, So you can't use asp-for="Area_id". You need to use #foreach(var item in model){} and then asp-for="item.Area_id". Because your question is how Bind Dropdownlist with TagHelpers and don't provide the post method, So i will just show how to use TagHelpers to bind dropdownlist.
public IActionResult Index()
{
//If you want to use `<select>`, you need to use specific class `SelectListItem`
List<SelectListItem> test = new List<SelectListItem>();
foreach (var item in GetAreas())
{
test.Add(new SelectListItem { Text = item.Area_name, Value = item.Id.ToString() });
}
ViewBag.Areas = test;
return View();
}
page
<select asp-items="#ViewBag.Areas" name="xxx" Id="xxx"></select>

Related

How to get multiple checkbox/parameter values as a list in ASP.NET MVC

I have following code in my ASP.NET MVC application.
Model class:
public Nullable<bool> isInterestedCSharp { get; set; }
public Nullable<bool> isInterestedJava { get; set; }
public Nullable<bool> isInterestedPython { get; set; }
View:
<div class="form-group">
<label class="control-label col-md-2">
Interested In:
</label>
<div class="col-md-10">
<label class="label-inline">
#Html.CheckBox("CSharp", Model.isInterestedCSharp.Value)
C#
</label>
<label class="label-inline">
#Html.CheckBox("Java", Model.isInterestedJava.Value)
Java
</label>
<label class="label-inline">
#Html.CheckBox("Python", Model.isInterestedPython.Value)
Python
</label>
</div>
</div>
Controller:
public ActionResult EditProfile(tbl_user obj,bool CSharp, bool Java, bool Python)
{
obj.isInterestedCSharp = CSharp == true ? true : false;
obj.isInterestedJava = Java == true ? true : false;
obj.isInterestedPython = Python == true ? true : false;
return View();
}
Here I am getting checkboxes values one by one separately using separate parameter for every checkbox.
Is there any way to pass these checkboxes as a list in view and get these values using list as a instead of using multiple parameters.
In your scenario, I'm afraid you want to use foreach in the view to list all the checkboxes, so you need to change your model like this:
public class testModel{
public string Language { get; set; }
public Nullable<bool> isInterested { get; set; }
}
Then in your controller you may create a list and return it to view:
public ActionResult EditProfile(tbl_user obj,bool CSharp, bool Java, bool Python)
{
var list = new List<testModel>
{
new testModel{ Language="Java",isInterested=true},
new testModel{ Language="CSharp",isInterested=true},
new testModel{ Language="Python",isInterested=false}
};
return View(list);
}
And in the view, you can use foreach to list all the checkboxes:
#model IEnumerable
<div class="col-md-10">
#foreach (var item in Model)
{
<label class="label-inline">
#Html.CheckBox(#item.Language, #item.isInterested.Value)
#item.Language
</label>
}
</div>

Bind select list to complex object in ASP.NET Core

Using ASP.NET Core Razor Pages, is it possible to bind a select list to a complex object without having an ID property in the PageModel?
I have these model classes:
public class Department
{
[HiddenInput]
public int ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "{0} must be specified.")]
[StringLength(50, MinimumLength = 1, ErrorMessage = "{0} has to be {2} to {1} characters long.")]
public string Name { get; set; }
}
public class User
{
[HiddenInput]
public int ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "{0} must be specified.")]
[StringLength(50, MinimumLength = 1, ErrorMessage = "{0} has to be {2} to {1} characters long.")]
public string Name { get; set; }
[Display(Name = "Department")]
[Required(ErrorMessage = "{0} must be specified.")]
public Department Department { get; set; }
}
I've create a view for editing a user, including selecting department. This is my PageModel:
public class EditModel : PageModel
{
private readonly IUserRepository _userRepository;
[BindProperty(BinderType = typeof(UserModelBinder))]
public User UserToEdit { get; set; }
public SelectList DepartmentsSL { get; set; }
public EditModel(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void OnGet(int id)
{
UserToEdit = _userRepository.GetUser(id);
PopulateSelectList();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
PopulateSelectList();
return Page();
}
var success = _userRepository.UpdateUser(UserToEdit);
if (success)
{
return RedirectToPage("./Index");
}
else
{
throw new Exception("Could not save the user.");
}
}
private void PopulateSelectList()
{
var departments = _userRepository.GetAllDepartments();
DepartmentsSL = new SelectList(departments, nameof(Department.ID), nameof(Department.Name), UserToEdit.Department.ID);
}
}
...and this is my view:
#page
#model ProjectName.Pages.Admin.Users.EditModel
#{
ViewData["Title"] = "Edit user";
}
<h1>Edit user</h1>
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="UserToEdit.ID" />
<div class="form-group">
<label asp-for="UserToEdit.Name" class="control-label"></label>
<input asp-for="UserToEdit.Name" class="form-control" />
<span asp-validation-for="UserToEdit.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserToEdit.Department" class="control-label"></label>
<select asp-for="UserToEdit.Department" class="form-control" asp-items="#Model.DepartmentsSL">
<option value="">Choose department</option>
</select>
<span asp-validation-for="UserToEdit.Department" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-page="./Index" class="btn btn-secondary">Cancel</a>
</div>
</form>
As you might have noticed in the PageModel, I'm using a custom modelbinder. I wrote this so that the department of the user is set correctly when the view is posted back.
public class UserModelBinder : IModelBinder
{
private readonly IUserRepository _userRepository;
public UserModelBinder(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
var modelName = bindingContext.ModelName;
var user = new User();
// ID.
var idValue = bindingContext.ValueProvider.GetValue(modelName + ".ID");
if (idValue != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(modelName + ".ID", idValue);
var idString = idValue.FirstValue;
if (!String.IsNullOrEmpty(idString))
{
var id = Convert.ToInt32(idString);
user.ID = id;
}
}
// Department.
var departmentValue = bindingContext.ValueProvider.GetValue(modelName + ".Department");
if (departmentValue != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(modelName + ".Department", departmentValue);
var departmentIdString = departmentValue.FirstValue;
if (!String.IsNullOrEmpty(departmentIdString))
{
var departmentId = Convert.ToInt32(departmentIdString);
user.Department = _userRepository.GetDepartment(departmentId);
}
}
bindingContext.Result = ModelBindingResult.Success(user);
return Task.CompletedTask;
}
}
Now, all this seems to work fine - I can change the name and the department and it is posted back correctly. The validation also works. HOWEVER, when opening this edit view the current department of the user is NOT selected in the select list.
In EditModel.PopulateSelectList() I've tried different things, but none seem to work.
DepartmentsSL = new SelectList(departments, nameof(Department.ID), nameof(Department.Name), UserToEdit.Department);
DepartmentsSL = new SelectList(departments, nameof(Department.ID), nameof(Department.Name), UserToEdit.Department.ID);
DepartmentsSL = new SelectList(departments, nameof(Department.ID), nameof(Department.Name), UserToEdit.Department.Name);
Is it possible to solve this last part?
The solution I've seen so far has been to have a separate property "DepartmentID" in the PageModel and bind to that, and then setting the Department property of User after postback. I guess that would also mean I could get rid of the custom model binder, but I'd really like to see if my current solution could be taken all the way. :)
when opening this edit view the current department of the user is NOT selected in the select list
You can try to remove asp-for="UserToEdit.Department" from your SelectTagHelper and manually set name="UserToEdit.Department", like below.
<select name="UserToEdit.Department" class="form-control" asp-items="#Model.DepartmentsSL">
<option value="">Choose department</option>
</select>
And set selectedValue for SelectList, like below.
DepartmentsSL = new SelectList(departments, nameof(Department.ID), nameof(Department.Name), UserToEdit.Department.ID);
Test Result
Besides, another possible approach is creating a custom tag helper to render <select> element and set selected value based on your actual model data and requirement.

Pass common data between views and controllers

I am new to ASP.NET MVC. Want to pass common data to all pages in the application. Following Pass data to layout that are common to all pages. Still not able to get the value in layout and in the controller.
When user logged in to system I set flag 'UserhasLoggedIn' and redirect to another controller action.In the layout, I have added checks if flag is set to false then menu items should not be displayed and LoginPage should display. When page is redirected to Home Index it doesn't get 'UserhasLoggedIn' flag.
Layout.cshtml:
#using WebApplication2.Models
#model ViewModelBase
<div class="btn-toolbar" style="background-color:dimgrey; padding-left:35px;padding-top:-10px;">
#if (null != Model && (bool)Model.IsuserLoggedIn)
{
foreach (var menuItem in Model.MenuItems)
{
<div class="btn-group">
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">menuItem.MenuName <span class="caret"></span></button>
<ul class="dropdown-menu">
#foreach (var subMenu in menuItem.SubMenu)
{
<li>#Html.ActionLink((string)subMenu.MenuName, (string)subMenu.ActionName, (string)subMenu.ControllerName)</li>
}
</ul>
</div>
}
}
<div class="btn-group pull-right" style="padding-right:35px">
#if (null == Model || !(bool)Model.IsuserLoggedIn)
{
<button class="btn btn-primary" onclick="location.href='#Url.Action("Login", "Account")'">Log in</button>
}
else
{
<button class="btn btn-primary" onclick="location.href='#Url.Action("LogOff", "Account")'">Log out </button>
}
</div>
</div>
I have created common view model that can be used.
public abstract class ViewModelBase
{
public bool IsuserLoggedIn;
}
LoginViewModel:
public class LoginViewModel : ViewModelBase
{
[Required]
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
HomeViewModel:
public class HomeViewModel : ViewModelBase
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
AccountController Login Action:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
return RedirectToAction("Index", "Home", new HomeViewModel() { IsuserLoggedIn = true, MenuItems=null });
}
HomeController Index action: Not able to get the IsUserLoggedIn to true and not even in the Layout.
public ActionResult Index(HomeViewModel baseModel)
{
baseModel.FirstName = "First";
baseModel.LastName = "Last Name";
return View(baseModel);
}
IsuserLoggedIn is a field, not a property and the DefaultModelBinder cannot set its value (it has no setter) so the property is initialized as false in the Index() method (the default value for bool)
Change the base view model to make it a property
public abstract class ViewModelBase
{
public bool IsuserLoggedIn { get; set; }
}
Side note: Just use #if (Model != null && Model.IsuserLoggedIn) - there is no need to cast a bool to a bool

Mvc: Getting the selected value of the dropdownlistFor box

In my domain I have this service
public class StudentService
{
private readonly IStudentRepository _studentRepository;
public StudentService(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public StudentDto DisplayStudentInformation()
{
var objStuSec = _studentRepository.DisplayStudentSection();
return objStuSec;
}
}
Here is my studentDto
public class StudentDto
{
public string StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public List<DepartmentDto> GetAllDepartments;
}
Here is my code for the Home controller
public class HomeController : Controller
{
private StudentService _objStudentService;
public HomeController(StudentService objStudentService)
{
_objStudentService = objStudentService;
}
public ActionResult Index()
{
ViewBag.Message = "This is a Test";
var displayform = _objStudentService.DisplayStudentInformation();
return View(displayform);
}
}
Here is my html for the form
#using System.Net.Mime
#model Zakota.University.Domain.DTO.StudentDto
<form action="" method="post">
<div>
<label>First Name</label>
#Html.TextBoxFor(x=>x.FirstName, new { id = "testid1", name="firstname" })
</div>
<div>
<label>Last Name</label>
#Html.TextBoxFor(x=>x.LastName, new { id = "testid1", name="lastname" })
</div>
<div>
<label>Email Address</label>
#Html.TextBoxFor(x=>x.EmailAddress, new { id = "testid1", name="emailaddress" })
</div>
<div>
<label>Department</label>
#Html.DropDownListFor( x=> x.GetAllDepartments,new SelectList(Model.GetAllDepartments,"DepartmentId","DepartmentDescription"), new {#class = "mydropdown", name="dept"})
</div>
<div>
<label></label>
<input type="submit" value="submit"/>
</div>
</form>
I want to be able to get the selected value of the department from dropdownListFor box. I am getting null as the selected value.
Please assist. All other values are correct. The code below is part of the controller code. I just decided to separate it.
[HttpPost]
public ActionResult Index(StudentDto objstudent)
{
string strFirstName = objstudent.FirstName;
string strLastName = objstudent.LastName;
string strEmailAddress = objstudent.EmailAddress;
string strDept = Request.Form["dept"];
var displayform = _objStudentService.DisplayStudentInformation();
return View(displayform);
}
A <select> element postback a single value. You cannot bind a <select> to a collection of complex objects (in your case List<DepartmentDto>). Start by creating a view model representing what you want to edit
public class StudentVM
{
[Display(Name = "First Name")]
[Required(ErrorMessage = "Please enter your first name")]
public string FirstName { get; set; }
.... // other properties of StudentDto
[Display(Name = "Department")]
[Required(ErrorMessage = "Please select a department")]
public int SelectedDepartment { get; set; }
public SelectList DepartmentList { get; set; }
}
Next, your StudentDto model should not contain a property containing a collection of all departments. Your use of DropDownListFor() suggest each student has only one Department, therefore the property should be `public DepartmentDto> Department;
Controller
public ActionResult Create()
{
StudentVM model = new StudentVM();
ConfigureCreateViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(StudentVM model)
{
if(!ModelState.IsValid)
{
ConfigureCreateViewModel(model); // reassign the select list
return View(model); // return the view so user can correct errors
}
StudentDto student = new StudentDto()
{
FirstName = model.FirstName,
LastName = model.LastName,
EmailAddress = mdoel.EmailAddress,
Department = db.Departments.Find(model.SelectedDepartment) // modify to suit
}
// save StudentDto and redirect
}
private void ConfigureCreateViewModel(StudentVM model)
{
List<DepartmentDto> departments = // call a service to get a collection of all departments
model.DepartmentList = new SelectList(departments, "DepartmentId","DepartmentDescription");
}
View
#model yourAssembly.StudentVM
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
.... // other controls of StudentVM
#Html.LabelFor(m => m.SelectedDepartment)
#Html.DropDownListFor(m => m.SelectedDepartment, Model.DepartmentList, "--Please select--")
#Html.ValidationMessageFor(m => m.SelectedDepartment)
<input type="submit" value="submit"/>
}
Side notes:
Use #Html.LabelFor(m => m.FirstName) rather than <label>First
Name</label>. A <label> is an element associated with a control
(clicking on it sets focus to the associated control) - you usage
does nothing because it is missing the for attribute
The html helper methods correctly give the elements an id and
name attribute. In you case you are generating invalid html by
creating duplicate id attributes (the first 3 elements have
id="testid1") and you should never attempt to set the name
attribute (in the first 3 cases, your just setting it to what it
already is anyway, but in the case of the dropdown, you trying to
change it to name="dept" which fortunately does not work - because
if it did, binding would fail!)
You should also consider adding validation attributes to your view
model properties, e.g. [Required(ErrorMessage="Please enter a first
name")] and in the view including #Html.ValidationMessageFor(m =>
m.FirstName)

What is the name convention of binding a complex ViewModels?

I have these two ViewModels
public class AboutViewModel : ViewModel
{
public override long Id { get; set; }
public override string PageTitle { get; set; }
public override string TitleDescription { get; set; }
public override string ContentTitle { get; set; }
public virtual AboutItemViewModel AboutItem { get; set; }
}
public class AboutItemViewModel
{
public long Id { get; set; }
[AllowHtml]
public string Content { get; set; }
public string ImageUrl { get; set; }
public HttpPostedFileBase FileToUpload { get; set; }
}
Here is my controller:
[ValidateInput(false)]
[ValidateAntiForgeryToken, HttpPost]
public ActionResult Create(long? siteid, long? cid, AboutViewModel model)
{
return View(model);
}
Here is my View:
#using (Html.BeginForm("Create", "About", new { siteid = ViewData["siteid"], cid = ViewData["cid"] },FormMethod.Post,new { enctype = "multipart/form-data", #class = "form-horizontal rtl", autocomplete = "off" }))
{
<div class="controls">
<input type="file" name="FileToUpload" id="FileToUpload" style="margin-right: -9px;">
</div>
<div class="controls">
#Html.ValidationMessageFor(o => o.AboutItem.FileToUpload, "", new { id = "spanfile", #class = "alert alert-block alert-error span3 pull-right", style = "margin-right: 160px;" })
</div>
<div class="control-group pull-left">
<button type="submit" class="btn btn-large" data-toggle="button">Save</button>
</div>
}
How to bind the file to FileToUpload to stop returning me a null?
Except:
If I put it in the main AboutViewModel than it's returns a correct value.
Since the FileToUpload property is in the AboutItem proprety, which is a class property of the parent ViewModel, you need to preface the name of your input element with the property it came from. That's a long way of saying that the name of your file input should be AboutItem.FileToUpload.
<input type="file" name="AboutItem.FileToUpload" id="AboutItem_FileToUpload" />
This should take care of the model binding for you. Additionally, you can test this by using an HTML helper on on of the other properties of the AboutItem class. For instance:
#Html.TextBoxFor(x=>x.AboutItem.Id)
This should render in the HTML
<input type="text" name="AboutItem.Id" id="AboutItem_Id />
EDIT
Apparently the id attribute will be rendered with an underscore instead of a dot. However, since the ID attribute is not used in model binding, it shouldn't really matter.