asp.net MVC - code first foreign key lets me input anything - c#

I'm quite new to MVC and had searched and checked multiple solutions, but none worked for me yet.
I have a database made using Code First. It has 3 tables: Club, Match and Round. In every round there's a few matches. In every match play 2 clubs. Match contains one foreign key to Round (which works great) and two separate multiple keys to Match (home and away team).
When I add MatchesController with views using EF and start the application and try to input new match, I have a dropdownlist of Rounds from database - works amazing. I'd like to have two dropdownlists with team names/IDs from database as well so I can select teams playing, but for some reason I have two textboxes that let me write anything, as if the FK constraints don't exist.
I'm having quite a problem with it as I'm not sure what I'm doing wrong - is it the foreign keys? I coded them like that while browsing other questions, but maybe I messed up?
Round class:
public class Round : IValidatableObject
{
public int RoundID { get; set; }
[Required]
public int RoundNumber { get; set; }
public virtual ICollection<Match> Matches { get; set; }
}
Club class:
public class Club
{
public int ClubID { get; set; }
[Required]
public string ClubName { get; set; }
public virtual ICollection<Match> HomePlays { get; set; }
public virtual ICollection<Match> AwayPlays { get; set; }
}
Match class and its DB context class:
public class Match
{
public int MatchId { get; set; }
public int HomeClubId { get; set; }
public int AwayClubID { get; set; }
public virtual Club HomeClub { get; set; }
public virtual Club AwayClub { get; set; }
[Required]
public int RoundId { get; set; }
[ForeignKey("RoundId")]
[InverseProperty("Matches")]
public virtual Round Round { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Match>()
.HasRequired(m => m.HomeClub)
.WithMany(m => m.HomePlays)
.HasForeignKey(m => m.HomeClubId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Match>()
.HasRequired(m => m.AwayClub)
.WithMany(m => m.AwayPlays)
.HasForeignKey(m => m.AwayClubID)
.WillCascadeOnDelete(false);
}
}
And then there's the generated MatchesController with actions Index and Create:
public class MatchesController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Matches
public ActionResult Index()
{
var match = db.Match.Include(m => m.Round);
return View(match.ToList());
}
// GET: Matches/Create
public ActionResult Create()
{
ViewBag.RoundId = new SelectList(db.Round, "RoundID", "RoundID");
return View();
}
// POST: Matches/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MatchId,HomeClubId,AwayClubID,Club1Goals,Club2Goals,RoundId")] Match match)
{
if (ModelState.IsValid)
{
db.Match.Add(match);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.RoundId = new SelectList(db.Round, "RoundID", "RoundID", match.RoundId);
return View(match);
}
}
And the Create view:
#model ProjSty3Exp.Models.Match
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Match</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.HomeClubId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.HomeClubId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.HomeClubId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AwayClubID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AwayClubID, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.AwayClubID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RoundId, "RoundId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("RoundId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.RoundId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>

in Get action method
public ActionResult Create()
{
ViewBag.RoundId = new SelectList(db.Round, "RoundID", "RoundID");
ViewBag.AwayClubID = new SelectList(db.Clubs, "ClubID", "ClubID");
ViewBag.HomeClubId = new SelectList(db.Clubs, "ClubID", "ClubID");
return View();
}
In razor view
Replace
#Html.EditorFor(model => model.HomeClubId, new { htmlAttributes = new { #class = "form-control" } })
By
#Html.DropDownList("HomeClubId", null, htmlAttributes: new { #class = "form-control" })
Replace
#Html.EditorFor(model => model.AwayClubID, new { htmlAttributes = new { #class = "form-control" } })
By
#Html.DropDownList("AwayClubID", null, htmlAttributes: new { #class = "form-control" })
Post action method
// POST: Matches/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MatchId,HomeClubId,AwayClubID,Club1Goals,Club2Goals,RoundId")] Match match)
{
if (ModelState.IsValid)
{
db.Match.Add(match);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.RoundId = new SelectList(db.Round, "RoundID", "RoundID", match.RoundId);
ViewBag.AwayClubID = new SelectList(db.Clubs, "ClubID", "ClubID", match.AwayCludId);
ViewBag.HomeClubId = new SelectList(db.Clubs, "ClubID", "ClubID", match.HomeClubId);
return View(match);
}

Related

ASP.Net MVC Update entity met with "The entity could not be updated."

I'm fairly new to coding, and am trying to tie up the finishing touches to my personal MVC project. I have done breakpoints to see double-check that the types of the values I'm passing through are matching up, which I believe they are, but it's hard for me to navigate the breakpoints and understand what information I should be looking at.
When I do breakpoints, it skips the final If-statement shown below:
if (service.UpdatePromotion(model))
{
TempData["SaveResult"] = "The promotion has been updated!";
return RedirectToAction("Index");
}
If there is any other information needing to be posted, I'll update here if I can or in the comments. I really appreciate anyone who looks this over.
Controller
// GET: Edit
public ActionResult Edit(int id)
{
var service = CreatePromotionService();
var detail = service.GetPromotionById(id);
var model = new PromotionEdit
{
PromotionId = detail.PromotionId,
PromotionName = detail.PromotionName,
DateFounded = detail.DateFounded,
Website = detail.Website
};
return View(model);
}
// POST: Edit
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, PromotionEdit model)
{
if (!ModelState.IsValid) return View(model);
if (model.PromotionId != id)
{
ModelState.AddModelError("", "ID Mismatch");
return View(model);
}
var service = CreatePromotionService();
if (service.UpdatePromotion(model))
{
TempData["SaveResult"] = "The promotion has been updated!";
return RedirectToAction("Index");
}
ModelState.AddModelError("", "The promotion could not be updated.");
return View(model);
}
Services
public PromotionDetail GetPromotionById(int id)
{
using (var ctx = new ApplicationDbContext())
{
var entity = ctx.Promotions
.Single(e => e.PromotionId == id
&& e.OwnerId == _userId);
return new PromotionDetail
{
PromotionId = entity.PromotionId,
PromotionName = entity.PromotionName,
DateFounded = entity.DateFounded.Date,
Website = entity.Website,
CreatedUtc = entity.CreatedUtc
};
}
}
public bool UpdatePromotion(PromotionEdit model)
{
using (var ctx = new ApplicationDbContext())
{
var entity = ctx.Promotions.Single(e =>
e.PromotionId == model.PromotionId
&& e.OwnerId == _userId);
entity.PromotionName = model.PromotionName;
entity.IsStarred = model.IsStarred;
entity.DateFounded = model.DateFounded.Date;
entity.Website = model.Website;
return ctx.SaveChanges() == 1;
}
}
Model
public class PromotionDetail
{
[Display(Name = "Promotion ID")]
public int PromotionId { get; set; }
[Display(Name = "Promotion Name")]
public string PromotionName { get; set; }
[Display(Name = "Date Founded")]
public DateTime DateFounded { get; set; }
[Display(Name = "Website")]
public string Website { get; set; }
[Display(Name = "Date Created")]
public DateTimeOffset CreatedUtc { get; set; }
}
public class PromotionEdit
{
public int PromotionId { get; set; }
[Display (Name = "Promotion Name")]
public string PromotionName { get; set; }
public bool IsStarred { get; set; }
public DateTime DateFounded { get; set; }
public string Website { get; set; }
}
View
#model Models.PromotionEdit
#{
ViewBag.Title = "Edit";
}
<h2>Updating Promotion</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.PromotionId)
<div class="form-group">
#Html.LabelFor(model => model.PromotionName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PromotionName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PromotionName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DateFounded, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateFounded, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DateFounded, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Website, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Website, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Website, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div id="linkColor">
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Shout-out to curious.netter in the comments. I was being lazy with the update, not changing anything and clicking Submit. Since there were no changes to apply, the entity would not update, which would skip the last If statement.

