Why this Model Binding not working in Razor Page - asp.net-core

I am using ASP.NET Core 3.1 with a simple example to test model binding to post a form. The property to be bound is an object named "Student". Bud the model binding is not working with post method. I will appreciate any help to point out what's wrong here.
Here are the code of my test programs:
'Student Class':
namespace ModelBindPost.Models
{
public class Student
{
public int Id;
public string FirstName;
public string LastName;
}
}
'Edit.cshtml.cs'
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using ModelBindPost.Models;
namespace ModelBindPost.Pages
{
public class EditModel : PageModel
{
[BindProperty(SupportsGet = true)]
public Student Student { get; set; }
public EditModel()
{
Student = new Student();
}
public IActionResult OnGet()
{
Student.Id = 1;
Student.FirstName = "Jean";
Student.LastName = "Smith";
return Page();
}
public IActionResult OnPost()
{
string name = this.Student.FirstName;
return Page();
}
}
}
' Edit.cshtml':
#page
#model ModelBindPost.Pages.EditModel
#{
}
<h2>Model Binding Test</h2>
<form method="post">
<div class="form-group">
<lable asp-for="Student.Id"></lable>
<input asp-for="Student.Id" class="form-control" />
</div>
<div class="form-group">
<lable asp-for="Student.FirstName"></lable>
<input asp-for="Student.FirstName" class="form-control" />
</div>
<div class="form-group">
<lable asp-for="Student.LastName"></lable>
<input asp-for="Student.LastName" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>

A simple public field cannot work for model binding. You need add getter and setter to create property like below:
public class Student
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

Related

MVC checkbox list bounding to model

I am trying to collect all the options that the user have selected for a checkbox list. The checkbox list is built using a foreach loop and I have a int[] that I am trying to put the id into. Any help would be great.
View
#{
int idxFormats = 0;
foreach (var item in Model.ListOfFormats)
{
<div class='col-md-6'>
<input type="checkbox" value=#item.Value name="chkFormat" />
<label asp-for=#item.Selected>#Html.Raw(#item.Name)</label>
#Html.HiddenFor(m => Model.selectedFormats[idxFormats]);
</div>
idxFormats++;
}
#Html.ValidationMessageFor(model => model.selectedFormats[idxFormats])
}
Model
public List<GenericValues> ListOfFormats { get; set; }
[Display(Name = "At least one 'Format' must be selected")]
public int[] selectedFormats { get; set; }
Change the Checkbox name to selectedFormats
<input type="checkbox" value=#item.Value name="selectedFormats" />
Test example:
Model:
public class Test
{
public List<GenericValues> ListOfFormats { get; set; }
public int[] selectedFormats { get; set; }
}
public class GenericValues
{
public int Value { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
View:
#model Test
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<form method="post">
#{
foreach (var item in Model.ListOfFormats)
{
<div class='col-md-6'>
<input type="checkbox" value=#item.Value name="selectedFormats" />
<label asp-for=#item.Selected>#Html.Raw(#item.Name)</label>
</div>
}
}
<input type="submit" value="submit" />
</form>
Controller:
public IActionResult Index()
{
Test test = new Test
{
ListOfFormats = new List<GenericValues>
{
new GenericValues
{
Name = "A",
Value = 1,
},
new GenericValues
{
Name = "B",
Value = 2,
},
new GenericValues
{
Name = "C",
Value = 3,
}
}
};
return View(test);
}
[HttpPost]
public IActionResult Index(Test test)
{
return Ok();
}
Result:
if you are looking to put id as value of your idxFormats then use this code in your checkbox:
<input type="checkbox" value=#item.Value name="chkFormat" id="#idxFormats" />
Edit:
I am not so familiar with c#, I tested with minimum code :
// Replace the model correct path by yours
#model IEnumerable<WebApplication1.Models.MyModels.ListOfFormats>
#{
int idxFormats = 0;
foreach (var item in Model)
{
<div class='col-md-6'>
<input type="checkbox" value=#item.Value name="chkFormat" id="#idxFormats"/>
<label>#Html.Raw(#item.Name)</label>
</div>
idxFormats++;
}
}

Simplest way to pass an object in razor pages

