FoolProof RequiredIf in Dot Net core - .net-core

I am migrating my MVC project to Dot net core and have been trying to fix old conditional formatting on a field that was working fine in the MVC project.
I am putting the validation on the Assumption field to be mandatory if the CategoryId is greater than 2.
Below is the code I am trying.
Model
[Required(ErrorMessage = "*Required")]
public string CategoryId { get; set; }
[RequiredIf("CategoryId", Operator.GreaterThan, "2", ErrorMessage = "*Required")]
public string Assumptions { get; set; }
.cshtml View
<div class="zs-col-md-5">
Select Category
</div>
<div class="zs-col-md-5">
<div>
#Html.DropDownListFor(x => x.CategoryId, new SelectList(Model.Categories, "Key", "Value",
Model.CategoryId), "--select--", new { #onchange = "ChangeCategoryddn(this)", #id = "category-id" })
</div>
#Html.ValidationMessageFor(model => model.CategoryId)
</div>
<div class="zs-col-md-5">
Assumptions
</div>
<div class="zs-col-md-5">
#Html.TextAreaFor(model => model.Assumptions})
#Html.ValidationMessageFor(model => model.Assumptions)
</div>
I have added the Nuget package FoolProof.Core to my project and have also referred the below javascript files:
<script src="~/lib/jquery-3.4.1.js"></script>
<script src="~/lib/jquery.validate.min.js"></script>
<script src="~/lib/jquery.validate.unobtrusive.min.js"></script>
<script src="~/lib/mvcfoolproof.unobtrusive.min.js"></script>
The validation for Category field is working fine but for Assumption (conditional field) its not working.
Is there anything that I am missing or am doing wrong.

In Startup.cs
Add in ConfigurationServices the following line
services.AddFoolProof();

Related

ASP.NET MVC 5 Form Validation and Error Handling

Trying to implement data validation and error handling on a simple contact form. When I add the check for ModelState.IsValid I'm in a chicken and egg situation. I have looked at other similar questions and am just not getting this. Moving from Web Forms to MVC and struggling. Trying to toggle HTML elements based on what's happening - success/error message, etc. RIght now, not even the validation is working.
Right now I'm just trying to get server-side validation working but would welcome advice on how to add client-side validation also; for example, is it necessary to use jQuery for this or is there something baked in?
View:
#using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
if (ViewData["Error"] == null && ViewData["Success"] == null)
{
<h3>Send us an email</h3>
Html.ValidationSummary(true);
<div class="form-group">
<label class="sr-only" for="contact-email">Email</label>
<input type="text" name="email" placeholder="Email..."
class="contact-email" id="contact-email">
</div>
<div class="form-group">
<label class="sr-only" for="contact-subject">Subject</label>
<input type="text" name="subject" placeholder="Subject..."
class="contact-subject" id="contact-subject">
</div>
<div class="form-group">
<label class="sr-only" for="contact-message">Message</label>
<textarea name="message" placeholder="Message..."
class="contact-message" id="contact-message"></textarea>
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
}
else if (ViewData["Error"] == null && ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to call
us at (xxx) xxx-xxxx at any time.
</p>
}
else if (ViewData["Error"] != null)
{
<h3>Oops!</h3>
<p>
We apologize. We seem to be having some problems.
</p>
<p>
Please come back and try again later. Alternatively,
call us anytime at (xxx) xxx-xxxx.
</p>
}
}
Model:
public class ContactModel
{
[Required(ErrorMessage = "Email address is required")]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }
[Required(ErrorMessage = "Subject is required")]
public string Subject { get; set; }
[Required(ErrorMessage = "Message is required")]
public string Message { get; set; }
}
Controller:
[HttpPost]
public ActionResult Contact(ContactModel contactModel)
{
if (ModelState.IsValid)
{
try
{
MailMessage message = new MailMessage();
using (var smtp = new SmtpClient("mail.mydomain.com"))
{
// Standard mail code here
ViewData["Success"] = "Success";
}
}
catch (Exception)
{
ViewData["Error"]
= "Something went wrong - please try again later.";
return View("Error");
}
}
return View();
}
Error View:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Error</title>
</head>
<body>
<hgroup>
<h1>Error.</h1>
<h2>An error occurred while processing your request.</h2>
</hgroup>
</body>
</html>
UPDATE - 05/09/2017
Per Guruprasad's answer, if ModelState.IsValid evaluates to false, then no validation error messages are being reported on the form.
Note I had to change the AddModelError signature to not use the "Extension ex" parameter:ModelState.AddModelError("Error", "Server side error occurred"); as I do not want system errors being reported to users.
Note also that at this point I am only trying out validation on the server side (have yet to work through client-side validation).
I have updated the Contact.cshtml view as follows as no model errors were being displayed - I have included the Bootstrap .has-error and .help-block CSS rules for the validation errors:
#using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
<h3>Send us an email</h3>
Html.ValidationSummary(true);
<div class="form-group has-error">
<label class="sr-only" for="contact-email">Email</label>
#Html.TextBoxFor(m => m.Email, new { type = "text", name = "email",
placeholder = "Email..", #class = "contact-email" })
#Html.ValidationMessageFor(model => model.Email, String.Empty,
new { #class="help-block" })
</div>
<div class="form-group has-error">
<label class="sr-only" for="contact-subject">Subject</label>
#Html.TextBoxFor(m => m.Subject, new { type = "text",
name = "subject",
placeholder = "Subject..", #class = "contact-subject" })
#Html.ValidationMessageFor(model => model.Subject, String.Empty,
new { #class = "help-block" })
</div>
<div class="form-group has-error">
<label class="sr-only" for="contact-message">Message</label>
#Html.TextAreaFor(m => m.Message, new { name = "message",
placeholder = "Message..", #class = "contact-message" })
#Html.ValidationMessageFor(model => model.Message, String.Empty,
new { #class = "help-block" })
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
if (ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to
call us at (xxx) xxx-xxxx at any time.
</p>
}
}
There are multiple things you need to understand here. Let me go point by point.
Its good to know that you have your model designed, but how your view gets to know that it has a model to bind for itself and when posting the form contents, how would server comes to know that, there is a model to be received. So on the first instance, you need to construct your view binding the model. To bind a model in a view, you need to first get a reference/declare it at the top, letting view know that, ok, here is a model for you to generate my view.
Well, you have ValidationSummary to true, then I would suggest that, instead of using ViewData to pass error message, you can use ModelState.AddModelError and let ValidationSummary take care of that. As a side note, you might also want to take care of this issue and you can resolve the same with answers mentioned in the same post. If you are not using or do not want to use Html.ValidationSummary, then you can stick to your current view.
Now, to display Success message, you can either use TempData or ViewData and follow the same structure as you have in your view now. Here is one more post to let you work on that.
Last and most important on View part is binding model properties to View elements. Use Razor View extension helpers to generate View for your model. You have #Html.TextBoxFor,#Html.TextAreaFor etc., You also have #Html.TextBox, #Html.TextArea which is not for binding model properties, but just to generate a plain HTML view. You can add other html properties within these helpers as shown in the updated view below. I would suggest to dig down more on the overloads available for these helpers.
So here is your updated view.
#model SOTestApplication.Models.ContactModel #*getting model reference*#
#using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
<h3>Send us an email</h3>
Html.ValidationSummary(true);
<div class="form-group">
<label class="sr-only" for="contact-email">Email</label>
#Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", #class = "contact-email" })
#*Usage of helpers and html attributes*#
</div>
<div class="form-group">
<label class="sr-only" for="contact-subject">Subject</label>
#Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", #class = "contact-subject" })
</div>
<div class="form-group">
<label class="sr-only" for="contact-message">Message</label>
#Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", #class = "contact-message" })
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
}
if (ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to call
us at (xxx) xxx-xxxx at any time.
</p>
}
Controller Side validation
Not much to say on this part as it looks good. But based on few of my points above, I would suggest you to add ModelState.AddModelError instead of using ViewData for error messages. Eliminate your if conditions in view, so that contact form remains, even after postback. Now if you want to persist the values after server side validation, then just pass back the model to your view in your post method. Updated Controller would be:
[HttpPost]
public ActionResult Contact(ContactModel contactModel)
{
if (ModelState.IsValid)
{
try
{
MailMessage message = new MailMessage();
using (var smtp = new SmtpClient("mail.mydomain.com"))
{
// Standard mail code here
ViewData["Success"] = "Success";
}
}
catch (Exception)
{
ModelState.AddModelError("Server side error occurred", ex.Message.ToString());
}
}
return View(contactModel); //this will persist user entered data on validation failure
}
Client Side Validation
As far as this portion is considered, you have few more things to set up in your application.
You need to add Html.EnableClientValidation(true); and Html.EnableUnobtrusiveJavaScript(true); to your application. There are various possible ways to add this. You can add this on Web.config file under appSettings for global implication Or you can add this in particular view as mentioned in below updated View example.
Global Implication in Web.Config ex:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
If you have noticed your BundleConfig.cs file under App_Start directory, you would have seen below entries created by default. These are the jquery stuffs responsible for your Client Side validation.
jQuery and jQueryVal entries
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
Next Step is to add reference to these files/use #section Scripts to render these bundles either in _Layout.cshtml or in any specific view. When you include this in _Layout.cshtml. these scripts/bundles are rendered wherever you use this layout with other views. So basically, its your call on where to render these.
For example here, I would render these in Contact.cshtml view soon after adding reference to model.
#section Scripts
{
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
}
One Last thing to make this work here is that you need to use #Html.ValidationMessageFor razor extension and let MVC do the binding of error messages on particular properties. Also for these error messages to be displayed in the View, you need to specify ErrorMessage for each property in your model as you are doing it now with Required(ErrorMessage=... for each properties in model. There are more to know about these stuffs if you explore it in detail.
Your updated view with proper validations added.
#model SOTestApplication.Models.ContactModel
#section Scripts
{
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
}
#using (Html.BeginForm("Contact", "Contacts", FormMethod.Post))
{
<h3>Send us an email</h3>
Html.ValidationSummary(true);
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
<div class="form-group">
<label class="sr-only" for="contact-email">Email</label>
#Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", #class = "contact-email" })
#Html.ValidationMessageFor(m => m.Email)
</div>
<div class="form-group">
<label class="sr-only" for="contact-subject">Subject</label>
#Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", #class = "contact-subject" })
#Html.ValidationMessageFor(m => m.Subject)
</div>
<div class="form-group">
<label class="sr-only" for="contact-message">Message</label>
#Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", #class = "contact-message" })
#Html.ValidationMessageFor(m => m.Message)
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
if (ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to call
us at (xxx) xxx-xxxx at any time.
</p>
}
}
Hope I have clarified most of your doubts with these points. Happy Coding.. :)

