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

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:

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();

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

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()

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.

why checkbox wont check on page load?

i have the following database table for the Compounds table (chemical compounds/elements in the periodic table) there are typos in table data so ignore them
the data is :
the controller :
public class CheckboxController : Controller
{
//
// GET: /Checkbox/
testdbEntities db = new testdbEntities();
[HttpGet]
public ActionResult Index()
{
var comps = db.Compounds.Select(c => new CompoundModel { Id=c.Id, CompoundName=c.Name, IsSelected=c.IsSelected}).ToList();
CheckboxVM cvm = new CheckboxVM { checkboxData=comps};
return View(cvm);
}
[HttpPost]
public string Index(IEnumerable<CheckboxVM> collection)
{
return "";
}
}
Model class CompoundModel is:
public class CompoundModel
{
public int Id { get; set; }
public string Code { get; set; }
public string CompoundName { get; set; }
public bool IsSelected { get; set; }
}
and the ViewModel CheckBoxVM:
public class CheckboxVM
{
public string Id { get; set; }
public string CompoundNmae { get; set; }
public bool IsSelected { get; set; }
public IEnumerable<CompoundModel> checkboxData { get; set; }
}
When the page loads it should display check boxes with names and if db table has checked on them (IsSelected=1) then they should be checked.In the post back i need to receive the id, of the user checked checkboxes. At the moment my code does meet the first requirement to check the checked checkboxes based on IsSelected on page load. Is there a way to fix this?
If you need a video with debugging please ask i will be happy to post : )
THE VIEW: (UPDATE)
#model recitejs1.Models.CheckboxVM
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
foreach (var item in Model.checkboxData)
{
#Html.CheckBoxFor(x=>x.IsSelected, (item.IsSelected)?new{#checked="check"}:null)#item.CompoundName
#Html.HiddenFor(x=>x.Id, item.Id)
#Html.HiddenFor(x=>x.CompoundNmae, item.CompoundName)
}
<br><br>
<input type="submit" name="name" value="Send" />
}
You cannot use a foreach loop to generate form controls. It generates duplicate name attributes (that have no relationship to your model) and duplicate id attributes (invalid html).
Create a custom `EditorTemplate for your model
In /Views/Shared/EditorTemplates/CompoundModel.cshtml
#model recitejs1.Models.CompoundModel
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.CompoundName)
#Html.CheckBoxFor(m => m.IsSelected)
#Html.LabelFor(m => m.CompoundName)
Then in the main view
#model recitejs1.Models.CheckboxVM
....
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.checkboxData)
<input type="submit" name="name" value="Send" />
}
The EditorFor() method will generate the correct html for each item in your collection
Note: You should inspect the html before and after you make this change to better understand how model binding works.
Note also that your POST method parameter needs to be
public string Index(CheckboxVM model)
since that's what the view is based on. However the only property of CheckboxVM that you use in the view is IEnumerable<CompoundModel> checkboxData in which case your view should be
#model IEnumerable<CompoundModel>
...
#Html.EditorFor(m => m)
and keep the POST method as it is (but change the GET method)

How to pass List<model> to controller in MVC 4

I have 2 model : Question and Answer such as below, I want to send a List model to View, and when submit form, i submit List model to controller, but in Action UpdateQuestion a can only get the list of question but the list of answer was not. Can you explain and show me how to get list answer of each question when i submit form
public class Question
{
[Key]
public int Id { get; set; }
[ForeignKey("QuestionType")]
public int QuestionTypeId { get; set; }
public virtual QuestionType QuestionType { get; set; }
[ForeignKey("Field")]
public int FieldId { get; set; }
public virtual Field Field { get; set; }
public string Brief { get; set; }
public bool IsGroup { get; set; }
[ForeignKey("QuestionGroup")]
public int? QuestionGroupId { get; set; }
public virtual QuestionGroup QuestionGroup { get; set; }
public int Priority { get; set; }
public int Order { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
and:
public class Answer
{
[Key]
public Int32 Id { get; set; }
[Column(TypeName = "ntext")]
[MaxLength]
public string Content { get; set; }
[ForeignKey("Question")]
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
public float Mark { get; set; }
public int Priority { get; set; }
}
I have controller Index to passing a list of Question to View:
public ActionResult Index()
{
ApplicationDbContext db = new ApplicationDbContext();
var listQuestion = db.Questions.ToList();
return View(listQuestion);
}
[HttpPost]
public ActionResult UpdateQuestion(string submit, List<Question> Questions)
{
...
return RedirectToAction("Index");
}
And In View :
#model List<Question>
#{
int i = 0;
int j = 0;
}
#using (Html.BeginForm("UpdateQuestion", "TestRoom"))
{
<ul>
#foreach(var question in Model)//Question
{
<li>
#Html.Hidden("Questions["+i+"].Id", question.Id)
#{i++;}
#Html.Raw(question.Brief)
<ul>
#foreach (var answers in question.Answers)
{
<li>#Html.RadioButton("Questions["+i+"]_Answers["+j+"]",answers.Id)
#Html.Raw(answers.Content)
#{j++;}
</li>
}
#{j = 0;}
</ul>
</li>
}
</ul>
<div class="aq-button-panel">
<button type="submit" value="Finish" name="submit"><i class="icon-pencil"></i>Submit</button>
<button type="submit" value="Back" name="submit">Go Next <i class="icon-arrow-left"></i></button>
<button type="submit" value="Next" name="submit">Go Back <i class="icon-arrow-right"></i></button>
</div>
}
There are multiple issues with you code. First you cannot bind a radio button to a complex object (in your case Answer because a radio button group only posts back a single value (in your case the id value of the selected Answer). Next you loops are generating radio buttons groups that would be attempting to bind the selected answer to only the first answer which makes no sense (your setting the value of j to 0 each time). Your model needs a property to bind to (say) int SelectedAnswer.
Start by creating view models that represent what you want to display/edit in your view (add display and validation attributes as required)
public class AnswerVM
{
public int ID { get; set; }
public string Content { get; set; }
}
public class QuestionVM
{
public int ID { get; set; }
public string Brief { get; set; }
public int SelectedAnswer { get; set; }
public IEnumerable<AnswerVM> PossibleAnswers { get; set; }
}
In your get method, get your data models and map then to the view models and return IEnumerable<QuestionVM> to the view.
Next create an EditorTemplate for typeof QuestionVM (/Views/Shared/EditorTemplates/QuestionVM.cshtml)
#model QuestionVM
<li>
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.Brief)
<ul>
#foreach(var answer in Model.PossibleAnswers)
{
<li>
<label>
#Html.RadioButtonFor(m => m.SelectedAnswer, answer.ID, new { id = "" })
<span>#answer.Content</span>
</label>
</li>
}
</ul>
</li>
and in the main view
#model IEnumerable<QuestionVM>
....
#Html.BeginForm(...))
{
<ul>
#Html.EditorFor(m => m) // this will generate the correct html for each question in the collection
</ul>
<div class="aq-button-panel">
<button type="submit" ... />
...
</div>
}
and change the POST method to
[HttpPost]
public ActionResult UpdateQuestion(string submit, IEnumerable<QuestionVM> model)
The model now contains the ID of each question and the ID of the selected answer for each question.
Note that if you need to return the view because ModelState is invalid, you will need to repopulate the PossibleAnswers property of each question (your not generating a form control for each property of each Answer in each Question - and nor should you) so the PossibleAnswers property will be an empty collection when you submit the form)