"DRY up" MVC models when using SelectListItem - c#

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.

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>

Passing ViewModel to Controller doesn't fully work

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

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

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

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.

Categories