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; }
}
Related
I have created a viewmodel like this:
public class M_Master
{
public string SUPWH { get; set; }
public string STKGL { get; set; }
public string SALGL { get; set; }
public string COSGL { get; set; }
public decimal ROQ { get; set; }
public int MIN { get; set; }
public int MAX { get; set; }
public int LT { get; set; }
public string PRODSPEC { get; set; }
public string REMK1 { get; set; }
public string REMK2 { get; set; }
public decimal openingQty { get; set; }
public decimal openingAmt { get; set; }
public decimal onHandQty { get; set; }
public decimal ytdReceiptQty { get; set; }
}
Then I use this ViewModel to create a View. So the view looks something like the below:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>INV_Master</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PROD, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PROD, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PROD, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.WH, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.WH, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.WH, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DESCR, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DESCR, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DESCR, "", new { #class = "text-danger" })
</div>
</div>
What I realise is that when I visit the page/view, the html inputfields by default are filled in with values like 0 for those properties in the ViewMoel which are int, decimal, datetime. How do I make sure that all the input fields by default are empty ?
Use Nullable types like int?, decimal?, and DateTime?. When objects are instantiated, each property will be initialized to the datatype default. Int defaults to 0, and int? defaults to null. And so on. You can still put a [Required] on the property to ensure they are entered by the user before being posted back to the server.
I have two entities Gabarit and Local_Gabarit and i have a foreign key that related those entities called CodeBarre which is the primary Key of Gabarit. So on my Gabarit Edit Page i don't want to edit Gabarit attributes but the related attributes existing in Local_Gabarit such as Armoire and Emplacement.
Please, I need your help and Thank you :)
Gabarit Model:
public Gabarit()
{
this.Demande = new HashSet<Demande>();
this.Local_Gabarit = new HashSet<Local_Gabarit>();
this.Mvt_Gabarit = new HashSet<Mvt_Gabarit>();
}
public int CodeBarre { get; set; }
public string Designation { get; set; }
public string Photo { get; set; }
public string Exemplaire { get; set; }
public Nullable<int> Produit { get; set; }
public Nullable<int> Poste { get; set; }
public virtual ICollection<Demande> Demande { get; set; }
public virtual ICollection<Local_Gabarit> Local_Gabarit { get; set; }
public virtual ICollection<Mvt_Gabarit> Mvt_Gabarit { get; set; }
Local_Gabarit Model:
public partial class Local_Gabarit
{
public int id_local { get; set; }
public Nullable<int> Emplacement { get; set; }
public string Armoire { get; set; }
public int CodeBarre { get; set; }
public int id_demande { get; set; }
public string InOut { get; set; }
public virtual Demande Demande { get; set; }
public virtual Gabarit Gabarit { get; set; }
}
GabaritViewModel:
public class GabaritViewModel
{
public int CodeBarre { get; set; }
public string Designation { get; set; }
public string Photo { get; set; }
public Nullable<int> Produit { get; set; }
public string Exemplaire { get; set; }
public Nullable<int> Poste { get; set; }
public int id_local { get; set; }
public Nullable<int> Emplacement { get; set; }
public string Armoire { get; set; }
}
GabaritsController:
// GET: Gabarits/Edit/5
public ActionResult Edit(int? id)
{
var localrepository = new LocalGabaritRepository(db);
var dbgabarit = gabaritrepository.GetById(id);
var req = localrepository.Get(p => p.CodeBarre == dbgabarit.CodeBarre).SingleOrDefault();
var armoire = req.Armoire;
var emplacement = req.Emplacement;
if (dbgabarit != null)
{
var gabarit = new GabaritViewModel()
{
CodeBarre = dbgabarit.CodeBarre,
Designation = dbgabarit.Designation,
Photo = dbgabarit.Photo,
Exemplaire = dbgabarit.Exemplaire,
Poste=dbgabarit.Poste,
Produit=dbgabarit.Produit,
Emplacement=emplacement,
Armoire=armoire,
};
return View(gabarit);
}
else
return HttpNotFound();
}
// POST: Gabarits/Edit/5
// 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 Edit( GabaritViewModel gabarit)
{
var localrepository = new LocalGabaritRepository(db);
if (ModelState.IsValid)
{
var req= localrepository.Get(p => p.CodeBarre == gabarit.CodeBarre).SingleOrDefault();
var armoire = req.Armoire;
var emplacement = req.Emplacement;
var idl = req.id_local;
var idd = req.id_demande;
var InOut = req.InOut;
var CodeBarre = req.CodeBarre;
var dblocal = new Local_Gabarit()
{
Emplacement = emplacement,
Armoire = armoire,
id_local=idl,
id_demande=idd,
InOut=InOut,
CodeBarre=CodeBarre
};
localrepository.Update(dblocal);
}
return View(gabarit);
}
Edit.cshtml (Gabarit):
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Gabarit</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.CodeBarre)
<div class="form-group">
#Html.LabelFor(model => model.CodeBarre, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CodeBarre, new { htmlAttributes = new { #class = "form-control", #readonly = "readonly" } })
#Html.ValidationMessageFor(model => model.CodeBarre, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group" >
#Html.LabelFor(model => model.Designation, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Designation, new { htmlAttributes = new { #class = "form-control", #readonly = "readonly" } })
#Html.ValidationMessageFor(model => model.Designation, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Photo, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<img src="#Url.Content(Model.Photo)" width="200" height="200" />
#Html.ValidationMessageFor(model => model.Photo, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Produit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Produit, new { htmlAttributes = new { #class = "form-control", #readonly = "readonly" } })
#Html.ValidationMessageFor(model => model.Produit, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Armoire, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Armoire, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Armoire, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Emplacement, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Emplacement, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Emplacement, "", 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>
}
I don't know if i'm in the right way or not but this is what i tried to do.
Thank you.
I am trying to save changes to in a basic CRUD. I have edited my view for 3 columns in my model (table has 7 columns).
I tried attach method which was referenced in a different post which did not work. Any thoughts would be appreciated.
Model
public class AssetRequest
{
public int Id { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Request date")]
public DateTime AssetRequestDate { get; set; }
[Display(Name = "Facility")]
public int FacilityId { get; set; }
[Required]
[Display(Name = "Asset requested")]
public int AssetId { get; set; }
[Display(Name ="Serial no.")]
public string AssetSN { get; set; }
[Required]
[Display(Name = "Request type")]
public int RequestTypeId { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "Job date")]
public DateTime JobRequestDate { get; set; }
[Required]
[Display(Name = "Request status")]
public int RequestStatusId { get; set; }
[Display(Name = "Tracking no.")]
public string TrackingNo { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
[Display(Name = "Sending facility")]
public string SendingFacilityt { get; set; }
public virtual Asset Asset { get; set; }
public virtual Facility Facility { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual RequestType RequestType { get; set; }
public virtual RequestStatus RequestStatus { get; set; }
}
}
Controller
public async Task<ActionResult> Edit([Bind(Include = "RequestStatusId, TrackingNo, Comments")] AssetRequest assetRequest)
{
if (ModelState.IsValid)
{
//db.AssetRequestTable.Attach(assetRequest);
db.Entry(assetRequest).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("All");
}
}
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>AssetRequest</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.DistrictId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("DistrictId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.DistrictId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AssetId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("AssetId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.AssetId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RequestStatusId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("RequestStatusId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.RequestStatusId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TrackingNo, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TrackingNo, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TrackingNo, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Comments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Comments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Comments, "", 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>
}
<div>
#Html.ActionLink("Back to List", "All")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
You ned to include the Id property (which is the primary key) to the Include list so that EF can get the item and update it.
public async Task<ActionResult> Edit([Bind(Include = "Id,RequestStatusId, TrackingNo,
Comments")] AssetRequest assetRequest)
{
// your code
}
Looks like you are using your entity model as the parameter to update the entity values. A better approach to prevent over posting is to use a view model.
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...");
}
}
}
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.