Building a simple book reservation system - c#

Everything works fine and I am almost done, but would like to add a restriction regarding users age. To be specific, if a user tries to make a reservation for book that has a category of "Adults" (id=3), she/he should be at least 18 years old, otherwise validation error should occur.
Classes:
public class Book
{
[Key]
public int Id { get; set;}
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public int Remaining { get; set; }
public int Borrowed { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
public string Available { get; set; }
}
public class Lending
{
[Key]
public int Id { get; set; }
[ForeignKey("Book")]
public int BookId { get; set; }
public virtual Book Book { get; set; }
public string UserId { get; set; }
[DataType(DataType.Date)]
public DateTime? StartDate { get; set; }
[DataType(DataType.Date)]
public DateTime? EndDate { get; set; }
}
public class Category
{
[Key]
public int Id { get; set; }
public string CategoryName { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
}
I have three categories in my database, Children(Id=1), Young(Id=2) and Adults(Id=3).
One of the ViewModels:
public class LendingViewModel
{
public int Id { get; set; }
[ForeignKey("Book")]
public int BookId { get; set; }
public virtual Book Book { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string UserId { get; set; }
[DataType(DataType.Date)]
public DateTime? StartDate { get; set; }
[DataType(DataType.Date)]
public DateTime? EndDate { get; set; }
public string CategoryName { get; set; }
public decimal Price { get; set; }
public int Year { get; set; }
public string AvailableNow { get; set; }
public int RemainingNow { get; set; }
}
Changes in IdentityModels:
public class ApplicationUser : IdentityUser
{
public DateTime? BirthDate { get; set; } = DateTime.Now;
}
LendingController (just a part that is relevant):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LendingViewModel vm)
{
string userId = User.Identity.GetUserId();
vm.UserId = userId;
var user = new ApplicationUser();
user.Id = userId;
var age = DateTime.Now.Year - user.BirthDate.Value.Year;
var booksa = new Book();
booksa.Id = vm.BookId;
if (ModelState.IsValid && vm != null)
{
if (age < 18 && booksa.CategoryId == 3)
{
ModelState.AddModelError("", "You are to young!");
return View(vm);
};
I also made necessary changes in RegisterViewModel and Register View. However, when a user that is younger than 18 selects a category with the id of 3 (Adults), she/he is still able to make a reservation for the book. What is wrong with my code?

Solved! I used static method and combined it with the method from my repository. Here it is:
Repository:
public bool BookForAdults(int bookId)
{
return db.Books.Where(b => b.Id == bookId).Any(b =>
b.CategoryId == 3);
}
Extension:
public static int GetUserAge(this IIdentity identity)
{
var db = ApplicationDbContext.Create();
var user = db.Users.FirstOrDefault(u =>
u.UserName.Equals(identity.Name));
return user.Age;
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LendingViewModel vm)
{
string userId = User.Identity.GetUserId();
vm.UserId = userId;
var age = User.Identity.GetUserAge();
var bookForAdults =
repo.BookForAdults(vm.BookId);
if (ModelState.IsValid && vm != null)
{
if(age < 18 && bookForAdults)
{
ModelState.AddModelError("", "You are to
young!");
return View(vm);
}

Related

ASP .NET Core MVC - How to initialize an object property in a model class which is a result of many-to-many relationship?

When I want to call a property of my object property Trip from TripApplicationUser model class its values are null. So I do not know how to initialize the Trip object to get its property values later on and to now have problem with indexing in database. I have pasted here the most important parts of code.
[Authorize]
public async Task<ActionResult> Enroll(int id)
{
if (id == null)
{
return NotFound();
}
var currentTrip = await _context.Trip.FindAsync(id);
var currentUser = await _userManager.GetUserAsync(User);
var isAlreadyEnrolled = _context.TripApplicationUsers.Where(tu => tu.ApplicationUserId.Equals(currentUser.Id) && tu.TripId == id);
var UserTrips = isAlreadyEnrolled.ToList();
if (currentTrip.TripSeats > 0 && !UserTrips.Any())
{
ViewBag.process = "done";
currentTrip.TripSeats--;
_context.Update(currentTrip);
var rowToSave = new TripApplicationUser
{
TripId = currentTrip.TripId,
ApplicationUserId = currentUser.Id,
Trip = currentTrip //HOW SHOULD I INITIALIZE IT ACTUALLY?
};
_context.Add(rowToSave);
await _context.SaveChangesAsync();
} else if (UserTrips.Any())
{
ViewBag.process = "already done";
} else if(currentTrip.TripSeats <= 0)
{
ViewBag.process = "not done";
}
var UsersTrips = _context.TripApplicationUsers.Where(t => t.ApplicationUserId.Equals(currentUser.Id)).ToList();
return View(UsersTrips);
}
public class ApplicationUser : IdentityUser
{
[PersonalData]
[Column(TypeName = "nvarchar(MAX)")]
public string FirstName { get; set; }
[PersonalData]
[Column(TypeName = "nvarchar(MAX)")]
public string Surname { get; set; }
[PersonalData]
[Column(TypeName = "nvarchar(MAX)")]
public string BirthDate { get; set; }
public ICollection<TripApplicationUser> TripApplicationUsers { get; set; }
}
public class Trip
{
public int TripId { get; set; }
public string TripDate { get; set; }
public int TripDuration { get; set; }
public int TripLength { get; set; }
public int TripSeats { get; set; }
public int TrailId { get; set; }
public Trail Trail { get; set; }
public ICollection<TripApplicationUser> TripApplicationUsers { get; set; }
}
public class TripApplicationUser
{
public int TripId { get; set; }
public Trip Trip { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
If you want your Trip object to contain data from Navigational properties you have to include them in the request.
var currentTrip = await _context.Trip.Include(trip=> trip.TripApplicationUsers).FirstOrDefaultAsync(trip => trip.TripId == id);

Explicit loading do not loading related entities(list in list)

I try to load from the already found user a list of friends and from the list of friends and load for each list of messages.
dont work
Load only frends, do not load messages in each frend
_context.Entry(ldetails).Collection(p=>p.ListFriends).Query().
Include(r=>r.MessagesDetails).Load();
My data struct
public class RegistrationUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PMId { get; set; }
[Required]
[Column(TypeName ="varchar(16)")]
public string UserName { get; set; }
[Required]
[Column(TypeName = "varchar(16)")]
public string Password { get; set; }
[Column(TypeName = "varchar(480)")]
public string Token { get; set; }
public ICollection<ListFriend> ListFriends { get; set; }
public RegistrationUser()
{
ListFriends = new List<ListFriend>();
}
}
public class ListFriend
{
[Key,Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Required]
[Column(TypeName ="varchar(16)")]
public string UserFriendName { get; set; }
public ICollection<MessagesDetail> MessagesDetails { get; set; }
public ListFriend()
{
MessagesDetails = new List<MessagesDetail>();
}
}
public class MessagesDetail
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key,Column(Order = 0)]
public int PMIdMes { get; set; }
[Required]
[Column(TypeName ="nvarchar(240)")]
public string TextMessage{ get; set; }
[Required]
[Column(TypeName = "varchar(16)")]
public string SenderUser { get; set; }
[Required]
[Column(TypeName = "varchar(16)")]
public string CatcherUser { get; set; }
}
It my method not work
[HttpPost("EnterUserDetail")]
public async Task<ActionResult<RegistrationUser>> postUserDetail( RegistrationUser registrationUser )
{
var ldetails = _context.RegistrationUsers.SingleOrDefault(c=>c.UserName==registrationUser.UserName);
var pdetails = _context.RegistrationUsers.SingleOrDefault(c=>c.Password==registrationUser.Password);
if (ldetails == null && pdetails == null)
{
return NotFound();
}
//_context.Entry(ldetails).Collection("ListFriends").Load();
_context.Entry(ldetails).Collection(p=>p.ListFriends).Query().Include(r=>r.MessagesDetails).Load();
//_context.Entry(ldetails).Collection("ListFriends").IsLoaded = true;
//await _context.SaveChangesAsync();
return ldetails;
}
You are actually making 3 requests to the database to get the desired result. A better, optimized way to achieve this could be as
[HttpPost("EnterUserDetail")]
public async Task<ActionResult<RegistrationUser>> postUserDetail( RegistrationUser registrationUser)
{
// send one query to database to get the result and include here.
var ldetails = _context.RegistrationUsers.Include(i => i.ListFriends).SingleOrDefault(c => c.UserName == registrationUser.UserName && c.Password == registrationUser.Password);
if (ldetails == null && pdetails == null)
{
return NotFound();
}
return ldetails;
}

Cannot implicitly convert type 'System.Collections.Generic.List<int>' to 'System.Collections.Generic.IEnumerable<Model>'

When using Select() I am getting this error
Cannot implicitly convert type 'System.Collections.Generic.List<int>' to 'System.Collections.Generic.IEnumerable<OgrenciEvi.Areas.Message.Models.MessageModel>'.
SiteContext db = new SiteContext();
[Route]
public ActionResult Index()
{
var model = new ViewModel();
int UserID = int.Parse(User.Identity.GetID());
model.Messages = db.Message.Where(m => m.ReceiverID == UserID || m.SenderID == UserID).Select(m=>m.SenderUser.Name).ToList();
return View(model);
}
How I fix it ?
Edit:
public class MessageModel
{
[Key]
public int MessageID { get; set; }
[Required(ErrorMessage = "Boş mesaj gönderilemez.")]
public string MessageContent { get; set; }
[Required]
public int SenderID { get; set; }
[Required]
public int ReceiverID { get; set; }
[Required]
public DateTime SendingDate { get; set; }
[Required]
public bool IsSeen { get; set; }
public virtual UserModel SenderUser { get; set; }
public virtual UserModel ReceiverUser { get; set; }
MessageModel() {
SendingDate = DateTime.Now;
}
}
userModel:
public class UserModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int UserID { get; set; }
[Required (ErrorMessage ="İsim alanı boş geçilemez.")]
public string Name { get; set; }
[Required(ErrorMessage = "Soyadı alanı boş geçilemez.")]
public string Surname { get; set; }
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}" +
#"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
#".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
ErrorMessage = "E-Posta adresi geçersiz.")]
[DataType(DataType.EmailAddress)]
public string Mail { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
[NotMapped] // Does not effect with your database
public string ConfirmPassword { get; set; }
public int Gender { get; set; }
[RegularExpression("^[0-9]*$", ErrorMessage ="Telefon numarası geçersiz.")]
public string PhoneNumber{ get; set; }
public int SmokeStatus { get; set; }
public DateTime RegistrationDate { get; set; }
[HiddenInput(DisplayValue = false)]
public string ReturnUrl{ get; set; }
[Range(1900,2000, ErrorMessage ="Doğum yılı 1900 ile 2000 yılları arasında olmalı.")]
[Required(ErrorMessage ="Yaş alanı boş geçilemez.")]
public int BirthYear { get; set; }
public string FacebookID { get; set; }
public bool Manager { get; set; }
public int AccessFacebook { get; set; }
public int AccessInstagram { get; set; }
public int AccessPhoneNumber { get; set; }
public int AccessTwitter { get; set; }
public List<LookingForaMateModel> LFMate { get; set; }
public List<LookingForaHome> LFHome { get; set; }
public List<AlbumModel> Album { get; set; }
public List<PropertyTransferModel> PropertyTransfer{ get; set; }
[ForeignKey("SenderID")]
public List<MessageModel> SenderUser { get; set; }
[ForeignKey("ReceiverID")]
public List<MessageModel> ReceiverUser { get; set; }
public UserModel() {
RegistrationDate = DateTime.Now;
}
}
I want "direct message" for my web site and the design look like facebook messenger so there is a panel on left side. I should get list Message's sender name and last message. But I couldn't make it :)
You still havn't shown your code for the ViewModel class. But Assuming that model.Messages is of type List<Message>, the following should work:
var model = new ViewModel();
int UserID = int.Parse(User.Identity.GetID());
model.Messages = db.Message.Where(m => m.ReceiverID == UserID || m.SenderID == UserID).ToList();
Make Messages type IEnumerable

Entity to model mapping in LINQ

I have article and author classes.
Fetch articles like so and map entity to model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = MapUserEntityToModel(db.Users.FirstOrDefault(u => u.UserID == t.UserID))
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}
This doesn't work because LINQ can't run that function at run-time. What's the simplest and cleanest way to do the mapping?
Here are models:
namespace Model {
public class Article {
public string Title { get; set; }
public string Teaser { get; set; }
public User Author { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
}
Here are entities:
namespace MyProj {
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
[Key]
public int UserID { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
public class ArticleContext : DbContext {
public ArticleContext() : base("name=conn") {
public DbSet<Article> Articles { get; set; }
public DbSet<User> Users { get; set; }
}
}
}
Before continue, map your relationship in a navigation property:
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
[ForeignKey("UserID")]
public virtual User Author {get; set; } // navigation property
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
And then just project your navigation property to his equivalent Model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = new Model.User {
DisplayName = t.User.DisplayName,
Email = t.User.Email,
Website = t.User.Website,
PasswordHash = t.User.PasswordHash
},
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}
You don't need to do anything, just return db.Articles directly:
using Model;
public List<Article> GetArticleList() {
using(var db = new ArticlesContext()) {
return db.Articles.ToList();
}
}
Assuming your EF model is set-up correctly with Foreign Keys, your Article type will have a lazily-evaluated Author property which will return a User object when accessed.

value cannot be null in Entity Sql Statement

My News.cs class has a one to many relationship with Comment.cs as defined below
public class News
{
public int NewsId { get; set; }
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
public ICollection<Comment> Comment { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
public int? NewsId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
[ForeignKey("NewsId")]
public virtual News News { get; set; }
}
I have a controller action where i am trying to fetch one News item alongside all its comments so i set up two viewModels like this
public class CommentVM
{
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
}
public class NewsCommentsVM
{
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
public List<CommentVM> Comments { get; set; }
}
In my Controller action i have
public ActionResult Details(int? id)
{
UOW _unit = new UOW();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
News news = _unit.NewsRepository.GetByID(id);
if (news == null)
{
return HttpNotFound();
}
var model = new NewsCommentsVM()
{
Title = news.Title,
Details = news.Details,
DateCreated = news.DateCreated,
Author = news.AppUser.FirstName
Comments = news.Comment.Select(c => new CommentVM()
{
CommentText = c.CommentText,
Author = c.AppUser.Email,
DateCreated = c.DateCreated
}).ToList()
};
return View(result);
}
The problem is that the debugger is showing that Comment is returning Null whereas in the database there are related comments to that particular news item so i'm getting the error
Value cannot be null. Parameter: source
I've been able to use this code in another project without issues.
I think the problem is because you need to change the Comments collection property as virtual. If you want that related entities be lazy loaded, you need to follow this requirements:
public class News
{
//...
public virtual ICollection<Comment> Comment { get; set; }
}
Now,If you have disabled lazy loading, another option could be using the Include extension method in your query when you need to find a particular news:
int id=3;
var specificNews=context.News.Include(n=>n.Comment).FirstOrDefault(n=>n.Id==id);
This way the related entity will be included in the query result

Categories