Create record using Model List - asp.net-mvc

I am trying to create an invoice. I have two models, Invoice & InvoiceItems.
I am able to insert using hardcoded values, but I want to be able to use TextBoxes to create an invoice on the fly. How do I insert a record that takes the data for the invoice and the dynamic data from the invoice items and inserts into both tables, using the same view? I'd like to have an add more button eventually where I can stay on the same page and keep adding items to the same invoice. You can see what I've tried so far below.
Invoice Model:
public class Invoice
{
[Key]
public int InvoiceId { get; set; }
public int ClientId { get; set; }
[Display(Name = "Amount")]
public decimal Amount { get; set; }
[Display(Name = "Invoice Creation Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime CreationDate { get; set; }
[Display(Name = "Invoice Due Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime DueDate { get; set; }
[Display(Name = "Notes")]
public string InvoiceNotes { get; set; }
public List<InvoiceDetails> InvoiceDetails { get; set; }
public List<Clients> Clients { get; set; }
}
InvoiceItem Model:
public class InvoiceDetails
{
[Key]
public int InvoiceDetailsId { get; set; }
public int InvoiceId { get; set; }
[DisplayName("Item Name")]
public string Name { get; set; }
[DisplayName("Item Note")]
public string Note { get; set; }
[DisplayName("Qty")]
public decimal? Quantity { get; set; }
[DisplayName("Rate/Hour")]
public decimal? Price { get; set; }
[DisplayName("Item Total")]
public decimal? Total { get; set; }
}
Invoice Controller:
private NovaDb _db = new NovaDb();
public ActionResult InvoiceInformation()
{
var invoice = new Invoice();
invoice.InvoiceDetails = new List<InvoiceDetails>();
return View(invoice);
}
[HttpPost]
public ActionResult InvoiceInformation(Invoice model)
{
if (ModelState.IsValid)
{
var invoices = new Invoice()
{
Amount = model.Amount,
CreationDate = model.CreationDate,
DueDate = model.DueDate,
InvoiceNotes = model.InvoiceNotes,
InvoiceId = model.InvoiceId,
ClientId = model.ClientId
};
_db.Invoices.Add(invoices);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
Invoice View:
#model NovaFinancial.Models.Invoice
#{
ViewBag.Title = "InvoiceInformation";
}
<h2>InvoiceInformation</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Invoice</legend>
#Html.HiddenFor(model => model.InvoiceId)
<div class="editor-label">
#Html.LabelFor(model => model.ClientId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ClientId)
#Html.ValidationMessageFor(model => model.ClientId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Amount)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Amount)
#Html.ValidationMessageFor(model => model.Amount)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CreationDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CreationDate)
#Html.ValidationMessageFor(model => model.CreationDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DueDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DueDate)
#Html.ValidationMessageFor(model => model.DueDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceNotes)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceNotes)
#Html.ValidationMessageFor(model => model.InvoiceNotes)
</div>
<div>
<table>
<tr>
<th>Name</th>
<th>Notes</th>
<th>Qty</th>
<th>Price</th>
<th>Total</th>
</tr>
#for (int i = 0; i < Model.InvoiceDetails.Count; i++)
{
#Html.HiddenFor(m=>m.InvoiceDetails[i].Name)
#Html.HiddenFor(m=>m.InvoiceDetails[i].Note)
#Html.HiddenFor(m=>m.InvoiceDetails[i].Quantity)
#Html.HiddenFor(m=>m.InvoiceDetails[i].Price)
#Html.HiddenFor(m=>m.InvoiceDetails[i].Total)
<tr>
<td>#Html.DisplayFor(m=>m.InvoiceDetails[i].Name) | #Html.TextBoxFor(m=>m.InvoiceDetails[i].Name)</td>
<td>#Html.DisplayFor(m=>m.InvoiceDetails[i].Note) | #Html.TextBoxFor(m=>m.InvoiceDetails[i].Note)</td>
<td>#Html.DisplayFor(m=>m.InvoiceDetails[i].Quantity) | #Html.TextBoxFor(m=>m.InvoiceDetails[i].Quantity)</td>
<td>#Html.DisplayFor(m=>m.InvoiceDetails[i].Price) | #Html.TextBoxFor(m=>m.InvoiceDetails[i].Price)</td>
<td>#Html.DisplayFor(m=>m.InvoiceDetails[i].Total) | #Html.TextBoxFor(m=>m.InvoiceDetails[i].Total)</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}

I worked out "a" solution that might not be the best, but it works. You'd still have to add to the code to make it more robust, but the general framework is there.
In your view, keep all the text boxes for the invoice details (Name, Quantity, Price) the same value for the name attribute, but do keep the id's unique. I used a bit of jQuery and JavaScript to generate extra rows as needed per a button that the user would click. For example,
<input type="text" name="Name" id="Name"> <!--first line item for Item Name-->
<input type="number" name="Quantity" id="Quantity"> <!--first for Quanitity -->
<input type="text" name="Name" id="Name2"> <!--second line item for Item Name-->
<input type="number" name="Quantity" id="Quantity2"> <!-- second for Quanitity -->
The values for the InvoiceDetail lines will pass back to the server as comma-delimited strings (better make certain that your item names don't have commas!). On the server-side,
var Names = Request["Name"]; // this would yield something like "Labor,Parts"
In the controller, you'll need to parse the strings into arrays and create the instances of your InvoiceDetail from them. I wrote a private method to split the strings and return a list of InvoiceDetail objects to the action method. The onus is on you to validate this data: both client-side and server-side need validation.
I did a few experiments. You can see all the code here: http://mefixme.blogspot.com/2014/10/aspnet-mvc-how-to-add-model-with.html
I hope that this helps you.

Related

complex type model wont pass list propery

hi guys i am having trouble with my mvc app. its a simple quiz app and i am stuck at creating create view for question model.
I have Question and Option model with appropriate view models(in my case they are QustionDTO and OptionDTO) and i want to make cshtml create view for Question with list of Options.like this but when i submit form, my list of options is null.
this is my Question and Option model
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string QuestionText { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name ="Answer text")]
public string OptionText { get; set; }
[Required]
public bool IsCorrect { get; set; }
}
this is my DTO models
public class QuestionDTO
{
public int Id { get; set; }
public string QuestionText { get; set; }
public List<OptionDTO> Options { get; set; }
}
public class OptionDTO
{
public int Id { get; set; }
public string OptionText { get; set; }
public bool IsCorrect { get; set; }
}
and this is my view with editor template located in "~/views/shared/editortemplate/OptionDTO.cshtml"
#model Quiz.BusinessEntites.QuestionDTO
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>QuestionDTO</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.QuestionText, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionText, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionText, "", new { #class = "text-danger" })
</div>
</div>
<table class="table" style="width:50%">
#for (int i = 0; i < 3; i++)
{
#Html.EditorFor(model=>model.Options[i])
}
</table>
<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>
this is OptionDTO editor template
#using Quiz.BusinessEntites
#model Quiz.BusinessEntites.OptionDTO
<tr>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.OptionText)
</th>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.IsCorrect)
</th>
</tr>
<tr>
<td class="col-md-2">
#Html.EditorFor(m => m.OptionText)
</td>
<td class="col-md-2">
#Html.EditorFor(m => m.IsCorrect)
</td>
</tr>
from the image above u can see that options list is null. if u have any suggestion it will be appreciated.
In your http post action method, the Bind attribute with Include list is telling the Model binder to bind only "Id","QuestionText" and "IsCorrect" properties of QuestionDto object from the posted form data. So the model binder will not bind the Options property value.
Remove the Bind attribute from your Http post action method.
There is no need to use the Bind attribute if your view model is specific to your view, means you have only properties needed for your view (In your case it looks like so)
public ActionResult Create(QuestionDTO model)
{
// to do :return something
}
If you want to use a non view specific view model, but still want to use Bind attribute to specify only subset of properties, Include just those properties. In your case, your code will be like
public ActionResult Create([Bind(Include="Id,QuestionText",Options"] QuestionDTO model)
{
// to do :return something
}
Also you should editer template view should be in a directory called EditorTemplates , not EditorTemplate

MVC model not bound to collection in POST

Visual Studio 2012
MVC 5.2.0
jQuery 2.1.1
jQuery UI Combined 1.10.4
Json.Net 6.0.3
Kendo UI MVC 2014.1.528
I've read through many similar posts. I've tried to apply what I've seen to no avail. I throw myself at feet of your keyboards for your mercy and help.
I have a survey-like site, so for simplicity here, I made up a sample project to duplicate the issue. I can upload that too.
I have a model with a child object of Address - no problemo there - it binds.
The model also has a collection of questions. It never binds on the post, and this is the issue here.
Lets start with models:
The Survey itself:
public class Survey
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name { get; set; }
[DisplayName("Phone #")]
[StringLength(15)]
[DataType(DataType.PhoneNumber)]
public string ContactMethodPhone { get; set; }
[DisplayName("Email")]
[StringLength(120)]
[EmailAddress]
public string ContactMethodEmail { get; set; }
public virtual Address Address { get; set; }
public virtual List<Question> Questions { get; set; }
}
and the address:
public class Address
{
[ScaffoldColumn(false)]
public int AddressId { get; set; }
[DisplayName("Address line 1")]
[Required]
[StringLength(200)]
public string Street { get; set; }
[DisplayName("Address line 2")]
[StringLength(200)]
public string Street2 { get; set; }
[DisplayName(" ")]
[StringLength(50)]
public string ApartmentNum { get; set; }
[DisplayName("Type")]
[StringLength(50)]
public string Tenement { get; set; }
[DisplayName("City")]
[Required]
[StringLength(200)]
public string City { get; set; }
[DisplayName("Province/State")]
[StringLength(20)]
public string State { get; set; }
[Required]
[DisplayName("Postal/Zip Code")]
[StringLength(10)]
public string MailCode { get; set; }
[DisplayName("Country")]
[StringLength(30)]
public string Country { get; set; }
[NotMapped]
public List<SelectListItem> Cities { get; set; }
[NotMapped]
public List<SelectListItem> Provinces { get; set; }
[NotMapped]
public List<SelectListItem> Countries { get; set; }
[NotMapped]
public List<SelectListItem> MailCodes { get; set; }
[NotMapped]
public List<SelectListItem> Tenements { get; set; }
}
and the questions:
public class Question
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ScaffoldColumn(false)]
public int ImageID { get; set; } // Question Image
[DisplayName("Question: ")]
public string InformationIntakeGroupValue { get; set; } // Question: ie Did you graguate high school
public int ImageID_Topic { get; set; } // Topic Image
[DisplayName("Topic: ")]
public string InformationIntakeTopicValue { get; set; } // Topic: ie Education
[ScaffoldColumn(false)]
public string InformationIntakeTypeCode { get; set; } // Type of question (date, bool, text)
// below not filled by select;
// the key from the EntityAffilliateIntake record insert
public int PersonId { get; set; } // Person anwering question
// this is the user response area
[DisplayName("Answer: ")]
public string InformationIntakeValue { get; set; }
[DisplayName("Choice: ")]
public string InformationIntakeValueBool { get; set; }
[DisplayName("Date: ")]
public DateTime InformationIntakeValueDate { get; set; }
[ForeignKey("Survey")]
public int SurveyId { get; set; }
public virtual Survey Survey { get; set; }
}
(Note: fyi, I've tried the models without foreign keys as well - but perhaps it's not defined correctly)
The controller :
// GET: /Inquiry/Create
public ActionResult Create()
{
var geoIpData = Strings._download_serialized_json_data<GeoData>(StringConstants.UrlForGeoService);
SurveyModel = new Survey
{
Id=1,
Address = new AddressController().GetAddressModel(geoIpData.country, geoIpData.regionName, geoIpData.city, ""),
Questions = new QuestionController().GetQuestions(1).ToList()
};
return View(SurveyModel);
}
// POST: /Inquiry/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
public ActionResult Create([Bind(Include = "Id, Name, ContactMethodPhone, ContactMethodEmail, Address, Questions")] Survey survey)
{
if (ModelState.IsValid)
{
int i = 0;
}
if (survey.Address.Cities == null)
{
survey.Address.Cities = SurveyModel.Address.Cities;
survey.Address.Countries = SurveyModel.Address.Countries;
survey.Address.MailCodes = SurveyModel.Address.MailCodes;
survey.Address.Provinces = SurveyModel.Address.Provinces;
survey.Address.Tenements = SurveyModel.Address.Tenements;
}
if (survey.Questions == null)
{
survey.Questions = SurveyModel.Questions;
}
return View(survey);
}
The view:
#model BindCollection.Models.Survey
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// I use <div class="container">, <fieldset> and <div class="row">
// instead of <div class="form-horizontal"> and <div class="form-group">
<div class="container">
<fieldset>
<legend></legend>
<h4>Survey</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
<div class="row">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2 col-md-2 col-lg-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Your address information:<br />
<br />
</div>
</div>
#* no problem here with address *#
#{ var vddAddress = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Address" } };}
#Html.Partial("_AddressPartial", Model.Address, #vddAddress)
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
How we can contact you? :<br />
<br />
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodPhone, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodPhone)
#Html.ValidationMessageFor(model => model.ContactMethodPhone)
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodEmail, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodEmail)
#Html.ValidationMessageFor(model => model.ContactMethodEmail)
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Some Questions<br />
<br />
</div>
</div>
#*Here is the evil one! Beware!*#
#{ var vddQuestions = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Questions" } };}
#Html.Partial("_QuestionsPartial", Model.Questions, #vddQuestions)
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</fieldset>
</div>
}
The address partial view is insignificant as there's no problem there.
Here's the partial view for the questions:
#model IEnumerable<BindCollection.Models.Question>
#using BindCollection
#using Kendo.Mvc.UI
#{
string CurrentTopic = string.Empty;
bool FirstTime = true;
}
#foreach (var item in Model)
{
if (CurrentTopic != item.InformationIntakeTopicValue)
{
CurrentTopic = item.InformationIntakeTopicValue;
if (!FirstTime)
{
FirstTime = false;
item.InformationIntakeTopicValue = string.Empty;
}
}
else
{
item.InformationIntakeTopicValue = string.Empty;
}
#Html.EditorFor(m=>item, "Question")
<br />
<br />
}
and, of course, I made an EditorTemplate for a question, as you can see a few lines above...
#model BindCollection.Models.Question
#Html.HiddenFor(m=>m.Id)
#{if (!string.IsNullOrWhiteSpace(Model.InformationIntakeTopicValue))
{
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<br />
<br />
<h4>
#Html.DisplayFor(m => m.InformationIntakeTopicValue, new { #class = "control-label col-sm-10 col-md-10 col-lg-10" })
</h4>
<br />
</div>
</div>
}}
#*type of value to ask for is denoted by item.InformationIntakeTypeCode*#
<div class="row">
<div class="control-label col-sm-9 col-md-9 col-lg-9">
#Html.DisplayFor(m => m.InformationIntakeGroupValue, null)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
#{
var outputBool = false;
var outputDate = false;
var outputText = false;
if(Model.InformationIntakeTypeCode.ToLower().Contains("date") )
{
outputDate = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("bool"))
{
outputBool = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("string"))
{
outputText = true;
}
}
#if(outputBool)
{
#(Html.Kendo().DropDownListFor(m => m.InformationIntakeValueBool)
.HtmlAttributes(new { style ="width: 100px;", id="InformationIntakeValueBool"+Model.Id.ToString() })
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>() {
new SelectListItem() {
Text = "Please Select...",
Value = "0"
},
new SelectListItem() {
Text = "Yes",
Value = "true"
},
new SelectListItem() {
Text = "No",
Value = "false"
}
}).Value("0")
)
}
#if(outputDate)
{
#(Html.Kendo().DatePickerFor(m => m.InformationIntakeValueDate)
.HtmlAttributes(new { style = "width: 100px;", id="InformationIntakeValueDate"+Model.Id.ToString() })
)
}
#if (outputText)
{
#Html.Kendo().AutoCompleteFor(m => m.InformationIntakeValue).HtmlAttributes(new { style = "width: 80%;", id="InformationIntakeValue"+Model.Id.ToString()})
}
</div>
</div>
So... When I POST, an odd thing occurs. All form values are passed, but the ones for Questions look strange:
Id 1
Name sally
Address.Street 123 Avenue Steet
Address.Street2 Building C
Address.Tenement Suite
Address.ApartmentNum 111
Address.City_input Sarnia
Address.City Sarnia
Address.State_input Ontario
Address.State Ontario
Address.Country_input Canada
Address.Country Canada
Address.MailCode_input N6B 2K0
Address.MailCode N6B 2K0
ContactMethodPhone 555-555-5555
ContactMethodEmail r#r.com
Questions.item.Id 1
Questions.item.InformationIntakeValueBool true
Questions.item.Id 2
Questions.item.InformationIntakeValueDate 2/4/2014
Questions.item.Id 3
Questions.item.InformationIntakeValue Speckled
Questions.item.Id 4
Questions.item.InformationIntakeValueBool true
Questions.item.Id 5
Questions.item.InformationIntakeValue Lightly Toasted
Questions.item.Id 7
Questions.item.InformationIntakeValueBool true
Questions.item.Id 8
Questions.item.InformationIntakeValue Nothing!
Questions.item.Id 6
Questions.item.InformationIntakeValueBool true
Questions.item.Id 9
Questions.item.InformationIntakeValueDate 6/29/2014
I thought, as I've seen in other posts, that the Question items should look like:
Questions[0].Id 1
Questions[0].InformationIntakeValueBool true
Questions[1].Id 2
Questions[1].InformationIntakeValueDate 2/4/2014
Questions[2].Id 3
Questions[2].InformationIntakeValue Speckled
So I'm not sure why mine looks like this.
On the server side, the Request only shows one variable for each:
Request.Form.Results View Expanding the Results View will enumerate the IEnumerable
[0] "__RequestVerificationToken" object {string}
[1] "Id" object {string}
[2] "Name" object {string}
[3] "Address.Street" object {string}
[4] "Address.Street2" object {string}
[5] "Address.Tenement" object {string}
[6] "Address.ApartmentNum" object {string}
[7] "Address.City_input" object {string}
[8] "Address.City" object {string}
[9] "Address.State_input" object {string}
[10] "Address.State" object {string}
[11] "Address.Country_input" object {string}
[12] "Address.Country" object {string}
[13] "Address.MailCode_input" object {string}
[14] "Address.MailCode" object {string}
[15] "ContactMethodPhone" object {string}
[16] "ContactMethodEmail" object {string}
[17] "Questions.item.Id" object {string}
[18] "Questions.item.InformationIntakeValueBool" object {string}
[19] "Questions.item.InformationIntakeValueDate" object {string}
[20] "Questions.item.InformationIntakeValue" object {string}
See the last 4 items? What about the other records?
I'm guessing that there's something strange with the ViewDataDictionary that I'm sending to the Question Partial View.
Any help would be appreciated. This should be simple...
Neither Partial Views or foreach statements contain the necessary information to properly bind collections. I always use EditorTemplates for everything. Especially since EditorTemplates will automatically iterate over a collection.
However, if you're bound and determined to use a loop, then use a for loop, and then index the model. In your case:
#for(int i = 0; i < Model.Count; i++)
{
if (CurrentTopic != Model[i].InformationIntakeTopicValue)
...
#Html.EditorFor(m => Model[i]) // don't have to specify the template name
// since it's the same name as the type
}
However, i'd just do this:
In your view, do this:
#*Here is the evil one! Beware!*#
#Html.EditorFor(m => m.Questions)
Then have your Question.cshtml as usual, which will automatically get iterated over by the EditorFor
However, at the top of the Question.cshtml (after the model declaration) add the following code, it's all that's necessary to achieve what you're trying to do. You don't need the partial view at all.
#{
if (ViewBag.CurrentTopic != Model.InformationIntakeTopicValue)
{
ViewBag.CurrentTopic = Model.InformationIntakeTopicValue;
}
else
{
Model.InformationIntakeTopicValue = string.Empty;
}
}

