Passing ViewModel to Controller doesn't fully work - c#

I have been spending all day on and issue searching the web for a solution, however I couldn't find one for my error. It is getting quite frustrating and I have a feeling its one one those facepalm mistakes. I hope you guys can find the time to help me out.
I am working on a Create View which uses a ViewModel. I am using a BeginForm and submit input type button. Now it passes the first property in the ViewModel but every other property passes as null.
Here is my ViewModel + Models
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FitnessSundhed.Models;
namespace FitnessSundhed.ViewModels
{
public class WorkoutViewModel
{
public Workouts Workout { get; set; }
public Sets Sets { get; set; }
public Execises Execises { get; set; }
}
}
Some of these property are supposed to lists, but I choose not make them lists untill I had tried this in case of issues. Dont mind it.
Heres the Workouts Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace FitnessSundhed.Models
{
public class Workouts
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Equipment { get; set; }
public string Targets { get; set; }
public List<Sets> Sets { get; set; }
}
}
Sets Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace FitnessSundhed.Models
{
public class Sets
{
public int Id { get; set; }
public string SetName { get; set; }
public string SetDesc { get; set; }
public List<Execises> Execises { get; set; }
}
}
Execises Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace FitnessSundhed.Models
{
public class Execises
{
public int Id { get; set; }
public string ImagePath { get; set; }
public string Name { get; set; }
public int Reps { get; set; }
public string Note { get; set; }
}
}
Basically Workouts contains a list of Sets which contains a list of execises.
View
#model FitnessSundhed.ViewModels.WorkoutViewModel
#{
ViewBag.Title = "Action";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Action</h2>
#using (Html.BeginForm("Create", "Workouts", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Workouts</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Workout.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Workout.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Workout.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Workout.Author, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Workout.Author, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Workout.Author, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Workout.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Workout.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Workout.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Workout.Equipment, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Workout.Equipment, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Workout.Equipment, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Workout.Targets, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Workout.Targets, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Workout.Targets, "", new { #class = "text-danger" })
</div>
</div>
<h4>Sets</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Sets.SetName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sets.SetName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sets.SetName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Sets.SetDesc, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sets.SetDesc, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sets.SetDesc, "", 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>
Controller
[HttpPost]
public ActionResult Create(WorkoutViewModel model)
{
Workouts workout = new Workouts();
workout = model.Workout;
workout.Sets.Add(model.Sets); // NullReferenceException here.
_context.Workoutss.Add(workout);
_context.SaveChanges();
return RedirectToAction("Library", "Workouts");
}
Execises hasn't been coded in yet.
So all in all. It is possible for the view to pass the ViewModel and I can add Workout to the database ( I have previous done it), however when I try and use Sets or Execises I get the ''System.NullReferenceException: 'Object reference not set to an instance of an object.'' error when I try and add the set to the workout in the controller.
I hope I have described it in somewhat good details. If not please do ask for more information.

Your model starts out as a blank slate here.
Workouts workout = new Workouts();
That means you can't Add to a List unless it exists. So when you call
workout.Sets.Add(model.Sets); // NullReferenceException here.
workout.Sets is null. Your object never initializes it.
Do this in your model.
public List<Sets> Sets { get; set; } = new List<Sets>();
Conversely, if there are times you don't want it initialized (ORM bindings being angry or whatever), make sure you call it before you use it, like so.
Workouts workout = new Workouts();
workout = model.Workout;
workout.Sets = new List<Sets>();
workout.Sets.Add(model.Sets);

Related

Entity Framework 6: entities and relationships

I am a solo and very beginner learner. I am trying to create a simple code first app with a database using EF6. I cannot understand how to insert the data of a entity inside another by the frontend.
I have two entities:
public class Movie
{
[Key]
public int Id { get; set; }
public string Title{ get; set; }
public int ActorId { get; set; }
public ICollection<Actor> Actors { get; set; }
}
public class Actor
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("MovieId")]
public ICollection<Movie> Movies { get; set; }
}
The controller.
public ActionResult AddMovie()
{
var actorsList = (from Name in ctx.Attors select Name).ToList();
ViewBag.Actors = new SelectList(actorsList, "Name", "Name");
return View(new Film());
}
[HttpPost]
public ActionResult PerformAddMovie(Movie m)
{
try
{
ctx.Movies.Add(m);
ctx.SaveChanges();
return RedirectToAction("Index", "Home");
}
catch(Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
return RedirectToAction("Index", "Home");
}
#model Cinema.Models.Movie
#{
ViewBag.Title = "AddMovie";
}
<h2>AddFilm</h2>
#{
var list = ViewBag.Actors as SelectList;
}
#using (Html.BeginForm("PerformAddMovie", "Movie", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Film</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ActorId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ActorId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ActorId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Actors, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.Actors, list, "---Select---", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Actors, "", 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")
}
After adding some movies into the database by the frontend web page, in the addmovie web page I can select one of them by the dropdown list, but when I save the movie nothing happens inside the third table created with movieid and actorid, it is always empty.
What am I doing wrong?
The Model is wrong
public class Movie
{
[Key]
public int Id { get; set; }
public string Title{ get; set; }
public int ActorId { get; set; }
public virtual Actor Actor { get; set; } // It should be one to one relationship
}
public class Actor
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
//[ForeignKey("MovieId")] This is unneccessary
public ICollection<Movie> Movies { get; set; }
}
Then u can select the Actor Id as key while display actor name in the select list
ViewBag.Actors = new SelectList((from s in db.Actor
select new {Id = s.Id, Name = s.Name }),
"Id", "Name");
Remove this under your html as the Id is attached to the dropdown list
<div class="form-group">
#Html.LabelFor(model => model.ActorId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ActorId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ActorId, "", new { #class = "text-danger" })
</div>
</div>
change the dropdownlist to this
<div class="form-group">
#Html.LabelFor(model => model.ActorId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.ActorId, (Selectlist)ViewBag.Actor, "---Select---", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ActorId, "", new { #class = "text-danger" })
</div>
</div>