Strongly Typed Model with MvcMailer

i am facing troubles passing strongly typed model to MvcMailer view. I am using asp.net mvc3 and installed MvcMailer3 using nuget.
There is no error messages and message is sent successfully but the data fields are not populated. I am doing everything also tried using ViewBag fields but the same problem- that is message sent successfully but fields are not populated...
please help...i am stuck for two days!
here is my controller and view code....
//Controller code
public ActionResult Index()
{
string invoicenumber = "BC-00000002";
IEnumerable<Quantum.Models.usp_MasterPrintInvoice_Result> mpi = db.usp_MasterPrintInvoice(invoicenumber);
IEnumerable<PrintDetailObject> printdetailobj = from anmpi in mpi select new PrintDetailObject { Head = anmpi.Head, ContributorName = anmpi.Name, RegistrationNumber = anmpi.RegistrationNumber, InvoiceNumber = anmpi.InvoiceNumber, InvoiceDate = anmpi.Date, Amount = anmpi.Amount, PaymentMonth = anmpi.Month, ReceivedBy = anmpi.Operator };
ViewData.Model = printdetailobj.FirstOrDefault();
IUserMailer mailer = new UserMailer();
mailer.Welcome().Send();
return View();
}
View Code
#model Quantum.Models.PrintDetailObject
#using Mvc.Mailer
<h2>Invoice</h2>
<fieldset>
<div class="print-details-page">
<legend>#Html.DisplayFor(model => model.Head)</legend>
<div class="display-label">InvoiceNumber</div>
<div class="display-field">
#Html.DisplayFor(model => model.InvoiceNumber)
</div>
<div class="clear"></div>
<div class="display-label">Date</div>
<div class="display-field">
#Html.DisplayFor(model => model.InvoiceDate)
</div>
<div class="clear"></div>
<div class="display-label">Recieved From:</div>
<div class="display-field">
#Html.DisplayFor(model => model.ContributorName)
</div>
<div class="clear"></div>
<div class="display-label">Registration Number:</div>
<div class="display-field">
#Html.DisplayFor(model => model.RegistrationNumber)
</div>
<div class="clear"></div>
<div class="display-label">Amount:</div>
<div class="display-field">
#Html.DisplayFor(model => model.Amount)
</div>
<div class="clear"></div>
<div class="display-label">Amount in Text:</div>
<div class="display-field">
#Html.DisplayFor(model => model.AmountText)
</div>
<div class="clear"></div>
<div class="display-label">Month:</div>
<div class="display-field">
#Html.DisplayFor(model => model.PaymentMonth)
</div>
<div class="clear"></div>
<div class="display-label">Recieved By:</div>
<div class="display-field">
#Html.DisplayFor(model => model.ReceivedBy)
</div>
<div class="clear"></div>
<br />
<p>Received with many thanks.</p>
</div>
</fieldset>
I am only get the following text as the email body:
Invoice
InvoiceNumber
Date
Recieved From:
Registration Number:
Amount:
Amount in Text:
Month:
Recieved By:
Received with many thanks.
hi i have just got the insight to answer my own question.
actually when i used ViewData.Model inside of the "SendEmail" Controller the model is available to its own view namely the "Index" view instead of the razor view used with the "UserMailer" Controller which is named "Welcome" in my case.
To pass the Model to "UserMailer" action we need to do the following modification:
//IUserMailer code
...
public interface IUserMailer
{
MvcMailMessage Welcome(PrintDetailObject myModel);
}
//UserMailer code
public virtual MvcMailMessage Welcome(PrintDetailObject myModel)
{
//ViewBag.Data = someObject;
ViewData.Model = myModel;
return Populate(x =>
{
x.Subject = "Welcome";
x.ViewName = "Welcome";
x.To.Add("cmmwahid#hotmail.com");
});
}
//and finally controller code
public ActionResult Index()
{
string invoicenumber = "BC-00000002";
IEnumerable<Quantum.Models.usp_MasterPrintInvoice_Result> mpi = db.usp_MasterPrintInvoice(invoicenumber);
IEnumerable<PrintDetailObject> printdetailobj = from anmpi in mpi select new PrintDetailObject { Head = anmpi.Head, ContributorName = anmpi.Name, RegistrationNumber = anmpi.RegistrationNumber, InvoiceNumber = anmpi.InvoiceNumber, InvoiceDate = anmpi.Date, Amount = anmpi.Amount, PaymentMonth = anmpi.Month, ReceivedBy = anmpi.Operator };
IUserMailer mailer = new UserMailer();
mailer.Welcome(printdetailobj.FirstOrDefault()).Send();
return View();
}
here is the final output as a email body:
Invoice
Generous Contribution
InvoiceNumber
BC-00000002
Date
15/02/2014
Recieved From:
Some Donor
Registration Number:
M104/6
Amount:
$23.00
Amount in Text:
Twenty Three Dollars Only
Month:
May/2014
Recieved By:
someuser
Received with many thanks.