I'm new to Razor and I'm trying to pass data from "Index.html" to "Quiz.html" but I'm struggling to put the pieces together using different tutorials. This is where I got so far. I'm trying to do it without using a service but I'm not sure if it's possible.
Thanks!
Index.html
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome, #Model.Name</h1>
<p>This is My Quiz App</a>.</p>
<form method="post">
<label>What's your name?</label>
<input type="text" asp-for="#Model.Visitor.Name">
<button type="submit">Send</button>
</form>
</div>
Index.cs
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
[ViewData]
[BindProperty]
public string Name { get; set; }
public Visitor Visitor { get; set; }
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
Name = "Stranger";
}
public IActionResult OnPost()
{
return RedirectToPage("/Quiz", new { Visitor.Name });
}
}
}
Quiz.html
#page
#model QuizModel
#{
ViewData["Title"] = "Quiz Page";
}
<div class="text-center">
<h1 class="display-4">Thanks for checking my first website out, #Model.Name. Are you ready?</h1>
<p>Let's see how much you know about me.</a>.</p>
</div>
Quiz.cs
public class QuizModel : PageModel
{
[ViewData]
[BindProperty]
public string Name { get; set; }
public Visitor Visitor { get; set; }
public void OnGet()
{
Name = ?
}
You can directly submit the form from Index.cshtml to Quiz.cs with asp-page tag helper. Define a OnPost handler in Quiz.cs
public class QuizModel : PageModel
{
[ViewData]
[BindProperty]
public string Name { get; set; }
[BindProperty]
public Visitor Visitor { get; set; }
public void OnGet()
{
}
public void OnPost()
{
Name = Visitor.Name;
}
}
Index.cshtml:
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome, #Model.Name</h1>
<p>This is My Quiz App.</p>
<form asp-page="Quiz" method="post">
<label>What's your name?</label>
<input type="text" asp-for="Visitor.Name">
<button type="submit">Send</button>
</form>
</div>
Result:

How to solve empty model trouble using both Ajax.BeginForm and Html.BeginForm in MVC

I am new to MVC and stuck in passing modal to controller.
I have read many similar threads in SO, to no avail.
Here, I have a view for entering order details.
User will enter order item details (using ajax.BeginForm) and when he clicks save, whole order will be saved in backend (using Html.BeginForm). Ajax.BeginForm is working properly and passing + displaying records properly. But Html.BeginForm is passing model as nothing.
Here is my code ...
My Models
public class OrderItemsModel
{
public string SrNo { get; set; }
public int? ItemCode { get; set; }
public string ItemName { get; set; }
public Decimal? Qty { get; set; }
public Decimal? Rate { get; set; }
public Decimal? Amount { get; set; }
}
public class OrderModel
{
public string OrderNumber { get; set; }
public string OrderDate { get; set; }
public int? CustomerCode { get; set; }
public string CustomerName { get; set; }
public string Note { get; set; }
//List of items selected in Order
public List<OrderItemsModel> ItemsSelected { get; set; }
}
Extract from My View
#model OrderApplication.Models.OrderModel
#{
ViewBag.Title = "Index";
Model.ItemsSelected = ViewBag.getlist;
}
#using (Ajax.BeginForm("UpdateItemList", "Order", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "selectedtable" }))
{
<h2 style="margin-left:5%;">Order Entry</h2>
//Order No & Date
<div class="row">
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderNumber, "OrderNo"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderNumber, new { #class = "form-control", #readonly = "readonly" })
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderDate, "Date"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderDate, new { #class = "form-control" })
</div>
</div>
</div>
<br />
//Table of entries
<div id="selectedtable">
#Html.Partial("_selectTable", Model);
</div>
<br />
}
#*Main Save*#
<div class="row">
<div class="col-sm-12">
<div class="col-sm-3">
#using (Html.BeginForm("SaveData", "Order", new { order = Model, id = "loginform", #class = "justify-content-center" }))
{
<input type="submit" value="Save Order" class="btn btn-success" />
}
</div>
<div class="col-sm-3">
<input type="button" class="btn btn-success" value="Clear Form" onclick="location.href='#Url.Action("Clear", "Order")'" />
</div>
</div>
</div>
My Controller
public class OrderController : Controller
{
public List<OrderItemsModel> dd = new List<OrderItemsModel>() ;
[HttpPost]
public ActionResult SaveData(OrderModel order, string id)
{
if (order == null) //order is always Nothing
{
return View(order);
}
if (order.CustomerCode == 0)
{
return View(order);
}
return View(order);
}
}
}
You shouldn't use both Ajax.BeginForm and Html.BeginForm, as it won't work. Please, check this post as might be of help to decide which one you wish to choose:
https://forums.asp.net/t/1757936.aspx?When+to+use+Html+BeginForm+vs+ajax+BeginForm
If you still want to use Html.BeginForm, just move the using sentence to replace your Ajax.BeginForm at the top of the page, so the form covers all fields, and the model won't be empty.

Asp.net core Identity multiple roles to user

I am working on asp.net core 3.1 project. There are several types of users in project. For example :
Some of users type
Admin
writer
Manager
Accountants
Operator
and ...
Users type are different and may increase or decrease. We may have 20 writers or 100 operators users in this project that all writers or all operators have same roles.
Now i can set roles to user manually. But i want to set a bunch of roles to users.
For example if user type was operator all roles depends on operator set to user.
I create a simple demo which you could choose UserType when you register a user. And assign all related roles of the UserType to user.
1.Models:
public class ApplicationUser : IdentityUser
{
[ForeignKey("UserType")]
public int UserTypeId {get;set;}
public UserType UserType { get; set; }
}
public class UserType
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<ApplicationUser> Users { get; set; }
public List<ApplicationRole> Roles { get; set; }
}
public class ApplicationRole : IdentityRole
{
[ForeignKey("UserType")]
public int? UserTypeId {get;set;}
public UserType UserType { get; set; }
}
2.DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<UserType> UserTypes { get; set; }
public DbSet<ApplicationRole> AppRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasOne(c => c.UserType)
.WithMany(u=>u.Users)
.OnDelete(DeleteBehavior.Restrict);
}
}
3.Register.cshtml.cs
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public int UserTypeId { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ViewData["UserType"] = new SelectList(_context.UserTypes, "Id", "Name");
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, UserTypeId = Input.UserTypeId };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
//add bunch of roles to user
var roles = _context.AppRoles.Where(r => r.UserTypeId == user.UserTypeId).Select(r => r.Name).ToList();
foreach(var role in roles)
{
await _userManager.AddToRoleAsync(user, role);
}
//...
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
4.Register.cshtml
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.UserTypeId"></label>
<select asp-for="Input.UserTypeId" asp-items="#ViewBag.UserType" class="form-control"></select>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
4.Startup.cs
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();