Validations not show up using EF Code First with complex types

This is a continuation of this question Model class and Mapping
I had my Client class now working fine and it's defined as
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using DataAnnotationsExtensions;
using System.ComponentModel.DataAnnotations.Schema;
using System.Collections.Generic;
namespace CardNumbers.Objects
{
[ComplexType]
public class PhoneInfo
{
[DataType(DataType.PhoneNumber)]
[StringLength(10)]
[DisplayName("Phone")]
public virtual string Phone { get; set; }
[StringLength(5)]
[DisplayName("Ext")]
public virtual string Ext { get; set; }
public bool HasValue
{
get
{
return (Phone != null || Ext != null);
}
}
}
[ComplexType]
public class ContactDetail
{
//Constructor
public ContactDetail()
{
phoneInfo = new PhoneInfo();
}
[StringLength(100)]
[DisplayName("Contact Name")]
[DisplayFormat(NullDisplayText = "")]
public virtual string Contact { get; set; }
[Email]
[StringLength(100)]
[DisplayName("Email")]
public virtual string Email { get; set; }
public virtual PhoneInfo phoneInfo { get; set; }
public bool HasValue
{
get
{
return (Contact != null || Email != null || phoneInfo.HasValue);
}
}
}
/// <summary>
/// Client class (Client No, Client Name, Address, Contact1, Contact2 info, Created By, Modified By (operator and date)
/// </summary>
public class Client
{
public Client()
{
Contact1 = new ContactDetail();
Contact2 = new ContactDetail();
}
[Key]
[Column("ClientId",TypeName = "int")]
public virtual int Id { get; set; }
[Required]
[DisplayName("Client No")]
[Column("client_no", TypeName = "smallint")]
public virtual Int16 Number { get; set; }
[Required]
[Column("client_name", TypeName = "varchar")]
[DisplayName("Client Name")]
[MaxLength(30, ErrorMessage = "Client Name should not be longer than 30 characters" )]
[MinLength(3, ErrorMessage = "Client Name is too short")]
public virtual string Name { get; set; }
[DataType(DataType.MultilineText)]
public virtual string Address { get; set; }
public virtual ContactDetail Contact1 {get; set;}
public virtual ContactDetail Contact2 {get; set;}
[ForeignKey("EnteredByOperator")]
public string EnteredBy { get; set; }
[InverseProperty("ClientsEnteredBy")]
public virtual Operator EnteredByOperator { get; set; }
[ForeignKey("ModifiedByOperator")]
public string ModifiedBy { get; set; }
[InverseProperty("ClientsUpdatedBy")]
public virtual Operator ModifiedByOperator { get; set; }
[DataType(DataType.DateTime)]
[DisplayName("Created on")]
public DateTime EnteredOn { get; set; }
[DataType(DataType.DateTime)]
[DisplayName("Modified on")]
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<ClientOrder> ClientOrders { get; set; }
public virtual ICollection<Reorder> Reorders { get; set; }
}
}
I mapped column names using Fluent API and I also re-defined my original "repository" classes to be very similar to defined in this tutorial http://code.msdn.microsoft.com/ASPNET-MVC-Application-b01a9fe8
This is my current partial view for the client form called _ClientForm:
#using WebDemo.Helper
#model CardNumbers.Objects.Client
<fieldset>
<legend>Client Info</legend>
#Html.ValidationSummary(true)
<input type="hidden" id="fntype" name="fntype">
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Number, EditorTemplate.TextBox)
#Html.EditorFor(model => model.Name, EditorTemplate.TextBox)
#Html.EditorFor(model => model.Address, EditorTemplate.EditBox)
<div id="ContactsInfo">
#*Contact 1*#
<div id="Contact1">
#*#Html.EditorFor(model=>model.Contact1)*#
#Html.EditorFor(model=>model.Contact1.Contact, EditorTemplate.TextBox)
#Html.EditorFor(model=>model.Contact1.Email, EditorTemplate.TextBox)
</div>
#*Contact2*#
<div id="Contact2">
#* #Html.EditorFor(model => model.Contact2)*#
</div>
</div>
#*<div class="clear"></div>*#
<div id="SaveCancel" class="float-right">
<button type="Submit" id="btnSave">Save</button>
<button type="reset" id="btnCancel">Cancel</button>
</div>
</fieldset>
I already tried to revert to original way of only one level and I also commented the second Contact2 info but still the e-mail validation doesn't work and all other validations also don't seem to work.
The EditorFor textboxes are defined based on this blog post http://fusionovation.com/post/2010/02/15/adding-a-rich-text-editor-to-asp-net-mvc-using-strongly-typed-helpers-dataannotations-amp-jquery.aspx
And these are two of the new EditorFor I added:
PhoneInfo.cshtml
#using WebDemo.Helper
#model CardNumbers.Objects.PhoneInfo
<div id="PhoneInfo">
<div class="float-left">
#Html.EditorFor(model => model.Phone, EditorTemplate.TextBox)
</div>
<div class="float-right">
#Html.EditorFor(model => model.Ext, EditorTemplate.TextBox)
</div>
</div>
And ContactDetail.cshtml
#using WebDemo.Helper
#model CardNumbers.Objects.ContactDetail
#Html.EditorFor(model => model.Contact, EditorTemplate.TextBox)
#Html.EditorFor(model => model.Email, EditorTemplate.TextBox)
#Html.EditorFor(model=>model.phoneInfo)
So, as you can see, the code of the views is now very compact.
However, with all of these in place the validations don't see to fire anymore. I used to test validation on EMail by typing some garbage. It used to provide a validation message near the textbox. Now I observe that the email textbox takes the red border, but there is no message.
Do you see what I am missing now and if it's possible to use complex type and validations?
To clarify, the _ClientForm is called from this Client view:
#model CardNumbers.Objects.Client
#{
ViewBag.Title = "Client";
}
#section scripts {
<script src="#Url.Content("~/Scripts/Clients.js")" type="text/javascript" ></script>
}
<form id="frmClientsSearch">
<label for="clientNo">Client No: </label>
<input type="number" name="searchClientNo" class="numericOnly" /><br />
<label for="clientName">Client Name: </label>
<input type = "text" size =25 value ="Please enter the search value" class="SelectOnEntry"
name ="searchClientName" />
<input type="button" id="btnClientsSearch" value ="Find / Refresh" />
</form>
<div style="padding-left: 150px; padding-top: 50px; padding-bottom: 50px;" id="ClientsResults">
<table id="flexClients" style="display: none">
</table>
</div>
<div style="display: none">
<form id="sform" title="Client Info">
#{Html.RenderPartial("_ClientForm", Model) ;}
</form>
</div>
Thanks.
I don't see a form anywhere on your page. A form context is required for validation to work. You need to wrap the Editor attributes in BeginForm block.
After some trials and error I found that the TextBox EditorFor view was the culprit. I documented what I found in my answer here http://forums.asp.net/t/1855963.aspx/1?Validation+messages+don+t+show+up+what+is+missing+
Basically, as long as I use this EditorFor
#*#using WebDemo.Helper*#
#model CardNumbers.Objects.PhoneInfo
<div id="PhoneInfo">
<div class="float-left">
#* #Html.EditorFor(model => model.Phone, EditorTemplate.TextBox)*#
<div class="editor-label">
#Html.LabelFor(model => model.Phone)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Phone)
#Html.ValidationMessageFor(model => model.Phone)
</div>
</div>
<div class="float-right">
#*#Html.EditorFor(model => model.Ext, EditorTemplate.TextBox)*#
<div class="editor-label">
#Html.LabelFor(model => model.Ext)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Ext)
#Html.ValidationMessageFor(model => model.Ext)
</div>
</div>
</div>
All seems to work OK. But if I try to switch to a shorter syntax and use this EditorFor for the textbox:
<div class="editor-label">
#Html.Label((ViewData.ModelMetadata.DisplayName??ViewData.ModelMetadata.PropertyName),
new Dictionary<string, object>
{
{ "for", ViewData.ModelMetadata.PropertyName }
})
</div>
<div class="editor-field">
#Html.TextBox("", (object)Model,
new Dictionary<string, object>
{
{ "id", ViewData.ModelMetadata.PropertyName },
{ "name", ViewData.ModelMetadata.PropertyName },
{ "class", "text-box single-line"},
{ "data-bind", "value: " + ViewData.ModelMetadata.PropertyName },
})
#Html.ValidationMessage(ViewData.ModelMetadata.PropertyName,
new Dictionary<string, object>
{
{ "data-valmsg-for", ViewData.ModelMetadata.PropertyName }
})
</div>
Validation messages do not show anymore.
Hopefully this answer will help someone or you may see what I am missing here.

