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)
Related
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>
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.
I'm developing an ASP.NET MVC4 App. I actually want to be able to change the email address of a user.
ModifyEmail.cshtml
#model Project.Models.ModifyEmailModel
#using (Html.BeginForm("Manage", "Account")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Change Email Address</legend>
<ol>
<li>
#Html.LabelFor(m => m.OldEmailAddress)
#Html.EditorFor(m => m.OldEmailAddress)
#Html.ValidationMessageFor(m => m.OldEmailAddress)
</li>
<li>
#Html.LabelFor(m => m.NewEmailAddress)
#Html.EditorFor(m => m.NewEmailAddress)
#Html.ValidationMessageFor(m => m.NewEmailAddress)
</li>
<li>
#Html.LabelFor(m => m.ConfirmEmailAddress)
#Html.TextBoxFor(m => m.ConfirmEmailAddress)
#Html.ValidationMessageFor(m => m.ConfirmEmailAddress)
</li>
</ol>
<input type="submit" value="Changer l'addresse email" />
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
AccountModels.cs
namespace Project.Models
{
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string EmailId { get; set; }
public string Details { get; set; }
}
public class ModifyEmailModel
{
[Required(ErrorMessage = "l'addresse email actuelle est requise")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Addresse email actuelle")]
public string OldEmailAddress { get; set; }
[Required(ErrorMessage = "la nouvelle addresse email est requise")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Nouvelle addresse email")]
public string NewEmailAddress { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Confirmer la nouvelle addresse mail")]
[Compare("NewEmailAddress", ErrorMessage = "Le nouvelle addresse email et la confirmation de l'addresse email ne correspondent pas.")]
public string ConfirmEmailAddress { get; set; }
}
}
I can't figure out how to implement the action in the AccountController.cs, Please help!
What I understand from your description, you want an action method to modify the email address. So, you can try the following,
[HttpPost]
public ActionResult Manage(ModifyEmailModel data)
{
if(data.NewEmailAddress == data.ConfirmEmailAddress)
{
// db is your context instance
var u = db.UserProfiles.Where(x => x.EmailId == OldEmailAddress).SingleOrDefault();
u.EmailID = data.NewEmailAddress;
db.SaveChanges();
}
else
string error = "Email ID's don't match";
return View();
}
To display email in your view in you GET method, i.e. the method which is loading the view, put the following,
public ActionResult Manage()
{
// at this point you must have the username for your currently logged in user
var u = db.UserProfiles.Where(x => x.UserName == UserName).SingleOrDefault();
return View(u);
}
The default identity account controller uses the Manage action to update or create a password right? If you want to retain this, I would suggest you create another GET action to something like ManageEmail where you can return the view above that you just created from a link or button click under the original Manage page. Then create a POST action ManageEmail to do the change email/ update async with the new email.
Inside Manage.cshtml, you can call a different get method inside it using Html.Action.
#Html.Partial("_ChangePassword") // change password partial here
#Html.Action("ManageEmail") // this will call the partial view page with your custom model
Inside your AccountController.cs add these 2 methods:
//GET ManageEmail
public ActionResult ManageEmail()
{
var model = new ModifyEmailModel()
{
OldEmailAddress = UserManager.GetEmail(User.Identity.GetUserId()) // this will load the current email for your view
};
return View("_ManageEmail",model); // _ManageEmail is your partial view name
}
//POST ManageEmail
[HttpPost()]
[ValidateAntiForgeryToken()]
public async Task<ActionResult> ManageEmail(ModifyEmailModel data)
{
//some validation here like if email is valid or not existing in the database to avoid duplicate
var user = UserManager.FindById(User.Identity.GetUserId());
await UserManager.UpdateAsync(user);
return RedirectToAction("Manage");
}
Then just edit your partial view to target the ManageEmail POST action.
My code is below:
On page i have a products list with checkbox, code & name of that product.
When selecting multiple checkboxes, on submit click, i need to get the selected checkbox values & save it in the DB.
Product View Model class:
public class ProductVM
{
public ProductVM()
{
this.ProductsList = new List<Product>();
}
public List<Product> ProductsList { get; set; }
public class Product
{
public bool IsSelected { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
}
Product Controller:
[HttpGet]
public ActionResult Edit()
{
var model = new ProductVM();
var product1 = new ProductVM.Product
{
Code = "Product1",
Name = "Apple"
};
var product2 = new ProductVM.Product
{
Code = "Product2",
Name = "Banana"
};
model.ProductsList.Add(Product1);
model.ProductsList.Add(Product2);
return View(model);
}
[HttpPost]
public ActionResult Edit(ProductVM model)
{
if (model.ProductsList.Any(m => m.Selected))
{
//How to get the selected Code & Name here
}
}
Product View:
#model ProductVM
#using(Html.BeginForm())
{
foreach (var item in Model.ProductsList)
{
#Html.CheckBox("IsSelected", item.IsSelected)
#Html.Label(item.Code)
#Html.Label(item.Name)
}
<input type="submit" />
}
First, you need to use for instead of foreach. Otherwise Razor won't generate the proper field names. Second, you need hidden inputs for the Code and Name properties so these will get posted back:
for (var i = 0; i < Model.ProductsList.Count(); i++)
{
#Html.HiddenFor(m => m.ProductsList[i].Code)
#Html.HiddenFor(m => m.ProductsList[i].Name)
<label>
#Html.CheckBoxFor(m => m.ProductsList[i].IsSelected)
#Html.DisplayFor(m => m.ProductsList[i].Code)
#Html.DisplayFor(m => m.ProductsList[i].Name)
</label>
}
To demo my code from my comment to question- You can get idea from it
#foreach (var item in ViewBag.FocusesList)
{
<label>
<input type="checkbox" value="#item.ConfigurationId" name="FocusList" id="FocusList" /> #item.Value.Name
</label>
}
After submit you have all checked values split by comma in Request["FocusList"]
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.