after OnPost values of variables reset to null in razor pages - asp.net-core

I wanted to add simple functionality to like on my page (that only displays data).
I've page with methods in PageModel behind (this is only example, becouse my page is soo more complicated):
(..)getting DB context and stuff
public string bookName {get;set;}
public string someVariable1 {get;set;}
public List<string> _bookTags {get;set;}
public int LikeCounter {get;set;}
public void OnGet(){
someVariable1 = (from u in _context.table
where something==something
select u.x);
_bookTags= (from u in _context.bookTags
where something==something
select u.y).ToList();
LikeCounter = (from u in _context.likes
where u.book==bookName
select u).Count();
}
public void OnPost(){
likes newLike = new likes();
newLike.userName = HttpContext.User.Identity.Name.Split("\\")[1];
newLike.book = bookName;
_context.likes.add(newLike);
_context.SaveChanges();
}
Page looks like this:
(..)
<div class="col-lg-4">
<form method="post">
<button class="btn btn-reddit" type="submit">LikeThisBook</button>
</form>
Current likes: #Model.LikeCounter
</div>
(..)
#foreach(string x in Model._bookTags){
<div class="border">
#x
</div>
}
(..)
so the problem is when I display page - everything works, but when I click "LikeThisBook" - OnPost method fires, adding the like for this book with userName in it... then page display error:
ArgumentNullException: Value cannot be null. (Parameter 'source')
and in stacktrace:
SomeProjectName.Pages.SomeBookStore.Pages_SomeBookStore_ShowBook.ExecuteAsync() in ShowBook.cshtml
#foreach(string x in Model._bookTags){ <- this is colored red
So it looks like that every variable that was filled in "OnGet" method is now null...
please help :)

For you do not want to reload the page and want to update the counter.The common way is using ajax.
Here is a working demo:
Index.cshtml:
#page
#model IndexModel
<div class="col-lg-4">
<form method="post">
<input name="bookName" />
<button type="button" class="btn btn-reddit" onclick="Add()">LikeThisBook</button>
</form>
Current likes: <label>#Model.LikeCounter</label> #*add this*#
</div>
#foreach (string x in Model._bookTags)
{
<div class="border">
#x
</div>
}
#section Scripts
{
<script>
function Add() {
$.ajax({
type: "post",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (data) {
$("label").html(data);
},
error: function () {
alert("Fail to add");
}
});
}
</script>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly RazorProj3_1Context _context;
public IndexModel(RazorProj3_1Context context)
{
_context = context;
}
[BindProperty]
public string bookName { get; set; }
public string someVariable1 { get; set; }
public List<string> _bookTags { get; set; }
public int LikeCounter { get; set; }
public void OnGet()
{
someVariable1 = (from u in _context.table
where u.Id == 1
select u.x).FirstOrDefault();
_bookTags = (from u in _context.bookTags
select u.y).ToList();
LikeCounter = (from u in _context.likes
//where u.book == bookName
select u).Count();
}
public ActionResult OnPost()
{
like newLike = new like();
newLike.book = bookName;
_context.likes.Add(newLike);
_context.SaveChanges();
LikeCounter = (from u in _context.likes
//where u.book == bookName
select u).Count();
return new JsonResult(LikeCounter);
}
}
Startup.cs:
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
My testing models:
public class likes
{
public int Id { get; set; }
public string book { get; set; }
}
public class BookTag
{
public int Id { get; set; }
public string y { get; set; }
}
public class Table
{
public int Id { get; set; }
public string x { get; set; }
}
Result:

Related

create a field, by many to many relationship on ef core (razor pages)

