I got two model classes like this
public class JobViewModel
{
public int Id { get; set; }
public float Price { get; set; }
public int JobSubCategoryId { get; set; }
public string jobDescription { get; set; }
public List<Machine> Machines { get; set; }
public int SpecialRequirementId { get; set; }
}
public class Machine
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Brand { get; set; }
}
Now, When a user creates a new "Job" he must be able to create multiple "Machine" with it. How can I make this possible inside my create view?
As well as Stephen's method, you can achieve all this relatively simply, without partial views.
In the first instance, you should revise your model a little. Add to Machine:
// Foreign key
public int JobID { get; set; }
// Navigation properties
public virtual Job Job{ get; set; }
The Job model, you have not shown, but it needs to be:
public class Job
{
public int Id { get; set; }
public float Price { get; set; }
public int JobSubCategoryId { get; set; }
public string JobDescription { get; set; }
public int SpecialRequirementId { get; set; }
public virtual List<Machine> Machines { get; set; }
}
Here is my complete JobViewModel:
public class JobViewModel
{
public JobViewModel()
{
Machines = new List<Machine>();
}
public int Id { get; set; }
public float Price { get; set; }
public int JobSubCategoryId { get; set; }
public string JobDescription { get; set; }
public int SpecialRequirementId { get; set; }
public List<Machine> Machines { get; set; }
public string NewMachineBrand { get; set; }
public string NewMachineType { get; set; }
public string NewMachineName { get; set; }
public void AddMachine()
{
Machine tmp = new Machine { Brand = NewMachineBrand, Type = NewMachineType, Name = NewMachineName };
Machines.Add(tmp);
NewMachineBrand = NewMachineType = NewMachineName = null;
}
public Job GetJob()
{
Job job = new Job();
job.JobDescription = JobDescription;
job.Price = Price;
job.JobSubCategoryId = JobSubCategoryId;
job.SpecialRequirementId = SpecialRequirementId;
job.Machines = new List<Machine>();
foreach (Machine m in Machines)
{
job.Machines.Add(m);
}
return job;
}
}
When creating your create view based on JobViewModel, you will need to add two things that are not defaulted for you, firstly a table to hold the new Machines, and secondly a button to add each machine in turn.
My complete create.cshtml view looks like this:
#model JobMachinesMVC.Models.JobViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Job</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Price, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Price, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Price, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.JobSubCategoryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.JobSubCategoryId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.JobSubCategoryId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.JobDescription, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.JobDescription, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.JobDescription, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SpecialRequirementId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SpecialRequirementId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SpecialRequirementId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.NewMachineBrand, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewMachineBrand, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NewMachineBrand, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.NewMachineType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewMachineType, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NewMachineType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.NewMachineName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewMachineName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NewMachineName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<table>
<thead>
<tr>
<th style="text-align:right">
#Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Brand)
</th>
<th style="text-align:right">
#Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Name)
</th>
<th style="text-align:right">
#Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Type)
</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Machines.Count; i++)
{
<tr>
<td style="text-align:right">#Html.HiddenFor(m => m.Machines[i].Id)#Html.DisplayFor(m => m.Machines[i].Brand)#Html.HiddenFor(m => m.Machines[i].Brand)</td>
<td style="text-align:right">#Html.DisplayFor(m => m.Machines[i].Name)#Html.HiddenFor(m => m.Machines[i].Name)</td>
<td style="text-align:right">#Html.DisplayFor(m => m.Machines[i].Type)#Html.HiddenFor(m => m.Machines[i].Type)</td>
</tr>
}
</tbody>
</table>
</div>
<div class="form-group">
<input type="submit" value="Add Machine" name="addmachine" class="btn btn-default" />
</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")
}
A couple of things to note here. I always include a #Html.HiddenFor in such a sub-table, because #Html.DisplayFor items can be lost when posting back to the controller. Secondly, there are two input type="submit" on the same View. One is given a name attribute. This is so that the Controller can distinguish between the two clicks.
The relevant lines from my controller are these:
// GET: Jobs/Create
public ActionResult Create()
{
JobViewModel job = new JobViewModel();
return View(job);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(JobViewModel jobvm)
{
if (Request.Form["addmachine"] != null)
{
jobvm.AddMachine();
ModelState.Remove("NewMachineName");
ModelState.Remove("NewMachineType");
ModelState.Remove("NewMachineBrand");
return View(jobvm);
}
if (ModelState.IsValid)
{
Job job = jobvm.GetJob();
db.Jobs.Add(job);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(jobvm);
}
If "addmachine" is clicked, the new machine values get added to the Machines List, get reset and the form is redisplayed. Note you need to set the ModelState even though the ViewModel sets the values to null, otherwise you old values persist in the view. If Create is clicked, the model is checked for ValidState, and the job is saved. What about the Machine table? Because the models are set up as outlined above, internally MVC knows that it has to save the values to Machine as well.
Please note that the above illustration is very crude. I have applied no styling apart from that which you get "out of the box". You will want to tidy this up (a lot!), but I hope I have given you a good start in one way to approach this problem.
Related
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>
I have this Issue class. Is there
public class Issue
{
public int IssueId { get; set; }
[Required]
public string ReporterName { get; set; }
[Required]
public string Description { get; set; }
public int? IssueTypeId { get; set; }
public virtual IssueType type { get; set; }
}
Then this is the view I have so I can change the value of the IssueTypeId. However when I try to save it in the database with that code in the controller, I am having an error saying that the ReporterName and Description are still required. How can I only update one specific value in database, in this case IssueTypeId?
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Issue</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.IssueId)
<div class="form-group">
#Html.LabelFor(model => model.IssueTypeId, "IssueTypeId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("IssueTypeId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.IssueTypeId, "", 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>
}
My code in controller
public ActionResult AssignFixer(int issueId, int issueTypeId)
{
var issue = new Issue { IssueId = issueId , IssueTypeId= issueTypeId};
using (var newContext = new SystemContext())
{
newContext.Issues.Attach(issue);
newContext.Entry(issue).Property(i => i.IssueTypeId).IsModified = true;
newContext.SaveChanges();
}
return View(issue);
}
Instead of attaching the issue in newContext.Issues. First get the instance and then update it. Like:
var existingIssue = newContext.Issues.Where(i => i.IssueId == issueId).FirstOrDefault();
existingIssue.IssueTypeId = issueTypeId;
newContext.SaveChanges();
I have no idea why I am getting this exception. I am trying to implement a simple upload image functionality.
I am getting the error when I am trying to save the image along with the rest of the data.
I suspect the the problem is in the view, because I used this code from my model and action method in another project and the code worked.
Can someone help with this. I believe I am close.
Model :
public class Company
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CompanyId { get; set; }
public byte[] ImageData { get; set; }
[NotMapped]
public HttpPostedFileBase UploadImage { get; set; }
[NotMapped]
public string ImageBase64 => System.Convert.ToBase64String(ImageData);
public string CompanyName { get; set; }
public string CompanyAddress { get; set; }
public string CompanyCountry { get; set; }
public string CompanyCity { get; set; }
public string CompanyPostalCode { get; set; }
public string CompanyPhoneNumber { get; set; }
public string CAId { get; set; }
}
Controller :
public ActionResult Create([Bind(Include = "CompId,ImageData,CompanyName,CompanyAddress,CompanyCountry,CompanyCity,CompanyPostalCode,CompanyPhoneNumber,EmailCA")] Company company, HttpPostedFileBase UploadImage)
{
if (ModelState.IsValid)
{
byte[] buf = new byte[UploadImage.ContentLength];
UploadImage.InputStream.Read(buf, 0, buf.Length);
company.ImageData = buf;
db.Companies.Add(company);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(company);
}
View :
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Company</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ImageData, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="col-md-10">
#Html.TextBoxFor(model => model.ImageData, new { type = "file" })
#*<input type="file" name="ImageData" class="input-files" />*#
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CompanyName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CompanyName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CompanyName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CAId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CAId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CAId, "", 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>
In your view your using #Html.TextBoxFor(model => model.ImageData, new { type = "file" }) where I think you should use #Html.TextBoxFor(model => model.UploadImage, new { type = "file" }) instead.
And in order to make that pass I think you need to add the UploadImage property to the Bind(Include) string as following:
public ActionResult Create([Bind(Include = "CompId,ImageData,CompanyName,CompanyAddress,CompanyCountry,CompanyCity,CompanyPostalCode,CompanyPhoneNumber,EmailCA,UploadImage")] Company company)
I have this ViewModel:
public class Rapport
{
[Key]
public int RapportId { get; set; }
public RapportAnomalie rapport { get; set; }
public IEnumerable<RefAnomalie> refAnomalies { get; set; }
}
which has two models in it, RapportAnomalie :
public class RapportAnomalie
{
[Key]
public int codeRapport { get; set; }
public DateTime date { get; set; }
public String heure { get; set; }
public String etat { get; set; }
[ForeignKey("codeAgence")]
public virtual Agence agence { get; set; }
public int codeAgence { get; set; }
public IEnumerable<LigneRapportAnomalie> lignesRapport { get; set; }
}
and RefAnomalie.
However when I want to send data from view to controller from a form, I keep getting an exception.
The view :
#model InspectionBanque.Models.Rapport
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>RapportAnomalie</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.rapport.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.date, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.heure, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.heure, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.etat, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.etat, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.codeAgence, "codeAgence", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("codeAgence", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.rapport.codeAgence, "", 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>
}
#for (int i = 0; i < Model.refAnomalies.Count(); i++)
{
<div class="col-md-10">
#Html.DisplayFor(model => model.refAnomalies.ElementAt(i).libele)
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
and then the controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create( Rapport rapportAnomalie)
{
if (ModelState.IsValid)
{
RapportAnomalie rp = new RapportAnomalie();
db.rapportAnomalies.Add(rapportAnomalie.rapport);
db.SaveChanges();
return RedirectToAction("Index");
}
var refanomal = from r in db.refAnnomalies
select r;
Rapport rapport = new Rapport { rapport = rapportAnomalie.rapport, refAnomalies = refanomal.ToArray() };
ViewBag.codeAgence = new SelectList(db.Agences, "codeAgence", "intituleAgence", rapportAnomalie.rapport.codeAgence);
return View(rapport);
}
Any ideas what's wrong with it?
I think you are getting the problem because the lignesRapport field is not initialized in your model RapportAnomalie
Create a constructor and initialize lignesRapport . I believe the problem will go away.
public RapportAnomalie()
{
lignesRapport = new List <LigneRapportAnomalie>();
}
Good luck
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.