MVC 3 Required annotation ErrorMessage text is not inserted into attribute data-val-required of my input tag

I have a required property, with an errormessage, in my model and the errormessage text is not being shown when the user does not enter any text for this property.
My cshtml page is
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Index", "Booking", FormMethod.Post, new { #class = "form-horizontal" }))
{
<fieldset>
<div class="control-group">
#Html.LabelFor(m => m.CustomerOrderNumber, "", "", new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(m => m.CustomerOrderNumber) #Html.ValidationMessageFor(m => m.CustomerOrderNumber)
<p class="help-block">This is a mandatory field.</p>
</div>
</div>
<input type="submit" value="Next" class="btn btn-primary"/>
</fieldset>
}
I've removed all extraneous content.
My model .cs source is
using System;
using Iesi.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
namespace Technolog.Rma.Business.Domain
{
[Serializable]
public class RepairBatch
{
[Required(ErrorMessage = "Please enter a order number.")]
[StringLength(30)]
[Display(Name ="Order number")]
public virtual string CustomerOrderNumber { get; set; }
public RepairBatch()
{
CustomerOrderNumber = "";
}
public virtual void copy(RepairBatch rb){
this.CustomerOrderNumber = rb.CustomerOrderNumber;
}
}
}
When I look at the returned html in the browser I see the following html for my customer order number input box
<div class="control-group">
<label class="control-label" for="CustomerOrderNumber">Order number</label>
<div class="controls">
<input data-val="true" data-val-required="&#39;CustomerOrderNumber&#39; must not be empty." id="CustomerOrderNumber" name="CustomerOrderNumber" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="CustomerOrderNumber" data-valmsg-replace="true"></span>
<p class="help-block">This is a mandatory field.</p>
</div>
The data-val-required attribute has the canned text returned by the MVC 3 framework and not the errormessage I specified on the customerordernumber property using the required attribute.
I'm using Visual Studo 2010 SP1 with MVC 3 April 2011 Tools update. I'm also using nhibernate 3.2 and spring.net 1.3.2
Has anyone experienced this problem or have any suggestions?
20/11/2012 update
I used the following workaround to show the correct validation message
#Html.TextBoxFor(m => m.CustomerOrderNumber, new { data_val_required="Please enter an order number"}) #Html.ValidationMessageFor(m => m.CustomerOrderNumber)
Jabbar Azam
I don't use either nhibernate or spring.net but I did notice that your CustomerOrderNumber property is virtual. Is your model class one derived from RepairBatch rather than RepairBatch itself? I don't believe data annotations are recognized when they're on a base class. (Folks, tell me if I'm wrong about this!)

