Problem when submit a form with select tag helper asp.net core 6 - asp.net-core

I want to use select tag helper to choose a role for creating account in my web app.
The following is my code
[HttpGet]
public ActionResult Create()
{
var model = new AccountCreateViewModel()
{
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
};
return View(model);
}
The following is the code in view of the select.
<div class="form-floating">
<select asp-for="RoleId" asp-items="#Model.Roles" class="form-select"></select>
<label asp-for="RoleId"></label>
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<input type="submit" class="w-100 btn btn-lg btn-primary" />
The following is my model
public class AccountCreateViewModel
{
public RegisterModel.InputModel Input { get; set; } = new();
[StringLength(50)]
[Required]
public string FullName { get; set; }
[DataType(DataType.Date)]
public DateTime? BirthDate { get; set; } = null;
[StringLength(80)]
public string Address { get; set; }
[Required]
[Display(Name = "Role")]
public string RoleId { get; set; }
public List<SelectListItem> Roles { get; set; }
}
However, after I submit the form, then the controller check the model state, and it is invalid. I have debugged and all the fields is valid except Roles.
So, someone can give me a solution for this situation?
model state debugging

Apply [ValidateNever] attribute to remove validate of the Roles on the server side. When applied to a property, the validation system excludes that property.
public class AccountCreateViewModel
{
...
[ValidateNever]
public List<SelectListItem> Roles { get; set; }
}

Consider that a SelectList takes an object of type IEnumerable as the first argument in the constructor. Make sure that you give it an IEnumerable or List in this section:
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
This code may help you:
Roles = new SelectList(_roleManager.Roles.ToList(), "Id", "Name")
If _roleManager.Roles returns an IEnumerable or List, you don't need the .ToList()

Related

Razor pages binding dropdownlist to complex type database not binding viewmodel

I got a dropdown list that is populated from a database, It renders fine and the items are shown. The issue comes in when i try to save the model and the viewstate says its invalid for the postCategory Title and description as they are null but does have the Id value from the selection.
my db class is as follows.
public class Article
{
public long ArticleId { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
public ArticleCategories PostCategory { get; set; } //this the problem
}
public class ArticleCategories
{
public long Id { get; set; }
[Required]
[MaxLength(100)]
public string Title { get; set; }
[Required]
[MaxLength(300)]
public string Description { get; set; }
public string Slug { get; set; }
public List<Article> AssociatedPosts { get; set; }
}
In my page model i load the dropdown list as follows.
public ArticleCategories NewArticleCategory { get; set; }
public List<SelectListItem> PostCategories
{
get
{
List<SelectListItem> NewList = new List<SelectListItem>();
NewList = _context.ArticleCategories.Select(a =>
new SelectListItem
{
Value = a.Id.ToString(),
Text = a.Title.ToString(),
}).ToList();
return NewList;
}
}
and on the page
<div class="form-group">
<label asp-for="BlogArticle.PostCategory" class="control-label"></label>
<select asp-for="BlogArticle.PostCategory.Id" class="form-control" asp-items="Model.PostCategories">
<option value="">--Choose a Catergory--</option>
</select>
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Title )
#Html.HiddenFor(m=>m.BlogArticle.PostCategory.Description )
<span asp-validation-for="BlogArticle.PostCategory" class="text-danger"></span>
</div>
It only select the Id so tried to attach it by retrieving it from the db.
var PostCategory = _context.ArticleCategories.Where(c => c.Id == BlogArticle.PostCategory.Id).FirstOrDefault();
if (PostCategory != null)
{
BlogArticle.PostCategory = PostCategory;
}
if (!ModelState.IsValid)
{
return Page();
}
not sure where i am going wrong, if there any advice or suggestions it would be greatly apricated. thank you in advance.
From your code, When you pass data to backend from view, the ArticleCategories model will only have Id value from selection, the values of Title and Description are null because you do not pass any value to their input tag right? modelsate will only validate the model passed from the view. Now the ArticleCategories model passed from the view only has id value, you also add [Required] tag to Title and Description properties, So Title and Description will be invalid in modelsate.
In your code, I think you want ModelSate to validate other properties, So you need to remove Title and Description properties from ModelSate, Please refer to this code :
if (ModelState.Remove("BlogArticle.PostCategory.Title") && ModelState.Remove("BlogArticle.PostCategory.Description"))
{
if(!ModelState.IsValid)
return Page();
}
return Page();