How to capture multiple checkboxes and radio buttons posted data by viewmodel ASP.Net MVC

i am new in MVC. so this is my html looks like
<form id='your-form' action='#Url.Action("Action","Controller")' method='post'>
<b>Gender</b><br />
<input type='radio' name='gender' value='Male' /> Male <br />
<input type='radio' name='gender' value='Female' /> Female <br />
<hr />
<b>Hobbies</b><br />
<input type='checkbox' name='hobbies' value='Reading' /> Reading <br />
<input type='checkbox' name='hobbies' value='Sports' /> Sports <br />
<input type='checkbox' name='hobbies' value='Movies' /> Movies <br />
<input type='submit' value='Update Profile' />
</form>
this way i am capturing data
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string gender, string[] hobbies)
{
// Example output
var output = String.Format("The user's gender is <b>{0}</b> and they enjoy <b>{1}</b>", gender, String.Join(",", hobbies));
return Content(output);
}
}
but i like to know how could i capture it by viewmodel concept. anyone can help me with sample code. thanks
View models
public class HobbyVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class PersonVM
{
....
public string Gender { get; set; } // would be better to use an enum
public List<HobbyVM> Hobbies { get; set; }
}
In the GET method, initialize an instance of the view model and pass it to the view
public ActionResult Create()
{
PersonVM model = new PersonVM()
{
Hobbies = new List<HobbyVM>()
{
new HobbyVM(){ ID = 1, Name = "Reading" },
new HobbyVM(){ ID = 2, Name = "Sports" },
....// etc these would actually be loaded from a repository
}
};
return View(model);
}
[HttpPost]
public ActionResult Create(PersonVM model)
{
// map you view model to a instance of your data model
// save and redirect
}
View
#model PersonVM
#Html.BeginForm())
{
....
<label>
#Html.RadioButtonFor(m => m.Gender, "Male", new { id = "" })
<span>Male</span>
</label>
<label>
#Html.RadioButtonFor(m => m.Gender, "Female", new { id = "" })
<span>Female</span>
</label>
for(int i = 0; i < Model.Hobbies.Count; i++)
{
#Html.HidenFor(m => m.Hobbies[i].ID)
#Html.CheckBoxFor(m => m.Hobbies[i].IsSelected)
#Html.LabelFor(m => m.Hobbies[i].IsSelected, Model.Hobbies[i].Name)
}
<input type="submit" />
}