ViewModel's list is null in action

I'm working on my first ASP.NET MVC 3 application and I've got a View that looks like this:
#model IceCream.ViewModels.Note.NotesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.TextBoxFor(m => m.Name)
foreach (var item in Model.Notes)
{
#Html.EditorFor(m => item);
}
<input type="submit" value="Submit"/>
}
And I have an EditorTemplate that looks like this:
#model IceCream.ViewModels.Note.NoteViewModel
<div>
#Html.HiddenFor(m => m.NoteID)
#Html.TextBoxFor(m => m.NoteText)
#Html.CheckBoxFor(m => m.IsChecked)
</div>
NotesViewModel looks like so:
public class NotesViewModel
{
public string Name { get; set; }
public IEnumerable<NoteViewModel> Notes { get; set; }
}
NoteViewModel looks like this:
public class NoteViewModel
{
public int NoteID { get; set; }
public System.DateTime Timestamp { get; set; }
public string NoteText { get; set; }
public bool IsChecked { get; set; }
}
The NotesViewModel is populated just fine when it is passed to the view. However when the submit button is clicked, the controller action handling the post has only the value for the Name property of the viewmodel. The Notes property - the list of notes that have been checked/unchecked by the user - is null. I've got a disconnect between the populating of those TextBoxFor and CheckBoxFor elements when the view is displayed and the ViewModel being sent back. Guidance on this?
SOLUTION
Thanks go to Mystere Man for setting me straight on this. As I understand it, essentially by changing my loop to
#Html.EditorFor(m => m.Notes)
changes the underlying HTML, which I understand provides for the proper model binding on the post. Looking at the resulting HTML, I see that I get the following generated for one of the Notes:
<div>
<input id="Notes_0__NoteId" type="hidden" value="1" name="Notes[0].NoteId">
<input id="Notes_0__NoteText" type="text" value="Texture of dessert was good." name="Notes[0].NoteText">
<input id="Notes_0__IsChecked" type="checkbox" value="true" name="Notes[0].IsChecked>
</div>
Which is different than this HTML generated by my original code:
<div>
<input id="item_NoteId" type="hidden" value="1" name="item.NoteId>
<input id="item_NoteText" type="text" value="Texture of dessert was good." name="item.NoteText" >
<input id="item_IsChecked" type="checkbox" value="true" name="item.IsChecked">
</div>
By looping through the Notes, the generated HTML essentially loses any references to the viewmodel's Notes property and while the HTML gets populated correctly, the setting of the checkbox values has no way to communicate their values back to the viewmodel, which I guess is the point of the model binding.
So I learned something, which is good.
You're a smart guy, so look at your view. Then, consider how the HTML gets generated. Then, consider how on postback the Model Binder is supposed to know to re-populate Notes based on the generated HTML.
I think you'll find that your HTML doesn't have enough information in it for the Model Binder to figure it out.
Consider this:
#EditorFor(m => Model.Notes)
Rather than the for loop where you are basically hiding the context from the EditorFor function.
And for those that just want the answer as a for loop:
#for (int x = 0; x < Model.Notes.Count(); x++) {
#Html.HiddenFor(m => m.Notes[x].NoteId)
#Html.EditorFor(m => m.Notes[x].NoteText)
#Html.EditorFor(m => m.Notes[x].IsChecked)
}