i want to create a field, by many to many relationship on ef core, this is my book class:
public class Book:BaseModel
{
public string Name { get; private set; }
public string Author { get; private set; }
public List<BookCategory> Categories { get; private set; }
public Book(string name, string author)
{
Name = name;
Author = author;
Categories=new List<BookCategory>();
}
public Book()
{
}
}
and the category class:
public class Category:BaseModel
{
public string Name { get; private set; }
public List<BookCategory> Books { get; private set; }
public Category(string name)
{
Name = name;
Books = new List<BookCategory>();
}
public Category()
{
}
my bookCategory class:
public class BookCategory
{
public long BookId { get; set; }
public long CategoryId { get; set; }
public Book Book { get; set; }
public Category Category { get; set; }
}
and now when i am creating a new book, i want to select multiple items of category and save this items to the categories property in the book:
<div class="form-group">
<label asp-for="?" class="control-label">Category product</label>
<select multiple="multiple" class="form-control" asp-for="?"
asp-items='new SelectList(Model.Categories, "Id", "Name")' >
<option value="0">productCategory</option>
</select>
<span asp-validation-for="Name" class="error"></span>
</div>
this is my repository(just i show here my Categoryrepository, the BookRepository is like this):
(notice: because of my architucture i use CategoryViewModel and BookViewModel instead of Book and Category,
public List<CategoryViewModel> GetAllCategories()
{
return _shopContext.Categories
.Include(x => x.Books)
.ThenInclude(x => x.Book)
.Select(x => new CategoryViewModel
{
Id = x.Id,
Name = x.Name,
//Books = ?
}).ToList();
}
CreateBook Class:
public long Id { get; set; }
public string Name { get; set; }
public List<CategoryViewModel> Categories { get; set; }
Index Model:
public IActionResult OnGetCreate()
{
var book = new CreateBook()
{
Categories = _categoryApplication.GetAllCategories(),
};
return Partial("Create" , book);
}
public JsonResult OnPostCreate(CreateBook command)
{
var book = _bookApplication.Create(command);
return new JsonResult(book);
}
it show me the categories, but when i select the items and push submit, the categories send null,
maybe because i didn't defenited category id in the book class,it's because of using many to many relationship that u should definete category id in a seperate class,and now what should i do?
i use from ? in some lines, it means that i don't know what should i use instead ,
i am using many to many relationship on ef core, i want to create a book with a list of categories, i can see the categories but when i select them and push submit button the categoreies sent as null,
Firstly, it is impossible to receive the value for you use private access modifier.
Secondly, the multiple select listpdownwill match the type of List<int> or int[].
At last, Model bound complex types must not be abstract or value types and must have a parameterless constructor.
The whole working demo for how to receive the multiple select dropdown should be:
Model:
public class Book : BaseModel
{
public string Name { get; set; }
public string Author { get; set; }
public List<BookCategory> Categories { get; set; }
public Book(string name, string author)
{
Name = name;
Author = author;
Categories = new List<BookCategory>();
}
public Book()
{
}
}
public class Category : BaseModel
{
public string Name { get; private set; }
public List<BookCategory> Books { get; private set; }
public Category(string name)
{
Name = name;
Books = new List<BookCategory>();
}
public Category()
{
}
}
public class BookCategory
{
public long BookId { get; set; }
public long CategoryId { get; set; }
public Book Book { get; set; }
public Category Category { get; set; }
}
Page:
#page
#model IndexModel
<form method="post">
<div class="form-group">
<label asp-for="Book.Name" class="control-label">گروه محصول</label>
<select multiple="multiple" class="form-control" asp-for="CategoryIds"
asp-items='new SelectList(Model.Categories, "Id", "Name")'>
<option value="0">گروه محصول</option>
</select>
<span asp-validation-for="Book.Name" class="error"></span>
</div>
<div class="form-group">
<label asp-for="Book.Author" class="control-label">گروه محصول</label>
<input asp-for="Book.Author" />
<span asp-validation-for="Book.Author" class="error"></span>
</div>
<input type="submit" value="Create" />
</form>
PageModel:
public class IndexModel : PageModel
{
[BindProperty]
public Book Book { get; set; }
public List<Category> Categories { get; set; }
[BindProperty]
public int[] CategoryIds { get; set; }
public void OnGet()
{
Categories = ......
}
public void OnPost()
{
//append the value to the Book model
foreach(int id in CategoryIds)
{
Book.Categories = new List<BookCategory>();
Book.Categories.Add(new BookCategory() { BookId = Book.Id, CategoryId = id });
}
//do your stuff for database insert operation...
}
}

how to Sending drowpDown values ​to the input of the action controller using the select2 library?

I want to both the previous tags pass in action controller and the new tags that I enter that do not already exist by Select2
I from Automatic tokenization into tags section in library select2
I want to use
But the new tags that I enter are empty in the action entry
I want to use this part:
https://select2.org/tagging
model:
public class CreateArticleCommand : IRequest<CommandResult>
{
public string Title { get; set; }
public string Description { get; set; }
public List<TagsVm>? Tags { get; set; }
}
action:
[HttpGet]
public async Task<IActionResult> Create()
{
IActionResult viewResult = View(new CreateArticleCommand());
var tags = await _mediator.Send(new GetAllTagsQuery());
if (tags.Status == ApplicationServiceStatus.Ok)
{
viewResult = View(new CreateArticleCommand() { Tags = tags.Data });
}
return viewResult;
}
[HttpPost]
public async Task<IActionResult> Create(CreateArticleCommand articleCommand)
{
IActionResult viewResult = View(articleCommand);
var res = await _mediator.Send(articleCommand);
if (res.Status == ApplicationServiceStatus.Ok)
viewResult = RedirectToAction(nameof(List));
return viewResult;
}
view:
#model CreateArticleCommand
<select asp-for="Tags" class="js-example-tokenizer form-control" multiple="multiple"
asp-items="#(new SelectList(Model.Tags,nameof(TagsVm.Id), nameof(TagsVm.Name)))">
</select>
js:
<script>
$(".js-example-tokenizer").select2({
tags: true,
tokenSeparators: [',', ' ']
})
</script>
slect2 can only pass List<string> or List<int> to action,it cannot be binded to List<TagsVm>? Tags,here is a demo:
public class TagsVm
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CreateArticleCommand : IRequest<CommandResult>
{
public string Title { get; set; }
public string Description { get; set; }
public List<TagsVm>? Tags { get; set; }
public List<int> SelectedIds { get; set; }
}
view:
#model CreateArticleCommand
<select asp-for="SelectedIds" class="js-example-tokenizer form-control" multiple="multiple"
asp-items="#(new SelectList(Model.Tags,nameof(TagsVm.Id), nameof(TagsVm.Name)))">
</select>
action:
[HttpPost]
public async Task<IActionResult> Create(CreateArticleCommand articleCommand)
{
//get selected ids from articleCommand.SelectedIds
...
}

Save navigation property as null

I have a Job model and StatusOnHold model.
I added navigation property StatusOnHold in the Job model.
from some reason, when I'm saving the Job model with an empty StatusOnHold, I'm still getting value in the StatusOnHoldId in the Job model.
when StatusOnHold is empty, I'm trying to receive NULL value in the StatusOnHoldId in the Job model.
when StatusOnHold is not empty, I'm trying to get StatusOnHoldId and save the value in the StatusOnHold model (which it's working like that now).
Thank you so much.
Here is my Models...
public class StatusOnHoldViewModel
{
public int Id { get; set; }
public string Note { get; set; }
}
public class JobViewModel
{
[Key]
public int Id { get; set; }
public string JobNote { get; set; }
public JobStatus JobStatus { get; set; }
public CompanyViewModel Company { get; set; }
public CustomerViewModel Customer { get; set; }
public StatusOnHoldViewModel StatusOnHold { get; set; }
}
Here is the Controller...
public async Task<IActionResult> Create(JobViewModel jobViewModel)
{
if (ModelState.IsValid)
{
var job = _mapper.Map<Job>(jobViewModel);
var newjobId = await _jobRepository.AddAsync(job);
return RedirectToAction("details", new { id = newjobId });
}
return View();
}
And here is the view...
<div class="form-group row">
<label class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<div class="m-1">On-Hold</div>
<textarea asp-for="StatusOnHold.Note" style="height:86px; min-height:86px" class="form-control" placeholder="Reason..."></textarea>
<span asp-validation-for="StatusOnHold.Note" class="text-danger"></span>
</div>
</div>
You don't have property for the StatusOnHoldViewModel the navigation property alone will not work.
So add
public int StatusOnHoldViewModelId { get; set; }
to your JobViewModel
StatusOnHoldId will not be empty and it always have data, if you do not write note in textarea, it will look like { "id":0,"note":null} on action for jobViewModel` ,this wll create a new record.
A workaround is that you could set StatusOnHold as null when note is null:
public async Task<IActionResult> Create(JobViewModel jobViewModel)
{
if (ModelState.IsValid)
{
if(jobViewModel.StatusOnHold.Note == null)
{
jobViewModel.StatusOnHold = null;
}
var job = _mapper.Map<Job>(jobViewModel);
var newjobId = await _jobRepository.AddAsync(job);
return RedirectToAction("details", new { id = newjobId });
}
return View();
}

Encrypted Id is not retaining back in controller

I am encrypting id to hide the raw id in query string and passing it to the controller. But the Id is not retaining back in the postback to the controller.
for eg
/Vendor/EditVendor/mELirpUhRYksFj7k8-XBcQ%3d%3d
DecryptLong() method will decrypt the above id string mELirpUhRYksFj7k8-XBcQ%3d%3d to 1
controller
public ActionResult EditVendor(string id)
{
var vendor = _vendorService.GetVendorById(id.DecryptLong());
return View(vendor);
}
[HttpPost]
public ActionResult EditVendor(Vendor vendor)
{
if (ModelState.IsValid)
{
vendor.Id -- it is always zero and not retaining back
_vendorService.EditVendor(vendor);
}
return View(vendor);
}
In view
#model Eclatech.KidsHub.Objects.Vendor
#{
ViewBag.Title = "EditVendor";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit Vendor</h2>
#using(Html.BeginForm("EditVendor","Vendor",FormMethod.Post, new Dictionary<string, object>
{
{"class","form-horizontal"},
{"role","form"}
}))
{
<div class="form-group">
#Html.LabelFor(m => m.VendorName, new Dictionary<string, object>
{
{"class","col-sm-2 control-label"}
})
<div class="col-sm-10">
#Html.TextBoxFor(m => m.VendorName,new Dictionary<string, object>
{
{"class","form-control"}
})
</div>
</div>
#Html.HiddenFor(m => m.Id)
<input type="submit" class="btn btn-primary btn-default" value="Save" />
}
Model
public class Vendor : AuditableEntity<long>
{
public string VendorName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity
{
[ScaffoldColumn(false)]
public DateTime CreatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string CreatedBy { get; set; }
[ScaffoldColumn(false)]
public DateTime UpdatedDate { get; set; }
[MaxLength(256)]
[ScaffoldColumn(false)]
public string UpdatedBy { get; set; }
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
private static long _rowNumber;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual T Id { get; set; }
[NotMapped]
public virtual long RowNumber
{
get { return ++_rowNumber; }
}
}
The problem is that your parameter name for the EditVendor method is named id and you are returning a model that also has a property named id. When you call the EditVendor method, the value of the parameter is added to ModelState which overrides the value of property Vendor.Id. If you inspect the html generated by #Html.HiddenFor(m => m.Id) you will see that the value of the input is mELirpUhRYksFj7k8-XBcQ%3d%3d, not the value returned by DecryptLong(). When this posts back, it cannot be bound to type int so Id has its default value of zero.
You can test this by adding ModelState.Clear(); before calling GetVendorById(). This will clear the value of Id and the hidden inputs value will now be 1. To solve the problem, change the name of the parameter, for example
public ActionResult EditVendor(string vendorID)
{
var vendor = _vendorService.GetVendorById(vendorID.DecryptLong());
return View(vendor);
}

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>