Only the value of the Partial view form is not being passed to the controller.:/ FounderInvestmentVM is the one whose partial view i have created,this VM is inside of PropertyVM.The other values are passed to the controller but not that of Partial View. It always gives FounderInvestments Count=0 when i put a debugger and see it :/
This is my PROPERTY VM containing FounderInvestorVM:-
namespace propertyMgmt.ViewModel.PropertyViewModel
{
public class PropertyViewModel
{
public int? Id { get; set; }
[Required]
[DisplayName("Property Title")]
public string PropertyTitle { get; set; }
......
public List<FounderInvestmentViewModel> FounderInvestments { get; set; }=new List<>(FounderInvestmentViewModel);
}
}
This is FounderInvestorVM:-
public class FounderInvestmentViewModel
{
public int? Id { get; set; }
public int PropertyId { get; set; }
public int InvestorId { get; set; }
public double Investment { get; set; }
public int InstallmentPeriod { get; set; }
public IEnumerable<SelectListItem> FounderInvestorList { get; set; }
}
This is My COntroller:-
public ActionResult Create(PropertyViewModel _propertyViewModel)
{
if (ModelState.IsValid)
{
Property property = new Property();
property.Id = _propertyViewModel.Id ?? 0;
property.PropertyTitle = _propertyViewModel.PropertyTitle;
........other properties......
}
_propertyQueryProcessor.Create(property);
foreach(var investment in _propertyViewModel.FounderInvestments)
{
FounderInvestment _founderInvestment = new FounderInvestment
{
Id = investment.Id??0,
InstallmentPeriod = investment.InstallmentPeriod,
InvestorId = investment.InvestorId,
PropertyId = investment.PropertyId,
Investment = investment.Investment
};
_founderInvestmentQueryProcessor.Create(_founderInvestment);
}
THIS IS THE PARTIAL VIEW:-
#model propertyMgmt.ViewModel.FounderInvestmentViewModel
#{
ViewData.TemplateInfo.HtmlFieldPrefix = "PropertyViewModel"; //bind to main model
}
<div class="founderInvestmentDetails">
#using (Html.BeginCollectionItem("founderInvestmentDetails"))
{
#Html.HiddenFor(m => m.Id, new { #class = "id" })
#Html.HiddenFor(m=>m.PropertyId, new { #name = "PropertyId" })
<div class="form-group">
#Html.LabelFor(m => m.FounderInvestorList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m=>m.FounderInvestorList,Model.FounderInvestorList , "Select Investor", htmlAttributes: new {#class = "form-control"})
#Html.ValidationMessageFor(m => m.FounderInvestorList, "", new { #class = "text-danger" })
#Html.HiddenFor(m => m.InvestorId, new { #name = "InvestorId" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Investment, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m=>m.Investment, new { htmlAttributes = new { #class = "form-control",#type="number" } })
#Html.ValidationMessageFor(m => m.Investment, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InstallmentPeriod, htmlAttributes: new { #class = "control-label col-md-2", #type = "number" })
<div class="col-md-10">
#Html.EditorFor(m => m.InstallmentPeriod, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(m => m.InstallmentPeriod, "", new { #class = "text-danger" })
</div>
</div>
}
</div>
And finally this is the main VIEW:-
#model propertyMgmt.ViewModel.PropertyViewModel.PropertyViewModel
#using (Html.BeginForm("Create", "Property", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model => model.PropertyTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PropertyTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PropertyTitle, "", new { #class = "text-danger" })
</div>
</div>
.....(other form groups)
<div id="founderInvestmentDetails" class="form-group">
#foreach(var founderInvestmentDetails in Model.FounderInvestments)
{
#Html.Partial("_FounderInvestmentDetails", founderInvestmentDetails)
}
</div>
Sorry,there was a silly mistake here.As #Stephen Muecke pointed out
#using (Html.BeginCollectionItem("founderInvestmentDetails"))
Should be
#using (Html.BeginCollectionItem("FounderInvestments"))
Because BeginCollectionItem uses name of the Collection it is working on.
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>
public partial class News {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public News() {
this.Seminarparticipants = new HashSet<Seminarparticipants>();
}
public int NewsID { get; set; }
public string NewsTitle { get; set; }
public string NewsDescription { get; set; }
public string NewsImageUrl { get; set; }
public System.DateTime CreateDate { get; set; }
public bool IsSeminar { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Seminarparticipants> Seminarparticipants { get; set; }
}
}
this is my view
#using (Html.BeginForm("Create", "News", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.NewsTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewsTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NewsTitle, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.NewsDescription, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.NewsDescription, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.NewsDescription, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.NewsImageUrl, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.Kendo().Upload().Name("NewsImageUrl").Multiple(false)
</div>
</div>
<div class="form-group">
<div class="col-md-2">
</div>
<div class="col-md-10">
#Html.LabelFor(model => model.IsSeminar)
#Html.EditorFor(model => model.IsSeminar)
#Html.ValidationMessageFor(model => model.IsSeminar)
</div>
</div>
this is my controller
public ActionResult Create(News news, HttpPostedFileBase NewsImageUrl,bool IsSeminar)
{
if (ModelState.IsValid)
{
string imagename = "no-photo.jpg";
if (NewsImageUrl != null)
{
imagename = Guid.NewGuid().ToString().Replace("-", "") +
Path.GetExtension(NewsImageUrl.FileName);
NewsImageUrl.SaveAs(Server.MapPath("/NewsImage/Images/" + imagename));
//------------------------Resize Image------------------------------
ImageResizer img = new ImageResizer();
img.Resize(Server.MapPath("/NewsImage/Images/" + imagename),
Server.MapPath("/NewsImage/Thumb/" + imagename));
}
news.NewsImageUrl = imagename;
news.CreateDate = DateTime.Now;
//-------------Seminar------------------------
if (IsSeminar)
{
news.IsSeminar = true;
db.Seminarparticipants.Add(new Seminarparticipants()
{
NewsID = news.NewsID,
FullName = String.Empty,
Phone = String.Empty,
Email = String.Empty
});
}
else
{
news.IsSeminar = false;
}
db.News.Add(news);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(news);
}
why do you need to import IsSeminar into the controller's parameters?
When you send the object of News, the model binder will do it for you.
so my suggestion is:
public ActionResult Create(News news, HttpPostedFileBase NewsImageUrl)
{
if (ModelState.IsValid)
{
if (news.IsSeminar)
{
}
}
}
Because the News object has a property of IsSeminar with the same name, the binder will understand it and create the correct object.
I have PROPERTY VM which contains List<FounderInvestmentViewModel>.I have successfully inserted the partial view of FounderInvestmentViewModel into the Main Create Property view.
FounderInvestmentViewModel in turn contains List<InstallmentDetailsViewModel>. I have created the Partial View for InstallmentDetailsViewModel as _InstallmentDetails.cshtml and all the necessary actions.
I want to insert the _InstallmentDetails.cshtml into the partial view of FounderInvestmentViewModel which is in turn inserted into the Main View.
First let us take a look at the codes that I have used so far :--
Property View Model:-
public class PropertyViewModel
{
public int? Id { get; set; }
public string PropertyTitle { get; set; }
....other attributes....
public List<FounderInvestmentViewModel> FounderInvestments { get; set; } = new List<FounderInvestmentViewModel>();
}
FounderInvestmentViewModel:-
public class FounderInvestmentViewModel
{
public int? Id { get; set; }
public int InvestorId { get; set; }
public double Investment { get; set; }
public int InstallmentPeriod { get; set; }
public IEnumerable<SelectListItem> FounderInvestorList { get; set; }
public List<InstallmentDetailsViewModel> InstallmentDetails { get; set; } = new List<InstallmentDetailsViewModel>();
}
InstallmentDetailsViewModel:-
public class InstallmentDetailsViewModel
{
public int? Id { get; set; }
[Display(Name = "Pay Date")]
public List<DateTime> PayDates { get; set; }
[Required]
public List<double> InstallmentAmounts { get; set; }
}
PartialView for InstallmentDetails (_InstallmentDetails.cshtml):-
#model propertyMgmt.ViewModel.InstallmentDetailsViewModel
<div class="installmentDetails">
#using (Html.BeginCollectionItem("InstallmentDetails"))
{
#Html.HiddenFor(m => m.Id, new { #class = "id" })
<div class="form-group">
#Html.LabelFor(m => m.InstallmentAmounts, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.InstallmentAmounts, new { htmlAttributes = new { #class = "form-control", #type = "number" } })
#Html.ValidationMessageFor(m => m.InstallmentAmounts, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.PayDates, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.PayDates, new { htmlAttributes = new { #class = "form-control", #placeholder = "01/02/2017" } })
#Html.ValidationMessageFor(m => m.PayDates, "", new { #class = "text-danger" })
</div>
</div>
}
</div>
This _InstallmentDetails.cshtml is inserted into this _FounderInvestmentDetails.cshtml which is PartialView for FounderInvestmentDetails View Model:-
#model propertyMgmt.ViewModel.FounderInvestmentViewModel
<div class="founderInvestmentDetails">
#using (Html.BeginCollectionItem("FounderInvestments"))
{
#Html.HiddenFor(m => m.Id, new { #class = "id" })
<div class="form-group">
#Html.LabelFor(m => m.InvestorId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.InvestorId, Model.FounderInvestorList, "Select Investor", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.InvestorId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Investment, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.Investment, new { htmlAttributes = new { #class = "form-control", #type = "number" } })
#Html.ValidationMessageFor(m => m.Investment, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InstallmentPeriod, htmlAttributes: new { #class = "control-label col-md-2", #type = "number" })
<div class="col-md-10">
#Html.EditorFor(m => m.InstallmentPeriod, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(m => m.InstallmentPeriod, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group" id="installmentDetailsDiv">
#foreach (var InstallmentDetails in Model.InstallmentDetails)
{
#Html.Partial("_InstallmentDetails", InstallmentDetails)
}
</div>
<div class="form-group col-md-10">
<input type="button" class="btn btn-info btn-xs" value="Add Installment Details" onclick="addInstallmentDetails()" />
</div>
}
</div>
This is the MAIN CREATE VIEW :-
#model propertyMgmt.ViewModel.PropertyViewModel.PropertyViewModel
#{
ViewBag.Title = "Create";
}
<script src="~/Areas/Admin/themes/jquery/jquery.min.js"></script>
<h2>Property</h2>
#using (Html.BeginForm("Create", "Property", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Add Property</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PropertyTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PropertyTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PropertyTitle, "", new { #class = "text-danger" })
</div>
</div>
.....Other form Groups.....
<div id="founderInvestmentDetails">
#foreach(var FounderInvestments in Model.FounderInvestments)
{
#Html.Partial("_FounderInvestmentDetails", FounderInvestments)
}
</div>
<div class="form-group col-md-10" >
<input type="button" class="btn btn-info btn-xs" value="Add Founder Investors" onclick="addFounderInvestors()" />
</div>
</div>
}
This is My JS Code in the Main View:-
function addFounderInvestors() {
var url = '#Url.Action("FounderInvestmentDetails")';
var form = $('form');
var founders = $('#founderInvestmentDetails');
$.get(url, function (response) {
founders.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
};
function addInstallmentDetails() {
var url = '#Url.Action("InstallmentDetails")';
var form = $('form');
var installments = $('#installmentDetailsDiv');
$.get(url, function (response) {
installments.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
};
Controller Code :-
public PartialViewResult FounderInvestmentDetails()
{
var model = new FounderInvestmentViewModel {
FounderInvestorList = _investorQueryProcessor.GetInvestorByType(1).Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.InvestorName
})
};
//return PartialView(model);
return PartialView("_FounderInvestmentDetails", model);
}
public PartialViewResult InstallmentDetails()
{
return PartialView("_InstallmentDetails",new InstallmentDetailsViewModel());
}
public ActionResult Create()
{
if (Session["AdminName"] != null)
{
//ViewBag.Investors = SelectListItems;
List<FounderInvestmentViewModel> model = new List<FounderInvestmentViewModel>();
List<InstallmentDetailsViewModel> model2 = new List<InstallmentDetailsViewModel>();
return View(new PropertyViewModel());
}
else return Redirect("/Account/Login");
}
EDIT:-
Sorry this is what is throwing the exception -->> Collection.cshtml
PROCESS:- In the Main View, "Add Founder Investor Buttons" on click event adds the partial view _FounderInvestmentDetails.cshtml successfully.Now the "Add Installment Details" button is appended.Upon clicking this "Add Installment Details" button _InstallmentDetails.cshtml partial view should be appended,BUT this part is not working. When I click this button, I get the error "Object reference not set to an instance of an object" in the following code:-
#using HtmlHelpers.BeginCollectionItem
<ul>
#foreach (object item in Model)-->>ERROR CODE
{
<li>
#using (Html.BeginCollectionItem(Html.ViewData.TemplateInfo.HtmlFieldPrefix))
{
#Html.EditorFor(_ => item, null, "")
}
</li>
}
</ul>
The Paydates and Installments does not need to be <List> as it already is a PartialView and can be added multiple times.
public class InstallmentDetailsViewModel {
public int? Id { get; set; }
[Display(Name = "Pay Date")]
public List<DateTime> PayDates { get; set; }
[Required]
public List<double> InstallmentAmounts { get; set; }
}
I'm just a beginner in MVC, so please help me out and bear with my question. I have a scenario, but I can't figure it out how to create a proper ViewModel for this. I have a ProjectViewModel which consists of 4 tables related to one another.
So far my this is my ProjectViewModel.
public class ProjectViewModel
{
public int? ProjectId { get; set; }
public string ProjectName { get; set; }
public string ProjectLocation { get; set; }
public string ProjectDescription { get; set; }
public double? WorkArea { get; set; }
public string ModeOfPayment { get; set; }
public string Duration { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public double? TotalDirectCost { get; set; }
public double? ProfitSupervision { get; set; }
public double? TotalProjectCost { get; set; }
public List<ScopeOfWork> ScopeOfWork { get; set; }
}
public class ScopeOfWork
{
public int? ScopeOfWorkId { get; set; }
public string ScopeOfWorkName { get; set; }
public List<Materials> Materials { get; set; }
public int? ProjectId { get; set; }
}
public class Materials
{
public string MaterialName { get; set; }
public int? Quantity { get; set; }
public double? Cost { get; set; }
public int? ScopeOfWorkId { get; set; }
//This should be a select list
public IEnumerable<SelectListItem> Category { get; set;
}
Supposed to be a Project can have many ScopeOfWork object and the ScopeOfWork object can have many material object. And I will fill the category property of list of categories in the controller.
I just tried to create a view for List ScopeOfWork Object to see if the ScopeOfWork object will show but didn't show in the view.
Controller:
[HttpGet]
public ActionResult _CreateProject()
{
return View();
}
//POST: Create Project
[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
return View(project);
}
View:
#model MigratingDB.Models.ProjectViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ProjectViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ProjectId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProjectName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProjectLocation, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectLocation, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectLocation, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProjectDescription, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectDescription, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectDescription, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.WorkArea, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.WorkArea, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.WorkArea, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ModeOfPayment, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ModeOfPayment, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ModeOfPayment, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Duration, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Duration, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Duration, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StartDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StartDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StartDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EndDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EndDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EndDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalDirectCost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalDirectCost, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalDirectCost, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProfitSupervision, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProfitSupervision, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProfitSupervision, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalProjectCost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalProjectCost, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalProjectCost, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProjectStatus, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectStatus, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectStatus, "", 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>
Kindly give suggestions to setup a correct ViewModel for this and that will allow me to create project with multiple ScopeOfWork and its corresponding materials for each ScopeOfWork created.
UPDATED: I separated the classes in each ViewModel
ProjectViewModel
public class ProjectViewModel
{
//tbl Project
public int? ProjectId { get; set; }
public string ProjectName { get; set; }
public string ProjectLocation { get; set; }
public string ProjectDescription { get; set; }
public double? WorkArea { get; set; }
public string ModeOfPayment { get; set; }
public string Duration { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public double? TotalDirectCost { get; set; }
public double? ProfitSupervision { get; set; }
public double? TotalProjectCost { get; set; }
//SelectList
public int? ProjectStatus { get; set; }
//SelectList
public int? ForemanId { get; set; }
//SelectList
public int? ClientId { get; set; }
//tbl ScopeOfWork
public List<ScopeOfWorkViewModel> ScopeOfWork { get; set; }
}
ScopeOfWorkViewModel
public class ScopeOfWorkViewModel
{
public int? ScopeOfWorkId { get; set; }
public string ScopeOfWorkName { get; set; }
public List<MaterialsViewModel> Materials { get; set; }
public int? ProjectId { get; set; }
}
MaterialsViewModel
public class MaterialsViewModel
{
public string MaterialName { get; set; }
public int? Quantity { get; set; }
public double? Cost { get; set; }
public int? ScopeOfWorkId { get; set; }
public IEnumerable<SelectListItem> Category { get; set; }
}
I want to add a custom validation attribute to my model to check if any of the answers contain duplicates. That is, if the user types in the same answer for any of the fields, I want to display an error when they type in a duplicate answer.
Here's my model:
public class SecurityQuestions
{
public int Question1Id { get; set; }
public int Question2Id { get; set; }
public int Question3Id { get; set; }
public int Question4Id { get; set; }
public int Question5Id { get; set; }
public int Question6Id { get; set; }
[UniqueAnswersOnly]
public string Answer1 { get; set; }
[UniqueAnswersOnly]
public string Answer2 { get; set; }
[UniqueAnswersOnly]
public string Answer3 { get; set; }
[UniqueAnswersOnly]
public string Answer4 { get; set; }
[UniqueAnswersOnly]
public string Answer5 { get; set; }
[UniqueAnswersOnly]
public string Answer6 { get; set; }
}
Here's my attempt at custom attribute:
public class UniqueAnswersOnly: ValidationAttribute, IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Get a list of all properties that are marked with [UniqueAnswersOnly]
var props = validationContext.ObjectInstance.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueAnswersOnly)));
var values = new HashSet<string>();
//Read the values of all other properties
foreach(var prop in props)
{
var pValue = (string)prop.GetValue(validationContext.ObjectInstance, null);
if (prop.Name!=validationContext.MemberName && !values.Contains(pValue))
{
values.Add(pValue);
}
}
if (values.Contains(value))
{
return new ValidationResult("Duplicate answer", new[] { validationContext.MemberName });
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = metadata.DisplayName + " is required!",
ValidationType = "duplicateanswers"
};
yield return rule;
}
}
The problem I'm having now is the the validation is sucessful even though I enter in duplicate answers. I can still continue to next dialog (I am expecting validation to fail if duplicates are entered). I think it's because my custom attribute isn't being fired or hit because I added breakpoints but nothing is hit.
In my controller, I have if(ModelState.IsValid) { //continue to next dialog} and the model state does return valid.
You could create a custom validation attribute like this:
public class UniqueAnswersOnly : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Get a list of all properties that are marked with [UniqueAnswersOnly]
var props = validationContext.ObjectInstance.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueAnswersOnly)));
var values = new HashSet<string>();
//Read the values of all other properties
foreach(var prop in props)
{
var pValue = (string)prop.GetValue(validationContext.ObjectInstance);
if (prop.Name!=validationContext.MemberName && !values.Contains(pValue))
{
values.Add(pValue);
}
}
if (values.Contains(value))
{
return new ValidationResult("Duplicate answer", new[] { validationContext.MemberName });
}
return null;
}
}
and here is a test case:
public class SecurityQuestions
{
public int Question1Id { get; set; }
public int Question2Id { get; set; }
public int Question3Id { get; set; }
public int Question4Id { get; set; }
public int Question5Id { get; set; }
public int Question6Id { get; set; }
[UniqueAnswersOnly]
public string Answer1 { get; set; }
[UniqueAnswersOnly]
public string Answer2 { get; set; }
[UniqueAnswersOnly]
public string Answer3 { get; set; }
[UniqueAnswersOnly]
public string Answer4 { get; set; }
[UniqueAnswersOnly]
public string Answer5 { get; set; }
[UniqueAnswersOnly]
public string Answer6 { get; set; }
}
class Program
{
static void Main(string[] args)
{
var questions = new SecurityQuestions();
questions.Answer1 = "Test";
questions.Answer2 = "Test";
questions.Answer3 = "Test3";
questions.Answer4 = "Test4";
questions.Answer5 = "Test5";
questions.Answer6 = "Test6";
var vc = new ValidationContext(questions, null, null);
var results = new List<ValidationResult>();
var validationResult = Validator.TryValidateObject(questions, vc, results, true);
}
}
Edit:
I created a default MVC project and added your current code. That validation works just fine.
I added this to the home controller:
public ActionResult AskQuestions()
{
var questions = new SecurityQuestions();
return View(questions);
}
[HttpPost]
public ActionResult CheckQuestions(SecurityQuestions questions)
{
if (ModelState.IsValid)
{
return View();
}
else
{
return HttpNotFound();
}
}
And the AskQuestions view looks like this:
#model WebApplicationValidation.Models.SecurityQuestions
#{
ViewBag.Title = "AskQuestions";
}
<h2>AskQuestions</h2>
#using (Html.BeginForm("CheckQuestions", "Home",FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SecurityQuestions</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Question1Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question1Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question1Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Question2Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question2Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question2Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Question3Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question3Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question3Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Question4Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question4Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question4Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Question5Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question5Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question5Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Question6Id, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Question6Id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Question6Id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer2, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer2, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer2, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer3, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer3, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer3, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer4, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer4, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer4, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer5, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer5, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer5, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Answer6, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Answer6, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Answer6, "", 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")
}
If I run the app the ModelState.IsValid() is false if I enter two identical answers. And setting a breakpoint in the validation routine shows that it's being run.
You can implement the IValidatableObject interface in your Model like below, and use jQuery to take care of the validation on the client side.
public class SecurityQuestions : IValidatableObject
{
public int Question1Id { get; set; }
public int Question2Id { get; set; }
public int Question3Id { get; set; }
public int Question4Id { get; set; }
public int Question5Id { get; set; }
public int Question6Id { get; set; }
public string Answer1 { get; set; }
public string Answer2 { get; set; }
public string Answer3 { get; set; }
public string Answer4 { get; set; }
public string Answer5 { get; set; }
public string Answer6 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var securityAnswers = new List<string>();
securityAnswers.Add(this.Answer1);
securityAnswers.Add(this.Answer2);
securityAnswers.Add(this.Answer3);
securityAnswers.Add(this.Answer4);
securityAnswers.Add(this.Answer5);
securityAnswers.Add(this.Answer6);
bool hasDuplicates = securityAnswers.GroupBy(x => x).Where(g => g.Count() > 1).Any();
if (hasDuplicates)
{
yield return new ValidationResult(
"There are duplicate Answers...");
}
}
}