How to add rows dynamically at runtime in ASP.NET MVC using c#? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
My model is:
public partial class transaction
{
public int transaction_id { get; set; }
public Nullable<int> transaction_req_no { get; set; }
public Nullable<System.DateTime> transaction_date { get; set; }
public Nullable<int> transaction_reqst_by { get; set; }
public Nullable<int> transaction_priority { get; set; }
public Nullable<int> transaction_item { get; set; }
public Nullable<int> transaction_site { get; set; }
public Nullable<int> transaction_dept { get; set; }
public Nullable<int> transaction_reqst_status { get; set; }
public virtual department department { get; set; }
public virtual item item { get; set; }
public virtual person person { get; set; }
public virtual priority priority { get; set; }
public virtual request request { get; set; }
public virtual site site { get; set; }
public virtual status status { get; set; }
}
The view:
<div class="form-horizontal">
<h4>transaction</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.transaction_req_no, "transaction_req_no", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_req_no", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_req_no, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.transaction_date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.transaction_date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_reqst_by, "transaction_reqst_by", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_reqst_by", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_reqst_by, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_priority, "transaction_priority", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_priority", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_priority, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_item, "transaction_item", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-4">
#Html.DropDownList("transaction_item", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_item, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_site, "transaction_site", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_site", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_site, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_dept, "transaction_dept", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_dept", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_dept, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.transaction_reqst_status, "transaction_reqst_status", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("transaction_reqst_status", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.transaction_reqst_status, "", 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>
and controller is generated from scaffolding. No additional code is added there. i would like to add multiple transaction items. public Nullable<int> transaction_item { get; set; } The scaffolded view generates a drop down for this. I need a used to be able to add more than one items from front end.
I read this, this and this. The first one looks promising but still i am not able to get how do i add data into database even if i can add elements in front end by using jQuery?
As far as frontend is related you can see answer to this. You can use FormCollection instead of model directly. Then you can assign the values to your rows as :
codelen1
codelen2
codelen3
then you can create a backend method to accept the formcollection as a parameter. In that method you can loop on the number of rows as follows:
[HttpPost]
publict ActionResult DynamicRows(FormCollection f)
{
int i = Convert.ToInt32(f["level"]); // you will set the number of rows added from JQuery
for (int a = 0; a <= i; a++)
{
ABC lvl = new ABC();
lvl.S1 = a.ToString();
lvl.N1 = a;
lvl.N2 = Convert.ToInt32(f["codelen" + a.ToString()]); // here you are getting back the values as codelen1..codelen2..codelen3
// your other logic comes here... anything you want to do with the data
WebDb.ABC.Add(lvl);
}
WebDb.SaveChanges();
}
Create it using JQuery, then use the jquery prop InserAfter() and add new row.
ex:
var r = $("HTML CODE");
$(r).insertAfter($("#BlaBlaBla"));

ASP.NET MVC 5 : Editing multiple records in a view and model binding (Bulk data update)

I need correction on the code below.
I have 2 classes "Employee" and "Child".
When I want to create a new Employee, I would like to be able to create in that same form the related Child (2 Children maximum).
Below are the models
public class Employee
{
public int EmployeeID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int ChildID { get; set; }
public virtual ICollection<Child> Childs { get; set; }
}
public class Child
{
public int ChildID { get; set; }
public string NameChild { get; set; }
public string SurnameChild { get; set; }
public virtual Employee Employee { get; set; }
}
The Employee controller
public class EmployeController : Controller
{
private ComideContext db = new ComideContext();
// GET: Employe/Create
public ActionResult Create()
{
List<Child> model = new List<Child>();
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EmployeID,Name,Surname,ChildID")] Employee employee)
{
if (ModelState.IsValid)
{
db.Employes.Add(employe);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employe);
}
}
The View of the Employee form
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Employe</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<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>
#for (int i=0; i<2; i++ )
{
<div class="form-group">
#Html.LabelFor(model => model.NameChild, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NameChild, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NameChild, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SurnameChild, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SurnameChild, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SurnameChild, "", 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>
}
Any help/thoughts would be most appreciated.
Thank you.
Disclaimer: I've done this on MVC 3. I don't know if there is an easier way to do it in MVC 5.
You will need to index the children in the view code so when you submit the form the model binder knows how to construct the Employee object.
This could be done this way:
for (var i = 0 ; i < collectionSize; i++)
{
#Html.EditorFor(child => Model.Childs[i].ChildID)
#Html.ValidationMessageFor(child => Model.Childs[i].ChildID)
[....]
}
This would require your collection to be initialized when passed to the view.
Another way you can bind to collection is to programatically build the html components' name to include their index in the list.
See the example below:
<input name="Employee.Childs[0].ChildID" >
<input name="Employee.Childs[1].ChildID" >

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