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

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();

Related

How to Implement CheckBoxFor in ASP.NET MVC 5?

I'm trying to understand how to use CheckBoxFor in MVC 5. I'm new to MVC and trying to learn how Entity Framework works using code first migrations.
Here are my main class Request:
[KeyAttribute] //One solution said to add this. Made no difference.
public virtual Guid ID { get; set; }
[Required(ErrorMessage = "First Name is required.")] [Display(Name = "First Name:")] [StringLength(25, ErrorMessage = "First Name must not exceed 25 characters.")]
public virtual string FName { get; set; }
[Display(Name = "Middle Initial:")] [StringLength(1, ErrorMessage = "Middle Initial must not exceed 1 character.")]
public virtual string MI { get; set; }
[Required(ErrorMessage = "Last Name is required.")] [Display(Name = "Last Name:")] [StringLength(25, ErrorMessage = "Last Name must not exceed 25 characters.")]
public virtual string LName { get; set; }
[Required(ErrorMessage = "Date of Birth is required.")] [Display(Name = "Date of Birth:")]
public virtual DateTime DOB { get; set; }
[Required(ErrorMessage = "Email is required.")] [Display(Name = "Email:")] [StringLength(25, ErrorMessage = "Email must not exceed 50 characters.")]
public virtual string Email { get; set; }
[Required(ErrorMessage = "Phone Number is required.")] [Display(Name = "Phone Number")] [StringLength(14, ErrorMessage = "Phone number must not exceed 14 characters.")]
public virtual string Phone { get; set; }
[Required(ErrorMessage = "Phone Type selection is required.")] [Display(Name = "Phone Type:")] [StringLength(4, ErrorMessage = "Phone Type selection must not exceed 4 characters.")]
public virtual string PhoneType { get; set; }
[Required(ErrorMessage = "Preferred Contact Method selection is required.")] [Display(Name = "Preferred Contact Method:")] [StringLength(16, ErrorMessage = "Preferred Contact Method selection must not exceed 16 characters.")]
public virtual PrefContactViewModel PrefContactViewModel {get;set;}
[Display(Name = "Preferred Contact Time:")] [StringLength(50, ErrorMessage = "Preferred Contact Time must not exceed 50 characters.")]
public virtual string PrefContactTime { get; set; }
...
Here is my ViewModel PrefContactViewModel:
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
Here is my controller RequestsController Index Action:
public ActionResult Index()
{
var requests = db.Requests.Include(r => r.PrefContactViewModel);
return View(requests.ToList());
}
Here is the same controller RequestForm Action:
public ActionResult RequestForm()
{
return View();
}
And here is my View:
#model AMYA.Models.Request
<div class="opensans margin-sides">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
<hr />
//FIRST NAME, MIDDLE INITIAL, LAST NAME
<div>
<div class="form-group col-md-5">
#Html.LabelFor(model => model.FName, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div>
#Html.EditorFor(model => model.FName, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group col-md-2">
#Html.LabelFor(model => model.MI, htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.MI, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group col-md-5">
#Html.LabelFor(model => model.LName, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div>
#Html.EditorFor(model => model.LName, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
</div>
//DATE OF BIRTH, EMAIL, PHONE NUMBER, PHONE TYPE
<div>
<div class="form-group col-md-3">
#Html.LabelFor(model => model.DOB, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div class="">
#Html.EditorFor(model => model.DOB, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group col-md-4">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div class="">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group col-md-3">
#Html.LabelFor(model => model.Phone, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div class="">
#Html.EditorFor(model => model.Phone, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group col-md-2">
#Html.LabelFor(model => model.PhoneType, htmlAttributes: new { #class = "control-label" })<span class="red">*</span>
<div class="">
#Html.CheckBoxFor(model => model.PrefContactViewModel.Checked, new { #class = "form-control" })
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="">
<input type="checkbox" id="PrefContact" name="PrefContact" value="#Request["PrefContact"]" />
#*#Html.EditorFor(model => model.PrefContact, new { htmlAttributes = new { #class = "checkbox" } })*#
#Html.CheckBoxFor(model => model.PrefContactViewModel.Checked, new { #class = "form-control" })
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
#Html.LabelFor(model => model.PrefContactTime, htmlAttributes: new { #class = "control-label" })
<div class="">
#Html.EditorFor(model => model.PrefContactTime, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
</div>
...
I just need to create a checkbox for each of the Name fields in my RequestForm Action. I've tried several of the solutions found here and elsewhere, but I cannot get the CheckBoxFor to work.
Can anyone offer me some insight into how to get CheckBoxFor to populate with the four selections? Or is there a way to do this using regular HTML <input> field?
And I get the following in my View:
(Update: I wrote this answer before I realised that the OP was using the List<> where each list item is its own field (i.e. as runtime customizable form builder) instead of representing each form field as a view-model class property, and this answer is not entire suitable for that scenario - but using EditorFor rather than CheckBoxFor should still work in this case).
You can't use an IEnumerable<T> as the static-type for a View's #model because ASP.NET model-binding (when repopulating the Model from the POST request body) works with List<T>, not IEnumerable<T>.
To use model-binding with a List<T> you need to use a for( int i = 0; i < list.Count; i++ )-style loop, you cannot use a foreach-style loop because it doesn't expose the item's index for use with the List<T>'s indexer property (okay, you can technically use foreach with .Select( (e, idx) => ... ) but it only results in more unproductive keyboard typing.
Change your ViewModel to its own class rather than directly exposing a List<T> anyway, because it makes everything else easier (such as adding a page title or other non-list content to the page).
Use a for() loop to render each item.
Your #model line must match the actual model object passed into View(Object).
Like so:
Controller action:
public class MyViewModel
{
// this is an example of an additional non-list-item property you'd add to the viewmodel:
[BindNever] // this is a one-way (server-to-client) property, so use [BindNever]
public String PageTitle { get; internal set; }
[Required]
public List<PrefContactViewModel> MyList { get; set; }
}
public ActionResult RequestForm()
{
MyViewModel vm = new MyViewModel()
{
PageTitle = "My list page!",
MyList = new List<PrefContactViewModel>
{
new PrefContactViewModel { ID = 1, Name = "Call", Checked = false },
new PrefContactViewModel { ID = 2, Name = "Text", Checked = false },
new PrefContactViewModel { ID = 3, Name = "Email", Checked = false },
new PrefContactViewModel { ID = 4, Name = "Traditional Mail", Checked = false },
}
};
return View( vm );
}
Your view:
#model MyViewModel
<div class="opensans margin-sides">
#using (Html.BeginForm()) {
<h1>#this.Model.PageTitle</h1>
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
<hr />
#for( int i = 0; i < this.Model.MyList.Count; i++ ) {
<!-- HTML markup removed for readability -->
#Html.LabelFor( m => m.MyList[i].FName )
#Html.TextBoxFor( m => m.MyList[i].FName )
#Html.ValidationMessageFor( m => m.MyList[i].FName )
#** Checkboxes should be wrapped in a <label> rather than using a separate label so you can't use #LabelFor **#
<label>#Html.CheckBoxFor( m => m.MyList[i].Checked ) #Html.DisplayNameFor( m => m.MyList[i].Checked )</label>
#Html.ValidationMessageFor( m => m.MyList[i].Checked )
#** etc for other members of each list item **#
} #** for loop **#
} #** </form> **#

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

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);
}

