"Create" Action Method Not Working ASP.NET Core - asp.net-mvc

When I add the following lines to the Tournament class the Create action method doesn't work in the webapp It just reloads the page. I recorded a short clip to show what it does: https://youtu.be/pvanpQD8LhM. I am using .NET 7
Code causing the problem:
public ICollection<Game> Games { get; set; }
Here's the classes:
using System.ComponentModel.DataAnnotations;
namespace WebApplication3.Models
{
public class Tournament
{
public int TournamentId { get; set; }
[Required]
public string TournamentName { get; set; }
[Required]
public string Location { get; set; }
//public DateTime StartDate { get; set; }
//public DateTime EndDate { get; set; }
public ICollection<Game> Games { get; set; }
}
}
namespace WebApplication3.Models
{
public class Game
{
public int GameId { get; set; }
//public DateTime GameDate { get; set; }
public string Player1 { get; set; }
public string Player2 { get; set; }
public string Score { get; set; }
public int TournamentId { get; set; }
public Tournament Tournament { get; set; }
}
}
I first tried different code from a Tutorial, which didn't work. I got this from the EFTutorial website. I have also tried removing the required annotations.
Edit:
Here's the create action method for Tournament:
public IActionResult Create()
{
return View();
}
// POST: Tournament/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("TournamentId,TournamentName,Location")] Tournament tournament)
{
if (ModelState.IsValid)
{
_context.Add(tournament);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(tournament);
}
Here's the create action method for Game:
public IActionResult Create()
{
ViewData["TournamentId"] = new SelectList(_context.Tournament, "TournamentId", "TournamentId");
return View();
}
// POST: Game/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("GameId,Player1,Player2,Score,TournamentId")] Game game)
{
if (ModelState.IsValid)
{
_context.Add(game);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["TournamentId"] = new SelectList(_context.Tournament, "TournamentId", "TournamentId", game.TournamentId);
return View(game);
}

From your description in this post, I think every time you create Tournament model in HttpPost create action, ModelState.IsValid is false and you will run return View(tournament) then the page will reload and show The Games field is required. error message in your view, right?
The reason is Beginning with .NET 6, new projects include the enable element in the project file. It means the project will add [requried] attribute by default even if you don't add it manually. So when you create Tournament model. The Games field is null, So ModelState.IsValid will be false and return The Games field is required. error message. You can just add ? to make this field nullable.
public ICollection<Game>? Games { get; set; }

#TJ_Frags you might want to try and just simply redirect to index in your create method. If that works then you need to debug your code from if (ModelState.IsValid) onwards. My guess is your RedirectToAction(nameof(Index)); is not reachable as public ICollection<Game> Games { get; set; } might be invalidating model state.
public IActionResult Create()
{
return View();
}
// POST: Tournament/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("TournamentId,TournamentName,Location")] Tournament tournament)
{
return RedirectToAction(nameof(Index));
}

Related

ASP:NET MVC Summary of a field as a validation for the field itself (Recursiveness)

I Have the following class in a model:
public partial class OrganizationUnit
{
public string code{ get; set; }
public int OrganizationCod { get; set; }
public string name { get; set; }
public string ParentUnitCode{ get; set; }
public int level{ get; set; }
public string author{ get; set; }
public System.DateTime CreateDtStmp{ get; set; }
public int Status { get; set; }
public decimal weighing { get; set; }
[ForeignKey("status")]
public virtual Status UnitStatus { get; set; }
public virtual Organization Organization { get; set; }
public virtual ICollection<OrganizationUnit> OrganizationUnit1{ get; set; }
[ForeignKey("ParentUnitCode")]
public virtual OrganizationUnit OrganizationUnit2{ get; set; }
public OrganizationUnit ()
{
CreateDtStmp= DateTime.Now;
author = HttpContext.Current.User.Identity.Name.Substring(4,HttpContext.Current.User.Identity.Name.Length - 4);
}
}
Before inserting a new record I need to validate sum(weighing) can not exceed 100 including the attempted new record, considering only the records with the same ParentUnit.
Can this be done in the model or should it be done in the controller?
this is the saving controller part (basically is what is autogenerated by VS),consider that the view will send the corresponding parameter to the method:
private SAIM_IPM_DVContext db = new SAIM_IPM_DVContext();
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(OrganizationUnit organizationunit)
{
if (ModelState.IsValid)
{
db.OrganizationUnit.Add(organizationunit);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(organizationunit);
}
If OrganizationUnit1 is the collection of all other units with the same ParentUnit, you can perform the check in the ViewModel by implementing IValidatableObject:
using System.ComponentModel.DataAnnotations;
public partial class OrganizationUnit : IValidatableObject {
/* properties etc... */
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (OrganizationUnit1.Sum(o => o.weighing) + this.weighing > 100.0) {
yield return new ValidationResult(
"Sum of weightings would exceed 100.",
new[] { "weighing" });
}
}
}
Note that this assumes that the weighing is posted back for all units in the OrganizationUnit1 collection (e.g. as hidden fields) so it is accessible when this check is performed by the MVC pipeline (this is done before your POST action will be hit).
If this check fails, ModelState.IsValid will be false in the controller.
If you have no access to the other units in the Viewmodel, you will have to fetch them from the DB in the controller and perform the check there.
After a while and tries I came up with this solution works perfect, however I am not sure is the best practice. This is on the controller class
public ActionResult Create(OrganizationUnit organizationunit)
{
decimal weighingsum= 0;
foreach (var val in db.OrganizationUnit.Where(t => t.ParentUnitCode== organizationunit.ParentUnitCode))
{
weighingsum+= val.weighing ;
}
weighingsum+= organizationunit.weighing ;
if (weighingsum > 100)
{
ModelState.AddModelError("", "Weighing sum can not exceed 100 for a Parent Unit");
}
else
{
if (ModelState.IsValid)
{
db.OrganizationUnit.Add(organizationunit);
db.SaveChanges();
return RedirectToAction("Index");
}
}
return View(organizationunit);
}
should anyone comes up with a better solution, please do share I'll appreciate it.

Adding values to the controller action manually

I have a model class that is below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
namespace MyForms.Models
{
public class Master
{
public int ID { get; set; }
public string ModuleName { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifyBy { get; set; }
public DateTime ModifyDate { get; set; }
public Boolean IsActive { get; set; }
public Boolean IsDeleted { get; set; }
// public virtual ICollection<MasterModule> MasterModules { get; set; }
}
Here in this code I am passing the values through views(textboxes). What are my expections
Module Name is entered through text box and when submit button is clicked
CreatedBy contains the ID of the person who create the module(e.g 1. admin 2. manager)
Created date is automatically added as current Date when submit is clicked
ModifyBy contains the ID of the person who modify (edit) the module(e.g 1. admin 2. manager)
ModifyDate is the curent date when the module is edited
when the module is created is activated always sets to be true.
2 to 6 are settled with coding. Now I want to know how can I handle each situation separetaly.
Currently I am adding all the values manually, I mean through the input box and checkboxes
namespace MyForms.Controllers
{
public class MasterController : Controller
{
//
// GET: /Master/
public ActionResult Index()
{
using (MyFormDemoContext context = new MyFormDemoContext())
{
return View(context.MasterForms.ToList());
}
// return View();
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Master master)
{
try
{
using (MyFormDemoContext context = new MyFormDemoContext())
{
context.MasterForms.Add(master);
context.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}

Can user hack values in action parameter?

Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.

Value cannot be null

I have written ASP.NET MVC Code for CRUD but while Updating,Deleting and Details the page is throwing the error as value can not be null.my code is as follows:
public ActionResult Edit(int id=0)
{
var alb=db.Albums.FirstOrDefault(x=>x.AlbumId==id);
return View(alb);
}
public ActionResult Detail(int id=0)
{
var alb=db.Albums.Find(id);
return View(alb);
}
public ActionResult Delete(int id=0)
{
var album = db.Albums.Find(id);
return View(album);
}
and my Model is
public class Album
{
public int AlbumId { get; set; }
public int GenreId { get; set; }
public int ArtistId { get; set; }
public string AlbumTitle { get; set; }
public String Price { get; set; }
public String AlbumArtUrl { get; set; }
public Genre Genre { get; set; }
public Artist Artist { get; set; }
}
This is because the variable alb and album is showing me the null value its not getting any ID.
so suggest me on this.
This is because the variable alb and album is showing me the null
value its not getting any ID
If there is no record in your database then you should handle it gracefully in code. You should have something like:
var alb=db.Albums.FirstOrDefault(x=>x.AlbumId==id);
if (alb==null) {
// TempData, simplest way to give you an example
TempData["error_msg"] = "the record does not exists";
return RedirectToAction("index", "error");
}
return View(alb);
in your ErrorController
public ActionResult Index() {
ViewBag.ErrorMsg = TempData["error_msg"];
return View();
}
in the view (Index.cshtml) for your error controller
<h1>You have been, or trying to be, a naughty boy</h1>
<p>#ViewBag.ErrorMsg</p>
This is not the most elegant solution though but good enough to get you started. I think the most important part of my answer is not the code I gave you but the suggestion to handle that kind of situation (null values / missing or invalid records) in code.

Creating related attributes when creating new instance of class MVC 4 - Entity Framework

I am just getting into MVC 4 and Entity Framework 5 and want to know if what I am doing is correct?
I have a UserObject and a JobObject, the jobObject has a reference to a User Object.
public class Job
{
public int id { get; set; }
public virtual MyUser User { get; set; }
public JobType JobType { get; set; }
}
When I want to create an instance of the Job I am passing in the query string a parameter UserID, but the Job only deals with an instance of MyUser.
Is the following the correct way to associate the user to the job?
[HttpPost]
public ActionResult Create(Job job, int userid)
{
if (ModelState.IsValid)
{
MyUser staffmember = db.MyUsers.Find(userid);
if (staffmember == null)
{
return View("StaffMemberNotFound");
}
job.User = staffmember;
db.Jobs.Add(job);
db.SaveChanges();
}
}
Or is there a better way to associate the user to the job?
Your way will work but I prefer to simply work with ids if possible.
What I would suggest is that you add a MyUserId property to your Job class (remember to update the database if you are using codefirst):
public class Job
{
public int id { get; set; }
[ForeignKey("User")]
public int MyUserId { get; set: }
public virtual MyUser User { get; set; }
public JobType JobType { get; set; }
}
Then simply populate the MyUserId. You can also change your check to simply check if the id exists as apposed to finding an object and letting EF map that to a class before returning it to you
[HttpPost]
public ActionResult Create(Job job, int userid)
{
if (ModelState.IsValid)
{
if (!db.MyUsers.Any(u => u.Id == userid)
{
return View("StaffMemberNotFound");
}
job.MyUserId = userid;
db.Jobs.Add(job);
db.SaveChanges();
}
}
EF will do the rest of the mapping for you when you next retrieve the record from the database.
Your approach works fine, the only small optmization you could make is not taking the "retrieval hit" of MyUser staffmember = db.MyUsers.Find(userid); since you already have the userid.
I am using ASP.NET MVC 4 and Entity Framework 5.0, and here is my code (different model objects, but same intent as what you are doing).
Note: I let EF generate my model classes by right-clicking on the Models folder and choosing Add->ADO.NET Entity Data Model in VS.NET 2012.
Store.Models.Product
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Product
{
public long Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public System.DateTime DateAdded { get; set; }
public Nullable<long> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
}
Store.Models.Category
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.Products = new HashSet<Product>();
}
public long Id { get; set; }
public string CategoryName { get; set; }
public System.DateTime DateAdded { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
On my Create.cshtml page, I have the User select the CategoryId from the drop-down list. This Category Id is bound to Product.CategoryId. All I do in my method is this:
ProductController
public class ProductController : Controller
{
...
[HttpPost]
public ActionResult Create(Product product)
{
product.DateAdded = DateTime.Now;
if (dbContext != null)
{
dbContext.Products.Add(product);
dbContext.SaveChanges();
}
return RedirectToAction("Index");
}
...
}

Resources