ASP.NET Core filter a list using a multi-select List Box

I have a standard razor page using the "List" template that I have added a dropdown to filter which records are shown. I used the code example from here: https://learn.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/search?view=aspnetcore-3.1
Using the tutorial's "Search by Genre" section.
I have it working but would like users to be able to select multiple items from the list to use as filters. I added "multiple" to the select tag and I can select multiple items, but it only processes the first item selected.
How can I have multiple items added to the filter so that all selected values are displayed on the list?
After adding "multiple" to the select tag, you need to modify the received model MovieGenre. For example:
public class IndexModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string SearchString { get; set; }
public SelectList Genres { get; set; }
[BindProperty(SupportsGet = true)]
public List<string> MovieGenre { get; set; }
public void OnGet()
{
// Transfer data here.
Genres = new SelectList(new List<Genre> {
new Genre{id=1,GenreName="Genre1"},
new Genre{id=2,GenreName="Genre2"},
new Genre{id=3,GenreName="Genre3"},
},"id", "GenreName");
}
}
public class Genre
{
public int id { get; set; }
public string GenreName { get; set; }
}
In Index.cshtml:
<form>
<select asp-for="MovieGenre" asp-items="Model.Genres" multiple>
<option value="">All</option>
</select>
<input type="submit" value="Filter" />
</form>
Then, it will get Multiple MovieGenre.
result:

HTTP get request possible without passing very long string to URL?