Create Views for object properties in model in MVC 3 application?

I have an Asp.Net MVC 3 application with a database "Consultants", accessed by EF. Now, the Consultant table in the db has a one-to-many relationship to several other tables for CV type information (work experience, etc). So a user should be able to fill in their name etc once, but should be able to add a number of "work experiences", and so on.
But these foreign key tables are complex objects in the model, and when creating the Create View I only get the simple properties as editor fields. How do I go about designing the View or Views so that the complex objects can be filled in as well? I picture a View in my mind where the simple properties are simple fields, and then some sort of control where you can click "add work experience", and as many as needed would be added. But how would I do that and still utilize the model binding? In fact, I don't know how to go about it at all. (BTW, Program and Language stand for things like software experience in general, and natural language competence, not programming languages, in case you're wondering about the relationships there).
Any ideas greatly appreciated!
Here's the Create View created by the add View command by default:
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Consultant</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
And here's the EF database diagram:
Update:
According to suggestion, I tried Steven Sanderson's blog solution, but I can't get it to work properly. I added this in the main view:
<div id="editorRows">
#foreach (var item in Model.Programs)
{
Html.RenderPartial("ProgramEditorRow", item);
}
</div>
<input type="button" value="Add program" id="addItem" />
I also added the javascript:
<script type="text/javascript">
var url = '#Url.Action("BlankEditorRow", "Consultant")';
$(document).ready(function () {
$("#addItem").click(function () {
$.ajax({
url: url,
cache: false,
success: function (html) {
alert(html);
$("#editorRows").append(html); }
});
return false;
});
});
</script>
A partial view:
#model Consultants.Models.Program
#using Consultants.Helpers
<div class="editorRow">
#*#using (Html.BeginCollectionItem("programs"))
{*#
#Html.TextBoxFor(model => model.Name)
#*}*#
</div>
And action methods in the controller:
public ActionResult Create()
{
Consultant consultant = new Consultant();
return View(consultant);
}
public ActionResult BlankEditorRow()
{
return PartialView("ProgramEditorRow", new Program());
}
Note that I commented out the Html.BeginCollectionItem part in the partial view, because I can't get that to work. It only gives me the hidden field Steven Sanderson talks about, but not the actual textbox. So I tried commenting that part out and just had a textbox. Well, that gets me the textbox, but I can't get to that info in the post method. I use the Consultant object as return parameter, but the Programs property contains no Program. Perhaps this has to do with the fact that I cannot get the BeginCollectionItem helper to work, but in any case I don't understand how to do that, or how to get to the Program supposedly added in the view. With a simple object I would add the new object in the post method by something like _repository.AddToConsultants(consultant), and when I save to EF it gets its id. But how do I do the same thing with the program object and save it to the database via EF?
Phil Haack has written a great blog post which explains how to model bind to a list. It's specific to MVC2, but I'm not sure if version 3 has improved on this.
Model Binding To A List.
In a scenario where the user is allowed to add an arbitrary number of items, you'll probably want to create new input fields using JavaScript. Steven Sanderson shows here how to achieve that:
Editing a variable length list, ASP.NET MVC 2-style.
Those resources should get you all the way there.
You may take a look at the following blog post about writing custom object templates. Also don't use EF models in your views. Design view models that are classes specifically tailored to the needs of a given view and have your controller map between the EF models and the view models that should be passed to the view. AutoMapper is a good tool that could simplify this mapping.

Resources