Cascading dropdowns In MVC5 On demand load is giving 500 Error

I currently have a data model that was generated from an existing database utilizing Entity Framework 6 (ASP.NET MVC 5). I'm currently working with two data tables: Contracts and Employees. They both are related to the Company table, so each table has CompanyID as a foreign key. When I scaffolded the Create Contract view, it currently has drop down lists for EmployeeID and CompanyID. What I'm trying to do is have the user select the CompanyID and only show the Employees that are tied to that Company.
I've tried using jQuery's getJSON method, but it's returning a 500 error due to a circular reference as I'm returning a JSON serialized Employee object which has a reference to Company (causing the circular reference error).
The Employee Model:
public partial class Employee
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Employee()
{
this.Contracts = new HashSet<Contract>();
this.HoleLoggings = new HashSet<HoleLogging>();
}
public int EmployeeID { get; set; }
public int CompanyID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public virtual Company Company { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Contract> Contracts { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<HoleLogging> HoleLoggings { get; set; }
}
The Contracts Model:
public partial class Contract
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Contract()
{
this.ContractDetails = new HashSet<ContractDetail>();
this.Platforms = new HashSet<Platform>();
this.ShiftReports = new HashSet<ShiftReport>();
}
public int ContractID { get; set; }
public int EmployeeID { get; set; }
public int CompanyID { get; set; }
public Nullable<System.DateTime> StartDate { get; set; }
public Nullable<System.DateTime> EndDate { get; set; }
public Nullable<bool> IsApproved { get; set; }
public virtual Company Company { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ContractDetail> ContractDetails { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Platform> Platforms { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ShiftReport> ShiftReports { get; set; }
public virtual Employee Employee { get; set; }
}
Here's the ActionMethod in my Contracts Controller:
public JsonResult GetEmployees(int id)
{
List<Employee> employees = new List<Employee>();
var employeeList = from e in db.Employees
where (e.CompanyID == id)
select e.
employees.AddRange(employeeList);
return Json(employees, JsonRequestBehavior.AllowGet);
}
The View's form:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Contract</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CompanyID, "CompanyID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CompanyID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CompanyID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EmployeeID, "EmployeeID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("EmployeeID", new SelectList(string.Empty, "Value", "Text"), "Please select an Employee", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.EmployeeID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StartDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StartDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StartDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EndDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EndDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EndDate, "", new { #class = "text-danger" })
</div>
</div>
And the jQuery:
$(function () {
$("#CompanyID").change(function () {
$("#EmployeeID").empty();
var token = $('[name=__RequestVerificationToken]').val();
$.getJSON('#Url.Action("GetEmployees")', { id: $(this).val() }, function (employees) {
var employeesSelect = $("#EmployeeID");
$.each(employees, function (i, employee) {
employeesSelect.append($('<option/>', {
value: employee.value,
text: employee.text
}));
});
});
});
});
Any suggestions would be greatly appreciated!
Return a collection of anonymous objects containing just the properties you need (no point sending a whole lot of data to the client that you don't even use)
employees.AddRange(employeeList);
var data = employees.Select(e => new
{
value = e.EmployeeID,
text = e.Name
};
return Json(data, JsonRequestBehavior.AllowGet);

How to bind Foreign Key? How to create Model objects with foreign key in controller class?

I have the following table relationships:
ProfileMeta 1 ----- 0...1 ProfileDetail
I'm getting a run-time error, after I click on submit on the Profile/Create page
Cannot insert the value NULL into column 'ID', table 'ContosoUniversity1.dbo.ProfileMeta'; column does not allow nulls. INSERT fails.
I've properly referenced ProfileMeta as a ForeignKey in Models/ProfileDetail.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
namespace ContosoUniversity.Models
{
//ProfileMeta is Principal Class
//ProfileDetail is Dependent Class
public class ProfileDetail
{
//classNameID or ID is interpreted by EF as PK.
public int ID { get; set; }
//ForeignKey("<Navigation Property Name>")
[Key, ForeignKey("ProfileMeta")]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]
public int ProfileMetaID {get; set;}
public string UserName { get; set; }
public string Age { get; set; }
public string Location { get; set; }
public string Gender { get; set; }
//Optional Details
public string HighSchool { get; set; }
public string UndergraduateSchool { get; set; }
public string GraduateSchool { get; set; }
public virtual ProfileMeta ProfileMeta { get; set; }
}
}
Models/ProfileMeta.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
namespace ContosoUniversity.Models
{
//ProfileMeta is Principal
//ProfileDetail is Dependent
public class ProfileMeta
{
public int ID { get; set; }
public string Username { get; set; }
public string password { get; set; }
public virtual ProfileDetail ProfileDetail {get; set;}
public virtual ICollection<MessageDetail> MessageDetails { get; set; }
public virtual ICollection<ConversationMeta> ConversationMetas { get; set; }
}
}
For a functional signup/register page, I have created a Model: "Register", that references ProfileMeta and ProfileDetail.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ContosoUniversity.Models
{
public class Register
{
public ProfileMeta ProfileMeta_ { get; set; }
public ProfileDetail ProfileDetail_ { get; set; }
}
}
Views/Profile/Create.cshtml:
#model ContosoUniversity.Models.Register
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Profile</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ProfileMeta_.Username, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileMeta_.Username, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileMeta_.Username, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileMeta_.password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileMeta_.password, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileMeta_.password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.Age, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.Age, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.Age, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.Location, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.Location, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.Location, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.Gender, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.Gender, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.Gender, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.HighSchool, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.HighSchool, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.HighSchool, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.UndergraduateSchool, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.UndergraduateSchool, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.UndergraduateSchool, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfileDetail_.GraduateSchool, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfileDetail_.GraduateSchool, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfileDetail_.GraduateSchool, "", 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>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
ProfileController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Register register)
{
if (ModelState.IsValid)
{
//Add 1 ProfileMeta row and 1 linked ProfileDetail row
ProfileMeta profileMeta = new ProfileMeta();
profileMeta.Username = register.ProfileMeta_.Username;
profileMeta.password = register.ProfileMeta_.password;
ProfileDetail profileDetail = new ProfileDetail();
//profileDetail.ID = register.ProfileDetail_.ID;
//How to assign FK below?
profileDetail.ProfileMetaID = register.ProfileDetail_.ID;
profileDetail.UserName = register.ProfileDetail_.UserName;
profileDetail.Age = register.ProfileDetail_.Age;
profileDetail.Location = register.ProfileDetail_.Location;
profileDetail.ProfileMeta = profileMeta;
//profileDetail.UserName = register.ProfileDetail_.UserName;
//profileDetail.Age = register.ProfileDetail_.Age;
//profileDetail.Location = register.ProfileDetail_.Location;
profileMeta.ProfileDetail = profileDetail;
db.ProfileMetas.Add(profileMeta);
db.ProfileDetails.Add(profileDetail);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(register);
}
Why can't I add a row of ProfileMeta and a corresponding row of ProfileDetail? I thought the database is automatically generating Primary Key (or ID)'s.
Is it necessary to explicitly set the Navigation Properties for a given Model object in the controller?
Also, do I need to explicitly set the Foreign Key: "ProfileMetaID" in the ProfileDetail object I had created?
First thing, I would like to suggest to use EF code conventions instead of explicitly defining the relationships in EF (Your declaration is correct , it's just a personal preference for me and i think it's cleaner).
So instead of this:
//ForeignKey("<Navigation Property Name>")
[Key, ForeignKey("ProfileMeta")]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]
public int ProfileMetaID {get; set;}
public virtual ProfileMeta ProfileMeta { get; set; }
You can do:
public int ProfileMetaID { get; set; }
public virtual ProfileMeta profileMeta { get; set; }
EF will create the FK automatically for you in the DB.
As for your question , I highly recommend that you use ViewModel and include all the attributes you want work with. but if you want to use your existing models , You can use ProfileMeta as your base model and bind the values of ProfileDetail using model.profileDetail.Age
here's an example:
Your models (Short version)
public class ProfileDetail
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string UserName { get; set; }
public string Age { get; set; }
public string Location { get; set; }
public string Gender { get; set; }
public int ProfileMetaID { get; set; }
public virtual ProfileMeta profileMeta { get; set; }
}
public class ProfileMeta
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Username { get; set; }
public string password { get; set; }
public virtual ProfileDetail profileDetail {get; set;}
}
Your controller:
(No need to explicitly set the values of your attributes since the relationship and EF will take care of it.)
[HttpGet]
public ActionResult Create()
{
return View(new ProfileMeta());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Register register)
{
if (ModelState.IsValid)
{
db.ProfileMetas.Add(profileMeta);
db.ProfileDetails.Add(profileDetail);
db.SaveChanges();
}
}
In your View (Just a snapshot)
<div class="form-group">
#Html.LabelFor(model => model.Username, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Username, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Username, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.password, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.profileDetail.Age, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.profileDetail.Age, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.profileDetail.Age, "", new { #class = "text-danger" })
</div>
</div>
And finally here's a fiddle to illustrate the whole concept : https://dotnetfiddle.net/N0prMA
In the fiddle you will see that if you provide an age it will submit it back and display it in the page.

"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.

Categories