Adding a Number of Items to a Model list?

I have a page with the same input box added a number of times.
#Html.TextBoxFor(m => m.Product)
#Html.TextBoxFor(m => m.Product)
#Html.TextBoxFor(m => m.Product)
How to I bind this to the Model.
I've tried:
public class Shop
{
public string ShopName { get; set; }
[Remote("ProductExists", "Account", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public List<String> Product { get; set; }
}
But I can only ever see the data in the first field. Also I tried:
#Html.TextBoxFor(m => m.Product[0])
#Html.TextBoxFor(m => m.Product[1])
#Html.TextBoxFor(m => m.Product[2])
But remote validation doesn't work so I'm a little stumped here. Essential what I would like to achieve is to send the list of products with the shop so that it can be validated via a remote call to a function. I tried putting the products within there own public class but then I wasn't able to access the shop name from within that class.
This is the Controller Action I'm trying to use:
public JsonResult ProductExists(List<String> Product, string ShopName)
Any Ideas how I could solve this would be so much appreciated?
EDIT
This Semi works but remote validation still isn't passing ShopName:
public class Shops
{
[Required]
public string ShopName { get; set; }
public List<Products> Product { get; set; }
}
public class Products
{
[Required]
[Remote("ProductExists", "Home", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public String Product { get; set; }
}
Controller Action:
public JsonResult ProductExists(List<String> Product, string ShopName)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
View:
#model Shop.Models.Shops
#{
ViewBag.Title = "Shop";
}
<h2>Shop</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>Shop</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ShopName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ShopName)
#Html.ValidationMessageFor(model => model.ShopName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[0])
#Html.ValidationMessageFor(model => model.Product[0])
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[1])
#Html.ValidationMessageFor(model => model.Product[1])
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Product[2])
#Html.ValidationMessageFor(model => model.Product[2])
</div>
<input type="submit" value="Create" />
</fieldset>
}
look at the following answer. I would make product a class on its own like you tried. Loot at the rendered html code for the page and check out the field name for the ShopName textbox. I think it should be ShopName, if so you dont need to change the AdditionalFields attribute if not change it to the name rendered.
So something like this:
public class Shop
{
public string ShopName { get; set; }
public List<Products> Product { get; set; }
}
public class Products
{
[Remote("ProductExists", "Account", AdditionalFields = "ShopName", ErrorMessage = "Product is already taken.")]
public String Product { get; set; }
}
in your view do something like this:
foreach(var item in Model.products)
{
#Html.TextBoxFor(item => item.product) // not sure if the syntax is right
}