ASP.Net MVC Unable to edit user because od the [Compare(Password)] in the class User

I have table named Korisnik (on my language, on english its User) and i added an Edit ActionResult in my Controller , but it wont work because of the [Compare("Lozinka")] that is comparing the password from the database and the added property PotvrdiLozinku, in other words i must enter the Confirm password in order to Submit the changes
namespace ProjekatFinalni.Models
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public partial class Korisnik
{
public int KorisnikID { get; set; }
[DisplayName("Korisnicko ime:")]
[Required(ErrorMessage ="Molimo vas unesite korisnicko ime.")]
public string Korisnickoime { get; set; }
[DisplayName("Lozinka:")]
[DataType(DataType.Password)]
[Required(ErrorMessage = "Molimo vas unesite lozinku.")]
public string Lozinka { get; set; }
[DisplayName("Admin:")]
public bool DaLiJeAdmin { get; set; }
[DisplayName("Gost:")]
public bool Gost { get; set; }
[DisplayName("Pravo za unos:")]
public bool PravoUnosa { get; set; }
[DisplayName("Potvrdi lozinku:")]
[DataType(DataType.Password)]
[Compare("Lozinka",ErrorMessage ="Lozinke se ne poklapaju.")]
public string PotvrdiLozinku { get; set; }
public string LoginErrorPoruka { get; set; }
}
This is the Edit ActionResult in my controller
public ActionResult Edit(int id)
{
using (BazaProjekatEntities4 dbModel = new BazaProjekatEntities4())
{
return View(dbModel.Korisniks.Where(x => x.KorisnikID == id).FirstOrDefault());
}
}
[HttpPost]
public ActionResult Edit(int id,Korisnik k)
{
try
{
using (BazaProjekatEntities4 dbModel = new BazaProjekatEntities4())
{
dbModel.Entry(k).State = EntityState.Modified;
dbModel.SaveChanges();
}
return RedirectToAction("Izlistaj");
}
catch
{
return View();
}
}
And this is the Edit.cshtml
#model ProjekatFinalni.Models.Korisnik
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Korisnik</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.KorisnikID)
<div class="form-group">
#Html.LabelFor(model => model.Korisnickoime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Korisnickoime, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Korisnickoime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Lozinka, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Lozinka, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Lozinka, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PotvrdiLozinku, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PotvrdiLozinku, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PotvrdiLozinku, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DaLiJeAdmin, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.DaLiJeAdmin)
#Html.ValidationMessageFor(model => model.DaLiJeAdmin, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Gost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Gost)
#Html.ValidationMessageFor(model => model.Gost, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PravoUnosa, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.PravoUnosa)
#Html.ValidationMessageFor(model => model.PravoUnosa, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Sacuvaj" class="btn btn-default" />
</div>
</div>
</div>
}
Only want to edit the permissions(Admin, Gost and PravoUnosa
EDIT( Added the registration form that i used)
[HttpPost]
public ActionResult DodajiliIzmeni(Korisnik korisnikmodel)
{
using (BazaProjekatEntities4 Modelkorisnik = new BazaProjekatEntities4())
{
if(Modelkorisnik.Korisniks.Any(x=> x.Korisnickoime == korisnikmodel.Korisnickoime))
{
ViewBag.DuplicateMessage = "Korisnicko ime vec postoji.";
return View("DodajiliIzmeni", korisnikmodel);
}
Modelkorisnik.Korisniks.Add(korisnikmodel);
Modelkorisnik.SaveChanges();
}
ModelState.Clear();
ViewBag.SuccessMessage = "Registracija je uspela";
return RedirectToAction("Index", "Login");
}
You should create a view model specific for the view, which has the properties and validation attributes on them as needed by the view and use that to transfer data between your view and action method.
public class EditUserVm
{
public int Id { get; set; }
[DisplayName("Korisnicko ime:")]
public string UserName { get; set; }
[DisplayName("Admin:")]
public bool Admin { get; set; }
[DisplayName("Gost:")]
public bool Gost { get; set; }
[DisplayName("Pravo za unos:")]
public bool PravoUnosa { get; set; }
}
Now you will use this view model for your GET and POST action methods. In your GET action method, first create an object of this view model, then get your Korisniks object for the Id passed in, Read and map the property values to the view model object and pass it to the view.
public ActionResult Edit(int id)
{
using (var dbModel = new BazaProjekatEntities4())
{
var user = dbModel.Korisniks.FirstOrDefault(x => x.KorisnikID == id);
// to do: If user is NULL, return a "Not found" view to user ?
var vm = new EditUserVm { Id = id };
vm.UserName = user.UserName;
vm.Admin = user.Admin;
vm.Gost = user.Gost;
vm.PravoUnosa = user.PravoUnosa;
return View(vm);
}
}
Now makes sure your view is strongly typed to this view model because we are passing an object of the EditUserVm class to it.
#model YourNamespaceGoesHere.EditUserVm
#using (Html.BeginForm())
{
#Html.HiddenFor(a=>a.Id)
<label>#Model.UserName</label>
#Html.LabelFor(a=>a.Admin)
#Html.CheckBoxFor(a=>a.Admin)
#Html.LabelFor(a=>a.Gost)
#Html.CheckBoxFor(a=>a.Gost)
#Html.LabelFor(a=>a.PravoUnosa)
#Html.CheckBoxFor(a=>a.PravoUnosa)
<button type="submit" >Save</button>
}
Now you will use the same view model as the action method parameter. Inside the method, we will read again the User entity from the database and udpate only the field we want to
[HttpPost]
public ActionResult Edit(EditUserVm model)
{
var db = new BazaProjekatEntities4();
var user = db.Korisniks.FirstOrDefault(x => x.KorisnikID == model.Id);
// to do : Do a null check on user to be safe :)
// Map the property values from view model to entity object
user.Admin = model.Admin;
user.Gost = model.Gost;
user.PravoUnosa = model.PravoUnosa;
db.Entry(k).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
In short, create a view model with properties absolutely needed by the view/ your code and use that to transfer data between your action method and view.
The solution was very simple and it could be from the start, i just need to add the user.PotvrdiLozinku = user.Lozinka; that will tell it that the Confirm password is equal to Password (For the [Compare] that is in the User class. :)
[HttpPost]
public ActionResult Edit(EditUserVm model)
{
var db = new BazaProjekatEntities4();
var user = db.Korisniks.FirstOrDefault(x => x.KorisnikID == model.Id);
// to do : Do a null check on user to be safe :)
// Map the property values from view model to entity object
user.Admin = model.Admin;
user.PotvrdiLozinku = user.Lozinka; // this line was missing
user.Gost = model.Gost;
user.PravoUnosa = model.PravoUnosa;
db.Entry(k).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}

How to update table properties from related table in ASP.NET MVC

I've recently started with ASP.NET and I must say that I like it a lot. But in every relationship, you must hit some obstacle here and there. Here is mine:
My starting project includes creating simple schools system for managing students. I have several tables (Students, StudentAddresses, Grades, and Courses).
Here are problematic two tables:
1) Student table:
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
[StringLength(50, MinimumLength = 2, ErrorMessage = "The name must have over 2 and under 50 characters!")] // stavlja ograničenje na duljinu stringa u bazi
public string Name { get; set; }
[Required]
[StringLength(50, MinimumLength = 2, ErrorMessage = "The name must have over 2 and under 50 characters!")]
public string Surname { get; set; }
public int CurrentGradeId { get; set; }
public Grade CurrentGrade { get; set; }
public virtual StudentAdress Address { get; set; }
public virtual ICollection<Course> Courses { get; set; }
2) StudentAddress table:
public int StudentId { get; set; }
public string Address1 { get; set; }
public string Adress2 { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
public string Country { get; set; }
public virtual Student Student { get; set; }
In short, I'm trying to update StudentAddresses properties (to be more precise Address1 property for Student) while inside Student edit Action Method.
Here is View for Edit student:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Student</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.StudentId)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Surname, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Surname, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Surname, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CurrentGradeId, "CurrentGradeId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CurrentGradeId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CurrentGradeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Address.Address1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Address.Address1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Address.Address1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
And finally here is Edit Action Method for StudentsController:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "StudentId,Name,Surname,CurrentGradeId")] Student student )
{
if (ModelState.IsValid)
{
// getting student id for current studentid
StudentAdress addresa = db.Addresses.Find(student.StudentId);
// trying to bind input value
addresa.Address1 = student.Address.Address1.ToString();
// saving new value in StudentAddresses field Address1
db.Entry(addresa).State = EntityState.Modified;
db.SaveChanges();
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CurrentGradeId = new SelectList(db.Grades, "GradeId", "GradeName", student.CurrentGradeId);
return View(student);
}
So, in Edit method, I want to save new input value for Address1 inside Edit method for Student table. Or to put it simpler, I want to update field Address1 in StudentAddress class while Editing Student class
UPDATE 1
So, let's say that I've figure it out but I'm still wondering if it is proper and right way of doing it.
In Student Edit action method I added addresa.Address1 = Request.Form["Address.Address1"]; where I updated field with attribute name=Address.Address1 and it did a trick but I'm wondering if it is right way of doing it?
Keep in mind that I've tried this
**addresa.Address1 = student.Address.Address1;**
but using this "cleaner" approach gave me:
System.NullReferenceException: Object reference not set to an instance
of an object.
Could I updated Address.Address1 field using some other approach?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "StudentId,Name,Surname,CurrentGradeId")] Student student )
{
if (ModelState.IsValid)
{
StudentAdress addresa = db.Addresses.Find(student.StudentId);
// newly added line
addresa.Address1 = Request.Form["Address.Address1"];
db.Entry(addresa).State = EntityState.Modified;
db.SaveChanges();
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CurrentGradeId = new SelectList(db.Grades, "GradeId", "GradeName", student.CurrentGradeId);
return View(student);
}
Your relationships are wrong.
In your StudentAddress model we can see the StudentId foreign key which means relationship between Student and StudentAddress is one to many. But in your Student model you have virtual StudentAdress Address which should be ICollection<StudentAdress> Addresses.
And in your add/edit you could just do:
var student = context.Student.Find(1);
student.Addresses.Add(new StudentAdress {
Address1 = "Ul. Janka Leskovara",
City = "Pregrada",
ZipCode = 49218,
Country = "Hrvatska"
});
context.SaveChanges();

"DRY up" MVC models when using SelectListItem

I have just started MVC and i am able to find examples of most DRY principles online. Although I don't employ all as i find some make code a lot harder to read.
I have found one I cannot find an example of though and I feel like there must be a way to do it.
Basically at the moment I populate model selectlist type objects in the controller, this means I have to reuse the code. I know I could just put this in a method but I was wondering if there anyway to put it in the model so that anytime the model/viewmodel is used this action is called to populate the the selectlist contents.
I have put an example of my code below.
Model
using System;
using System.Data.Entity;
namespace MyWebsite.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
namespace CustomerWebsites.Models
{
public class CustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
}
public class CustomerWebsitesDBContext : DbContext
{
public CustomerWebsitesDBContext()
: base("DefaultConnection")
{
}
public static CustomerWebsitesDBContext Create()
{
return new CustomerWebsitesDBContext();
}
public DbSet<CustomerWebsites> CustomerWebsites { get; set; }
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace MyWebsite.ViewModels
{
public class CreateCustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
[Required]
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
public IEnumerable<SelectListItem> AllUsers { get; set; }
}
}
Controler
// GET: CustomerWebsites/Create
public ActionResult Create()
{
var db = new ApplicationDbContext();
var users = db.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
var model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
// POST: CustomerWebsites/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateCustomerWebsites model)
{
if (ModelState.IsValid)
{
var userGuid = new Guid(User.Identity.GetUserId());
var developmentStatus = "Pending MyWebsite Review";
if (User.IsInRole("Administrator"))
{
userGuid = model.UserGuid;
developmentStatus = model.DevelopmentStatus;
}
db.CustomerWebsites.Add(new CustomerWebsites
{
UserGuid = userGuid,
WebsiteAddress = model.WebsiteAddress,
CreationDate = DateTime.Now,
ReleaseDate = model.ReleaseDate,
Budget = model.Budget ,
Description = model.Description,
DevelopmentStatus = developmentStatus,
CompletedPercentage = model.CompletedPercentage,
Completed = model.Completed,
TotalCost = model.TotalCost,
TotalPaid = model.TotalPaid
});
db.SaveChanges();
return RedirectToAction("Index");
}
var dbUsers = new ApplicationDbContext();
var users = dbUsers.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
View
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CustomerWebsites</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#if (User.IsInRole("Administrator"))
{
<div class="form-group">
#Html.LabelFor(model => model.UserGuid, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.UserGuid, Model.AllUsers, "-- Select a user --")
#Html.ValidationMessageFor(model => model.UserGuid, "", new { #class = "text-danger" })
</div>
</div>
}
<div class="form-group">
#Html.LabelFor(model => model.WebsiteAddress, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.WebsiteAddress, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.WebsiteAddress, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.WebsiteType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.WebsiteType, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.WebsiteType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ReleaseDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ReleaseDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ReleaseDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.Description, new { rows = "10", #class = "form-control" })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
#if (User.IsInRole("Administrator"))
{
<div class="form-group">
#Html.LabelFor(model => model.DevelopmentStatus, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.DevelopmentStatus, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.DevelopmentStatus, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CompletedPercentage, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.CompletedPercentage, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.CompletedPercentage, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Completed, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Completed)
#Html.ValidationMessageFor(model => model.Completed, "", new {#class = "text-danger"})
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalCost, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.TotalCost, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.TotalCost, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalPaid, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.TotalPaid, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.TotalPaid, "", new {#class = "text-danger"})
</div>
</div>
}
<div class="form-group">
#Html.LabelFor(model => model.Budget, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Budget, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Budget, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Basically at the moment I populate model selectlist type objects in
the controller, this means I have to reuse the code. I know I could
just put this in a method but I was wondering if there anyway to put
it in the model so that anytime the model/viewmodel is used this
action is called to populate the the selectlist contents.
It is generally not a good idea to put any methods in your viewmodels, especially for data access & population. Viewmodels should be plain data containers, void of knowledge or behavior. You are doing the right thing by populating the SelectList from the controller action.
As far as DRY, it looks pretty DRY to me already. You are only repeating one or two lines of code. You could DRY it up a little more by using something like AutoMapper to do the projection for you:
var users = dbUsers.Users.ToArray();
model = new CreateCustomerWebsites
{
AllUsers = Mapper.Map<IEnumerable<SelectListItem>>(users)
};
...but do do this, you would have to add even more code to define the mapping. You could also like you mentioned in your question move the projection to a private method on the controller, but this would also mean adding more code, and would move some relevant code away from the controller action body. And you're effectively only removing 2 lines of code (one from each action that needs to hydrate the dropdown). Personally, I don't really see a problem with the way you are doing it now.
Another option could be to write an ActionFilterAttribute to populate the SelectList during ResultExecuted. But the point is, don't do it from the ViewModel: do it during the action execution.
This might be OTT for your needs, but I looked at this a while ago and was also trying to address the fact that a db lookup was performed every time the selectlist was populated.
I have a service class that sits between the Controllers and dbcontext, so in your example I would have a class called UserService.cs. The service class handles business logic & keeps the controller methods reasonably 'thin'. In the UserService class you have a method called GetAsSelectList():
public SelectList GetAsSelectList()
{
var b = CacheHelper.GetCacheItem("UserSelectList", UsersDelegate, CacheHelper.SlidingParam, CacheHelper.AbsoluteParam);
return new SelectList((IEnumerable)b, "Id", "Name");
}
This uses a CacheHelper class to check if the selectlist exists in memory. If it does then it returns it, thus saving a database lookup. If not in the cache, it runs the method below to generate the selectlist & store it in the cache with the name 'UserSelectList'.
private object UsersDelegate()
{
return (from c in _context.Set<Users>()
select new
{
c.Id, c.Name
}).ToList();
}
The actual CacheHelper class can be found here
Using this approach has saved me loads of database lookups as I'm populating forms with multiple selectlists containing data that rarely - if ever - changes.

ASP.NET MVC multiple models single set of Create, View, Edit and Delete pages

I have a problem figuring out how to implement the Create, Edit and Delete pages for a case where there are multiple Models that I want to display in a single set of Views.
I'm using Visual Studio 2013 and MVC 5. And I also use the latest Entity Framework 6.1.1.
So far I've got the Models:
public class CompositeModel
{
public int ID { get; set; }
public MediaPlan MediaPlanModel { get; set; }
public ContactInfo ContactInfoModel { get; set; }
public MediaPlanDate MediaPlanDateModel { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
public class MediaPlan
{
public int ID { get; set; }
public string Client { get; set; }
public string Product { get; set; }
public string AE { get; set; }
public string Supervisor { get; set; }
public string Traffic { get; set; }
}
public class ContactInfo
{
public int ID { get; set; }
public string CompanyName { get; set; }
public string CompanyContact { get; set; }
public string CompanyAddress { get; set; }
public string CompanyPhoneNumber { get; set; }
public string CompanyEmail { get; set; }
}
public class MediaPlanDate
{
public int ID { get; set; }
public string Client { get; set; }
public string Product { get; set; }
public string AE { get; set; }
public string Supervisor { get; set; }
public string Traffic { get; set; }
}
Using Code First approach the database was created correctly.
I did auto-generate the CompositeModelsController.cs:
public class CompositeModelsController : Controller
{
private MprContext db = new MprContext();
// GET: CompositeModels
public ActionResult Index()
{
//var compositeModel = new CompositeModel();
//compositeModel.MediaPlanModel = new MediaPlan();
//compositeModel.ContactInfoModel = new ContactInfo();
//compositeModel.MediaPlanDateModel = new MediaPlanDate();
//return View(compositeModel.ToList());
return View(db.Composites.ToList());
}
// GET: CompositeModels/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CompositeModel compositeModel = db.Composites.Find(id);
if (compositeModel == null)
{
return HttpNotFound();
}
return View(compositeModel);
}
// GET: CompositeModels/Create
public ActionResult Create()
{
return View();
}
// POST: CompositeModels/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,RowVersion")] CompositeModel compositeModel)
{
if (ModelState.IsValid)
{
db.Composites.Add(compositeModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(compositeModel);
}
// GET: CompositeModels/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CompositeModel compositeModel = db.Composites.Find(id);
if (compositeModel == null)
{
return HttpNotFound();
}
return View(compositeModel);
}
// POST: CompositeModels/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,RowVersion")] CompositeModel compositeModel)
{
if (ModelState.IsValid)
{
db.Entry(compositeModel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(compositeModel);
}
// GET: CompositeModels/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CompositeModel compositeModel = db.Composites.Find(id);
if (compositeModel == null)
{
return HttpNotFound();
}
return View(compositeModel);
}
// POST: CompositeModels/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
CompositeModel compositeModel = db.Composites.Find(id);
db.Composites.Remove(compositeModel);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
I did not expect it to work and it doesn't.
I was able to get the Index to work.
I also created Editor Templates:
#model Online_Mpr.Models.MediaPlan
<h2>MediaPlan</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>MediaPlan</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Client, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Client, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Client, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Product, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Product, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Product, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AE, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AE, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.AE, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Supervisor, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Supervisor, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Supervisor, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Traffic, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Traffic, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Traffic, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And so forth for the other Models.
Then I have the Create View:
#model Online_Mpr.Models.CompositeModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CompositeModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.EditorFor(model => model.MediaPlanModel)
#Html.EditorFor(model => model.MediaPlanDateModel)
#Html.EditorFor(model => model.ContactInfoModel)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I was planning to do similar thing for the Edit View.
When I go to the Create View it displays the edit boxes for all the elements but how do I get to code the controller that will create the records in the database.
Preferably I would like to have just one "Create" button in the Create View that would create all four records.
Any help is appreciated as I scoured the internet for info about similar cases and could not find any.
Also if you could provide code samples. They don't have to be elaborate but the core thought will lead me on.
Thanks,
-Arek
Your CompositeModel is essentially a view model. This class shouldn't have an Id property and should n't actually be tied to Entity Framework in any way (don't add a DbSet for it). All it needs to do is contain properties for the other model instances that you want to edit. You should also add a constructor to this class that news up all other models inside:
public class CompositeModel
{
public CompositeModel()
{
MediaPlanModel = new MediaPlan();
ContactInfoModel = new ContactInfo();
MediaPlanDateModel = new MediaPlanDate();
}
...
}
Then in post version of your Create action. You'll simply save each sub-model individually:
[HttpPost]
public ActionResult Create(CompositeModel model)
{
if (ModelState.IsValid)
{
db.MediaPlans.Add(model.MediaPlanModel);
db.ContactInfos.Add(model.ContactInfoModel);
db.MediaPlanDates.Add(model.MediaPlanDateModel)
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
For your edit version:
[HttpPost]
public ActionResult Edit(CompositeModel model)
{
if (ModelState.IsValid)
{
db.Entry(model.MediaPlanModel).State = EntityState.Modified;
db.Entry(model.ContactInfoModel).State = EntityState.Modified;
db.Entry(model.MediaPlanDateModel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}

Categories