What is the most efficient way to pass a very long GET request? I see a similar question was posted here: Very long http get request which suggests using a post request instead of get? Why is this?
As an example, I have an application that includes 4 multiselectlists and a dropdown. The user can select several options to filter a table down and display the results. Currently this is included in my onget method by building a string in the URL and then running linq queries to display the results based on the user's selections. It works, but it seems very inefficient to me to pass such a long URL. Isn't there a better way to do this with model binding?
.cshtml file:
<select multiple class="form-control" name="CurSelDepts" asp-items="Model.DeptList" asp-for="SelDepts"></select>
<select multiple class="form-control" name="CurSelTechs" asp-items="Model.TechOneList" asp-for="SelTechs"></select>
<select multiple class="form-control" name="CurSelTech2s" asp-items="Model.TechTwoList" asp-for="SelTech2s"></select>
<select multiple class="form-control" name="CurSelRoles" asp-items="Model.RoleList" asp-for="SelRoles"></select>
<select class="form-control col-md-6" name="CurSelEmp" asp-items="Model.EmployeeList" asp-for="SelEmp">
<option disabled selected style="display:none">--select--</option>
</select>
<input formmethod="get" type="submit" value="Search" class="btn btn-primary btn-sm" id="searchbtn" />
.cs file:
public MultiSelectList DeptList { get; set; }
public MultiSelectList TechOneList { get; set; }
public MultiSelectList TechTwoList { get; set; }
public SelectList EmployeeList { get; set; }
public MultiSelectList RoleList { get; set; }
public int SelEmp { get; set; }
public int SelNewEmp { get; set; }
public int[] SelRoles { get; set; }
public int[] SelDepts { get; set; }
public int[] SelTechs { get; set; }
public int[] SelTech2s { get; set; }
public async Task OnGetAsync(int[] selRoles, int[] curSelRoles, int selEmp, int curSelEmp, int[] selDepts, int[] curSelDepts, int[] selTechs, int[] curSelTechs, int[] selTech2s, int[] curSelTech2s)
{
DeptList = new MultiSelectList(_context.ppcc_deptCds, "Id", "dept_cd", SelDepts);
TechOneList = new MultiSelectList(_context.ppcc_techCds, "Id", "tech_cd", SelTechs);
TechTwoList = new MultiSelectList(_context.ppcc_techTwoCds, "Id", "tech_cd_two", SelTech2s);
RoleList = new MultiSelectList(_context.ppcc_roles, "Id", "role_nm", SelRoles);
EmployeeList = new SelectList(_context.employees, "Id", "employee_nm", SelEmp);
SelEmp = curSelEmp;
SelDepts = curSelDepts;
SelTechs = curSelTechs;
SelTech2s = curSelTech2s;
SelRoles = curSelRoles;
IQueryable<ppcc_matrix> ppcc_matrixIQ = from s in _context.ppcc_matrices select s;
if (curSelDepts.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelDepts.Contains(s.ppcc_deptCdId));}
if (curSelTechs.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelTechs.Contains(s.ppcc_techCdId));}
if (curSelTech2s.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelTech2s.Contains(s.ppcc_techTwoCdId));}
if (curSelRoles.Any()) {ppcc_matrixIQ = ppcc_matrixIQ.Where(s => curSelRoles.Contains(s.ppcc_roleId));}
if (curSelEmp != 0) { ppcc_matrixIQ = ppcc_matrixIQ.Where(s => s.employeeId.Equals(curSelEmp)); }
}
Model Binding works with GET requests as well as POST requests. You just need to ensure that the public properties are decorated with the BindProperty attribute with SupportsGet = true (https://www.learnrazorpages.com/razor-pages/model-binding#binding-data-from-get-requests):
[BindProperty(SupportsGet=true)]
public Type MyProperty { get; set; }
As to very long URLs, there is a limit to the length of a GET request. POST requests also have limits imposed by servers, but it is very much larger by default. It needs to be to cater for file uploads, for example.

Dropdown List MVC 4 error

I am trying to get a drop down list to work but its not working for me. This application is mainly a festival based application where you can add a festival along with your events. The error I am getting is on line:
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
This is the error I get:
There is no ViewData item of type 'IEnumerable' that has the key 'towns'.
Create.cshtml
<div class="form-group">
#Html.LabelFor(model => model.FestivalTown, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("towns", (IEnumerable<SelectListItem>)ViewData["Town"], new{#class = "form-control", #style="width:250px" })
#Html.ValidationMessageFor(model => model.FestivalTown)
</div>
#*#Html.Partial("ddlFestivalCounty");*#
</div>
Controller.cshtml
//Get
List<SelectListItem> Towns = new List<SelectListItem>();
Towns.Add(new SelectListItem { Text = "Please select your Town", Value = "SelectTown" });
var towns = (from t in db.Towns select t).ToArray();
for (int i = 0; i < towns.Length; i++)
{
Towns.Add(new SelectListItem
{
Text = towns[i].Name,
Value = towns[i].Name.ToString(),
Selected = (towns[i].ID == 0)
});
}
ViewData["Town"] = Towns;
//Post
festival.FestivalTown.Town = collection["Town"];
Model.cs
public class Festival
{
public int FestivalId { get; set; }
[Required]
[Display(Name = "Festival Name"), StringLength(100)]
public string FestivalName { get; set; }
[Required]
[Display(Name = "Start Date"), DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[Display(Name = "End Date"), DataType(DataType.Date)]
public DateTime EndDate { get; set; }
[Required]
[Display(Name = "County")]
public virtual County FestivalCounty { get; set; }
[Display(Name = "Festival Location")]
public DbGeography Location { get; set; }
[Required]
[Display(Name = "Town")]
public virtual Town FestivalTown { get; set; }
[Required]
[Display(Name = "Festival Type")]
public virtual FestivalType FType { get; set; }
public UserProfile UserId { get; set; }
}
public class Town
{
public int ID { get; set; }
[Display(Name = "Town")]
public string Name { get; set; }
}
I suspect that this error occurs when you submit the form to the [HttpPost] action and not when you are rendering the form, right? And this action renders the same view containing the dropdown, right? And inside this [HttpPost] action you forgot to populate the ViewData["Town"] value the same way you did in your HttpGet action, right?
So, go ahead and populate this property the same way you did in your GET action. When you submit the form to your [HttpPost] action, only the selected value is sent to the controller. So you need to repopulate the collection values if you intend to redisplay the same view, because this view renders a dropdown which is attempting to bind its values from ViewData["Town"].
And here's what I mean in terms of code:
[HttpPost]
public ActionResult SomeAction(Festival model)
{
... bla bla bla
// don't forget to repopulate the ViewData["Town"] value the same way you did in your GET action
// if you intend to redisplay the same view, otherwise the dropdown has no way of getting
// its values
ViewData["Town"] = ... same stuff as in your GET action
return View(model);
}
And all this being said, I would more than strongly recommend you using view models instead of this ViewData/ViewBag weakly typed stuff. Not only that your code will become much more clean, but even the error messages will start making sense.

generate dropdownlist from a table in database

I'm tryng to be more precise to my previous question which can be found here, I got some nice answers but couldn't figure out how to use it in my situation Previous question
I got some nice answers but couldn't figure out how to use it in my situation.
basically I want to have registration page which contains
Email //Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
UserName//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Password//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Role//dropdownlist, comes from Role(datamodel) class, also Roles table exists in database
In my controller I have impelmented my Register method in following way:
public class AccountController : Controller
{
//private readonly IDbContext dbContext;
//
// GET: /Account/
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
[HttpGet]
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
return View(roles);
}
[HttpPost]
public ActionResult Register(AspNetUser model)
{
return View();
}
}
in my get method i'm passing the roles to view and right now i'm using AspNetUser as model in View
#model Sorama.CustomAuthentiaction.Models.AspNetUser
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.Email, new{#placeholder = "Email"})</div>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.UserName, new{#placeholder = "UserName"})</div>
<div class ="input-block-level">#Html.PasswordFor(model=>model.Password, new{#placeholder ="Password"})</div>
<div class ="input-block-level">#Html.DropdownlistFor(.....//don't no how to generate dropdownlist)
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
can u tell me how to get that dropdownlist and how can I pass that selected value to controller to use it so that i can put user in role during registration? Would it be better to create new model for Registration?
Edit: AspNetUser model
public class AspNetUser
{
private ICollection<Role> _roles= new Collection<Role>();
public Guid Id { get; set; }
[Required]
public virtual String Username { get; set; }
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
public virtual String FirstName { get; set; }
public virtual String LastName { get; set; }
[DataType(DataType.MultilineText)]
public virtual String Comment { get; set; }
public virtual Boolean IsApproved { get; set; }
public virtual int PasswordFailuresSinceLastSuccess { get; set; }
public virtual DateTime? LastPasswordFailureDate { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLockoutDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual String ConfirmationToken { get; set; }
public virtual DateTime? CreateDate { get; set; }
public virtual Boolean IsLockedOut { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual String PasswordVerificationToken { get; set; }
public virtual DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles
{
get { return _roles; }
set { _roles = value; }
}
}
You'd better have a view model specifically designed for this view. Think of what information you need in the view and define your view model:
public class RegisterViewModel
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string SelectedRole { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
}
As you can see from this view model, in order to have a dropdown list you need 2 properties: one scalar property that will hold the selected value and one collection property to hold the list of available values.
and then:
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
var model = new RegisterViewModel();
model.Roles = roles.Select(r => new SelectListItem
{
Value = r,
Text = r,
});
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
// the model.SelectedRole will contain the selected value from the dropdown
// here you could perform the necessary operations in order to create your user
// based on the information stored in the view model that is passed
// NOTE: the model.Roles property will always be null because in HTML,
// a <select> element is only sending the selected value and not the entire list.
// So if you intend to redisplay the same view here instead of redirecting
// makes sure you populate this Roles collection property the same way we did
// in the GET action
return Content("Thanks for registering");
}
and finally the corresponding view:
#model RegisterViewModel
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.Email, new { placeholder = "Email" })
</div>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.UserName, new { placeholder = "UserName" })
</div>
<div class ="input-block-level">
#Html.PasswordFor(model => model.Password, new { placeholder = "Password" })
</div>
<div class ="input-block-level">
#Html.DropdownlistFor(model => model.SelectedRole, Model.Roles)
</div>
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>