Unobtrusive javascript adding data to #Html.HiddenFor

I have a hidden field on a form that is created in Razor using the #Html.HiddenFor helper:
#Html.HiddenFor(model => model.BidID, new { id="bidItemID" })
My View model looks like this:
public class BidEditVM
{
[Display(Name = "Bid ID")]
public int BidID { get; set; }
[StringLength(51)]
[Display(Name = "Customer Name")]
public string CustomerName { get; set; }
[StringLength(75)]
[Display(Name = "Bid Name")]
public string BidName { get; set; }
[Display(Name = "Amount")]
public decimal Amount { get; set; }
[Display(Name = "Time")]
public DateTime BidTime { get; set; }
}
When the HTML is rendered, unobtrusive javascript adds it's stuff to the hidden input field even though it will never require validation:
<input id="bidItemID" type="hidden" value="5198" name="BidID" data-val-required="The Bid ID field is required." data-val-number="The field Bid ID must be a number." data-val="true">
What's odder is that the message and validation it adds aren't even part of the view model for this partial view. The view looks like this:
#model AuctionAdmin.Models.ViewModels.BidEditVM
#using (Ajax.BeginForm("UpdateBid", new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "modalBidInfo" }))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.BidID, new { id="bidItemID" })
<fieldset>
<legend>Edit Bid</legend>
<div class="display-label">#Html.LabelFor(model => model.CustomerName)</div>
<div class="display-field">
#Html.DisplayFor(model => model.CustomerName)
</div>
<div class="display-label">#Html.LabelFor(model => model.BidName)</div>
<div class="display-field">
#Html.DisplayFor(model => model.BidName)
</div>
<div class="editor-label">#Html.LabelFor(model => model.Amount)</div>
<div class="editor-field">
#Html.EditorFor(model => model.Amount)
</div>
<div class="editor-label">#Html.LabelFor(model => model.BidTime)</div>
<div class="editor-field">
#Html.EditorFor(model => model.BidTime)
</div>
</fieldset>
}
Where is it getting this metadata from and how can I stop it?
It's marked as such since the type in the view model is an int.
It's adding the html due to this line:
#Html.HiddenFor(model => model.BidID, new { id="bidItemID" })
Why is it a problem that the extra attributes are present?
If it really is problematic, try changing the type of BidId to int? (a nullable int).

Resources