Check from razor view if derived System.ComponentModel.DataAnnotations.ValidationAttribute exists - c#

I'm implementing Html.EditorForModel() so that it's Bootstrap friendly. I have editor templates for all the data types, and one, Object.cshtml, that wraps each editor template with <div class="form-control"></div> etc.
The issue is that when I have a property on a model that is marked as [ComplexType], I want to then list each property of the child class. This works fine, however, for [DataType(DataType.Upload)], which is an HttpPostedFileBase data type, prop.IsComplexType sees that as a complex data type and ends up listing its properties rather than rendering <input type="file" />
Here's Object.cshtml:
#model dynamic
#{
var modelMetaData = ViewData.ModelMetadata;
var properties = modelMetaData.Properties;
}
#foreach (var prop in properties.Where(p => p.ShowForEdit))
{
string propertyName = prop.PropertyName;
if (prop.TemplateHint == "HiddenInput")
{
#Html.Hidden(propertyName)
}
else
{
if (prop.IsComplexType)
{
<fieldset>
<legend>#propertyName</legend>
<div class="mt-ml-2em">
#foreach (var subProp in prop.Properties)
{
var propertyName1 = subProp.PropertyName;
string fullname = propertyName + "." + propertyName1;
<div class="form-group">
#Html.BootstrapLabel(propertyName1)
#Html.Editor(fullname, MyHelpers.TemplateHelpers.GetTemplateForProperty(subProp))
<p class="help-block">#subProp.Description</p>
#Html.ValidationMessage(fullname, new { #class = "color-red" })
</div>
}
</div>
</fieldset>
}
else
{
<div class="form-group">
#Html.BootstrapLabel(propertyName)
#Html.Editor(propertyName, MyHelpers.TemplateHelpers.GetTemplateForProperty(prop))
<p class="help-block">#prop.Description</p>
#Html.ValidationMessage(propertyName, new { #class = "color-red" })
</div>
}
}
}
My razor view:
#model MyMvc45Template.Areas.SampleTests.Models.Product
#{
ViewBag.Title = "ForModel";
}
#*<h2>BsFormGroupFor</h2>
#Html.BsFormGroupFor(m => m.ProductName)*#
<h2>For Model Test</h2>
#using (Html.BeginForm("ForModel", "FormTests", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.EditorForModel()
<div class="form-group">
<input type="submit" value="Save" class="btn btn-success" />
</div>
}
And my sample classes:
public class Product
{
public int Id { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[HasChildProperties] //I am thinking I can use this custom ValidationAttribute as a flag in Object.cshtml
public Address Address { get; set; }
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[DataType(DataType.Upload)]
public HttpPostedFileBase File { get; set; }
[DataType(DataType.Url)]
public string Url { get; set; }
}
Address class:
[ComplexType]
public class Address
{
[Display(Description = "This is the help block text")]
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
}
And what the output looks like:
As the image shows, my File property ends up having its members listed rather than rendering my Upload.cshtml:
#model dynamic
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { #class = "form-control", placeholder = ViewData.ModelMetadata.Watermark,
type = "file" })
I was thinking I could use this ValidationAttribute as a flag in Object.cshtml:
/// <summary>
/// This will make the editor template for Object.cshtml list any child properties in an edit form
/// </summary>
public class HasChildProperties : ValidationAttribute
{
}
But I can't find my custom ValidationAttribute in the metadata. How can I access this attribute? Is there a more elegant solution?

I ended up using UIHint:
[UIHint("HasChildProperties")]
public Address Address { get; set; }
if (prop.TemplateHint == "HasChildProperties") {...}

Related

html.hidden for value not set in asp.net MVC core Razor view

I am working on an asp.net MVc core application. I have a popup with a form element like this:
#using (Html.BeginForm("AddIVR", "ITPVoice", FormMethod.Post, new { role = "form" }))
{
#*#Html.HiddenFor(m =>m.m_newIVR.Account, new { #value= Model.accountID})*#
#Html.Hidden("m.m_newIVR.Account", Model.accountID)
}
I have a viewmodel like this:
public class AccountDetailsViewModel
{
public IVRS m_newIVR { get; set; }
}
and IVRS model class like this:
public class IVRS
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("account")]
public string Account { get; set; }
}
I am trying to populate it in my view like this:
#Html.HiddenFor(m =>m.m_newIVR.Account, new { #value= Model.accountID})
but when i see view source, Value is null
I tried using:
#Html.Hidden("m.m_newIVR.Account", Model.accountID)
and it shows m_newIVR.Account populated.
Then I am posting the form to controller this action
[HttpPost]
public ActionResult AddIVR(AccountDetailsViewModel model)
{
return RedirectToAction("AccountDetails", "mycontroller")
}
Although I see that AccountId is populated in view ( using viewsource), but in post action method value of model.m_newIVR.Account is null.
HTML output looks like this:
<div id="edit-ivrs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none;">
<div class="modal-dialog">
<form action="/ITPVoice/AddIVR" method="post" role="form"> <div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add IVR</h4>
<input id="m_newIVR_Account" name="m_newIVR.Account" type="hidden" value="" />
<input id="AccountId" name="AccountId" type="hidden" value="56f5e3d77ea022a042665be1" />
</div>
<div class="modal-body">
</div>
</div>
My Questions are:
Why html.hiddenfor is not setting value of the model variable?
Although html.hidden is setting value, why it is not accessible in post action method ?
Please suggest.
Now I am able to answer your question why does it works for Html.Hidden but not for Html.HiddenFor.
When you Html.HiddenFor with m =>m.m_newIVR.Account then it always try to set value for hidden field value whatever value available in property m.m_newIVR.Account not the value that you specify using #value = Model.AccountId.
If you want to use HiddenFor the set m_newIVR.Account in ViewModel just use following thing.
#Html.HiddenFor(m =>m.m_newIVR.Account)
Html.Hidden is not strongly type so it not depend on name. You can specify different name and value parameter. In this case It is your responsibility to generate proper name for HiddenField.
My Working Sample
Model
public class AccountDetailsViewModel
{
public string AccountId { get; set; }
public IVRS m_newIVR { get; set; }
}
public class IVRS
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("account")]
public string Account { get; set; }
}
Controller Action
[HttpGet]
public IActionResult Index1()
{
AccountDetailsViewModel model = new AccountDetailsViewModel();
//model.AccountId = "1222222";
model.m_newIVR = new IVRS();
model.m_newIVR.Account = "122222";
return View(model);
}
[HttpPost]
public IActionResult Index1(AccountDetailsViewModel model)
{
return View(model);
}
View (Index1.cshtml)
#model WebApplication2.Controllers.AccountDetailsViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(m =>m.m_newIVR.Account)
<input type="submit" />
}
// Sample Out

Strongly-typed view for model class with collection

I am trying to do something like personal blog and i encountered a problem. I have strongly typed form for edit of my article. I am able to update simple columns like title, content, ... But I have no idea how to handle collection of tags which is mapped as many-to-many collection. What is the best practice here? Can i use some HTML helper like those for simple columns? Or need I to create a new collection everytime? Honestly I have no idea.
Model class
public class Post : IEntity
{
public virtual int Id{ get; set; }
[Required(ErrorMessage = "Každý článek musí mít titulek")]
[MaxLength(250, ErrorMessage ="Nadpis může mít maximálně 250 znaků")]
public virtual string Title { get; set; }
public virtual string Annotation { get; set; }
[AllowHtml]
public virtual string Content { get; set; }
public virtual User Author { get; set; }
public virtual DateTime CreationDate { get; set; }
public virtual Rating Rating { get; set; }
public virtual string PreviewImageName { get; set; }
public virtual string ContentImageName { get; set; }
public virtual Category Category { get; set; }
public virtual IList<Tag> Tags { get; set; }
public virtual IList<BlogImage>Gallery { get; set; }
}
}
So far i was able to do all CRUDs with html helpers like these.
<div class="form-group">
<div class="col-sm-10">
<label>Anotace</label>
</div>
<div class="col-sm-10">
#Html.TextAreaFor(x => x.Annotation, new { #class = "form-control", #rows = 5 })
#Html.ValidationMessageFor(x => x.Annotation)
</div>
</div>
<div class="form-group">
<div class="col-sm-10">
<label>Obsah</label>
</div>
<div class="col-sm-10">
#Html.TextAreaFor(x => x.Content, new { #class = "form-control formatedText", #rows = 20 })
#Html.ValidationMessageFor(x => x.Content)
</div>
</div>
You basically need to use some client side javascript to make intuitive user interface to handle this collection properties which user adds. It could be one item or 100 items.
Here is a very simple way of doing it which allows user to enter each tag for the post in a textbox. user will be able to dynamically add a textbox to enter a tag name for the post.
Assuming you have a view model like this for your post creation.
public class PostViewModel
{
public int Id { set; get; }
public string Title { get; set; }
public List<string> Tags { set; get; }
}
Your view will be strongly typed to this view model
#model PostViewModel
<h2>Create Post</h2>
#using (Html.BeginForm("Create","Post"))
{
#Html.LabelFor(g=>g.Title)
#Html.TextBoxFor(f=>f.Title)
<button id="addPost">Add Tag</button>
<label>Enter tags</label>
<div id="tags">
<input type="text" class="tagItem" name="Tags" />
</div>
<input type="submit"/>
}
You can see that i have a div with one input element for the tag and a button to add tag. So now we have to listen to the click event on this button and create a copy of the textbox and add it to the dom so user can enter a second tag name. Add this javascript code to your page
#section scripts
{
<script>
$(function() {
$("#addPost").click(function(e) {
e.preventDefault();
$(".tagItem").eq(0).clone().val("").appendTo($("#tags"));
});
});
</script>
}
The code is self explanatory. When the button is clicked, it clone the textbox for tag name entry and add it to our container div.
Now when you submit the form, the Tags property will be filled with the tag names user entered in those textboxes. Now you just need to read the values posted and save that to the database.
[HttpPost]
public ActionResult Create(PostViewModel model)
{
var p = new Post { Title = model.Title };
//Assign other properties as needed (Ex : content etc)
p.Tags = new List<Tag>();
var tags = db.Tags;
foreach (var item in model.Tags)
{
var existingTag = tags.FirstOrDefault(f => f.Name == item);
if (existingTag == null)
{
existingTag = new Tag {Name = item};
}
p.Tags.Add(existingTag);
}
db.Posts.Add(p);
db.SaveChanges();
return RedirectToAction("Index","Post");
}

MVC model not binding on post

Can't figure out what I'm doing wrong. When the form in the view is posted the model properties turn out to be null.
Model
public class RegistrationModel
{
public RegistrationModel()
{
Registration = new REGISTRATION();
AddPayment = true;
}
public REGISTRATION Registration { get; set; }
public bool AddPayment { get; set; }
}
View
#model Client.Models.RegistrationModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(excludePropertyErrors: false)
<div class="form-group">
#Html.DropDownList("SECTION_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.DropDownList("STUDENT_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.DropDownList("STATUS_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.CheckBoxFor(model => model.AddPayment)
</div>
<p>
<input type="submit" class="btn btn-success" value="Create" />
</p>
}
Controller
public ActionResult Create()
{
//code to populate view dropdowns
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RegistrationModel model)
{
WriteFileLog(_logPath, Newtonsoft.Json.JsonConvert.SerializeObject(model));
}
In the controller's Create action that handles the post model properties are null.
Registration Class (autogenerated by EF from database):
public partial class REGISTRATION
{
public REGISTRATION()
{
this.REGISTRATION_AUDIT = new HashSet<REGISTRATION_AUDIT>();
}
public int ID { get; set; }
public int SECTION_ID { get; set; }
public int STUDENT_ID { get; set; }
public int STATUS_ID { get; set; }
public virtual ICollection<REGISTRATION_AUDIT> REGISTRATION_AUDIT { get; set; }
public virtual SECTION SECTION { get; set; }
public virtual V_REGISTRATION_STATUS V_REGISTRATION_STATUS { get; set; }
public virtual PERSON PERSON { get; set; }
}
I would recommend using the strongly-typed helpers, like so:
#Html.DropDownListFor(m => m.Registration.SECTION_ID, null, string.Empty, new { #class = "form-control" })
Otherwise, you need to adjust the names you're using to
#Html.DropDownList("Registration.SECTION_ID", null, string.Empty, new { #class = "form-control" })
You can probably simplify what you're doing by duplicating the Registration class's members into your view model, replacing the Registration property.
As #StephenMuecke points out, you're missing a few parts from your model/markup. The template for the DropDownList helper you're using is
DropDownListFor(
[model property to bind],
[collection of possible values to bind],
[option label],
[HTML attributes])
Passing null for that second parameter means you have no values to populate the generated <select> element with, and should normally generate an exception.
I'm not a fan of using ViewBag to pass collections into the view, so I'd recommend something like
public class RegistrationModel
{
public RegistrationModel()
{
Registration = new REGISTRATION();
AddPayment = true;
}
public REGISTRATION Registration { get; set; }
public bool AddPayment { get; set; }
public SelectList Sections { get; set; }
public SelectList Students { get; set; }
public SelectList Statuses { get; set; }
}
and then adjust the markup accordingly:
#Html.DropDownListFor(m => m.Registration.SECTION_ID, Model.Sections, string.Empty, new { #class = "form-control" })

BeginCollectionItem partial within partial not behaving correctly

I am trying to bind all my model data at once through a form submission in MVC 5 using an edited version of BeginCollectionItem as discussed in Joe Steven's blog here.
The model, Company, has a List<Pa_Ipv4>, the class Pa_Ipv4 in turn has a List<IpAllocation>, I want to access and save to the database all the properties of the IpAllocation in each Pa_Ipv4.
IE: Model.pa_ipv4s[x].requestedIps[x].subnet
The main page is using model Company, which has a partial accepting Pa_Ipv4, which has a partial accepting IpAllocation.
Question 1: In my controller, I'm setting a string property for the first item in the list (requestedIp), but when I submit and postback, the property (allocationType) is null, this property needs to be hard coded as it's for internal use within the DB - why is this being reset?
Reason: The property isn't in the post method, as such what is initially declared is discarded as it's not within the end post.
Possible Solution: Use a hidden property within the form so that it is present when the form is posted and the user cannot access the property.
Question 2: BeginCollectionItem is naming attributes appropriately, IE: pa_ipv4s[8e075d50-a5fb-436f-9cef-85abfb6910e3].requestedIps[b693b83c-b6b1-4c42-b983-4d058e766d4c].subnet, but only the initial model, it's then ignoring any others created, what have I done wrong?
Reason: The GUID needed for a prefix generated by the Pa_Ipv4 sections BeginCollectionItem is not able to be accessed by the IpAllocation BeginCollectionItem, as such only the initial content has the correct prefixes, anything added hereafter misses the necessary prefix.
Another potential solution is essentially the same concept, but instead of using a div, use html data attribute instead so that it's accessible.
I think both of the issues I'm experiencing are to do with how I've set my controller up, but I've included the Views and Model below as well. The model contains all the properties, lots of these have been removed in my views to save space as these are not causing the issue.
Create
#model Company
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<div class="jumboservice">
<div data-role="page">
<div data-role="header">
<h2>PA IPv4 Request Form</h2>
</div>
<div class="ui-content" data-role="main">
<h3>Company Details</h3>
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<p class="lblStyle">Company Name</p>
<span>
#Html.EditorFor(m => m.name)
#Html.ValidationMessageFor(m => m.name)
</span>
</div>
</div>
</div>
<br />
#foreach (var i in Model.pa_ipv4s)
{
#Html.Partial("Pa_IPv4View", i)
}
<br />
<div data-role="main" class="ui-content">
<div data-role="controlgroup" data-type="horizontal">
<input type="submit" class="ui-btn" value="Create" />
</div>
</div>
</div>
</div>
}
<script type="text/javascript">
$(function () {
$('#addItemRIpM').on('click', function () {
$.ajax({
url: '#Url.Action("RequestedManager")',
cache: false,
success: function (html) { $("#editorRowsRIpM").append(html); }
});
return false;
});
$('#editorRowsRIpM').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
});
</script>
Pa_Ipv4 Partial
#model Pa_Ipv4
#using (HtmlHelpers.BeginCollectionItem.HtmlPrefixScopeExtensions.BeginCollectionItem(Html,"pa_ipv4s"))
{
#Html.AntiForgeryToken()
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<p class="lblStyle">Subnet</p>
</div>
<div class="ui-block-b">
<p class="lblStyle">Size(CIDR)</p>
</div>
<div class="ui-block-c">
<p class="lblStyle">Mask</p>
</div>
<div class="ui-block-d">
</div>
</div>
#*Request IP Address Space List*#
<div id="editorRowsRIpM">
#foreach (var item in Model.requestedIps)
{
#Html.Partial("RequestedIpView", item)
}
</div>
#Html.ActionLink("Add", "RequestedManager", null, new { id = "addItemRIpM", #class = "button" })
}
RequestedIp Partial
#model IpAllocation
<div class="editorRow">
#using (HtmlHelpers.BeginCollectionItem.HtmlPrefixScopeExtensions.BeginCollectionItem(Html, "requestedIps"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
#Html.TextBoxFor(m => m.subnet)
</span>
</div>
<div class="ui-block-b">
<span>
#Html.TextBoxFor(m => m.cidr)
</span>
</div>
<div class="ui-block-c">
<span>
#Html.TextBoxFor(m => m.mask)
<span class="dltBtn">
Remove
</span>
</span>
</div>
</div>
}
</div>
Controller
public ActionResult Create()
{
var cmp = new Company();
cmp.contacts = new List<Contact>
{
new Contact { email = "", name = "", telephone = "" }
};
cmp.pa_ipv4s = new List<Pa_Ipv4>
{
new Pa_Ipv4
{
ipType = "Pa_IPv4", registedAddress = false, existingNotes = "",
numberOfAddresses = 0, returnedAddressSpace = false, additionalInformation = "",
requestedIps = new List<IpAllocation>
{
new IpAllocation { allocationType = "Requested", cidr = "", mask = "", subnet = "" } // allocationType is null in cmp in the Create[HttpPost]
}
}
};
return View(cmp);
}
public ActionResult Pa_IPv4Manager()
{
return PartialView("Pa_IPv4View", new Pa_Ipv4());
}
public ActionResult RequestedManager()
{
return PartialView("RequestedIpView", new IpAllocation { allocationType = "Requested" }); // allocationType is null in cmp in the Create[HttpPost]
}
// POST: Pa_Ipv4/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Company cmp) //only one requestedIps count regardless of how many add
{
if (ModelState.IsValid)
{
db.companys.Add(cmp);
db.SaveChanges();
return RedirectToAction("Index");
}
Model
[Table("Ipv_Base")]
public class Ipv_Base
{
[Key]
public int ipv_baseId { get; set; }
public int companyId { get; set; }
[ForeignKey("companyId")]
public Company company { get; set; }
public string ipType { get; set; }
[Required]
public bool registedAddress { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string existingNotes { get; set; }
[Required]
public int numberOfAddresses { get; set; }
[Required]
public bool returnedAddressSpace { get; set; }
[DataType(DataType.MultilineText)]
public string additionalInformation { get; set; }
// navigation properties
public virtual IList<IpAllocation> requestedIps { get; set; }
}
[Table("Company")]
public class Company
{
[Key]
public int companyId { get; set; }
[Required]
public string name { get; set; }
[Required]
public string telephone { get; set; }
[Required]
public string regNumber { get; set; }
// navigation properties to keep track of the models that belong to the company
public virtual IList<Pa_Ipv4> pa_ipv4s { get; set; }
}
[Table("IpAllocation")]
public class IpAllocation
{
[Key]
public int ipAllocationId { get; set; }
public int ipv_BaseId { get; set; }
[ForeignKey("ipv_BaseId")]
public Ipv_Base ipv_Base { get; set; }
[Required]
public string allocationType { get; set; }
[Required]
public string subnet { get; set; }
[Required]
public string cidr { get; set; }
[Required]
public string mask { get; set; }
}
public class Pa_Ipv4 : Ipv_Base
{
public Pa_Ipv4()
{
ipType = "pa_ipv4";
}
}
Question 1 Solution:
The issue with Q1 was that the property value I was assigning in the controller wasn't being parsed back from the form post, because the property wasn't there.
Added a hidden field for the property to rectify the pesky null.
<div class="ui-block-a">
<span>
#Html.HiddenFor(m => m.allocationType)
#Html.TextBoxFor(m => m.subnet, new { #class = "checkFiller" })
</span>
</div>
Question 2 Solution:
The issues that I was facing with the GUID of the first model being attached as the prefix to the second model was largely due to how I was sending data using AJAX to the controller action method.
The code snippets shown below fix the issues and display the correctly bound GUIDs.
name="pa_ipv4s[f7d8d024-5bb6-451d-87e3-fd3e3b8c1bba].requestedIps[d5c08a43-f65e-46d1-b224-148225599edc].subnet" is now being shown on the dynamically created model properties, not just the initially created.
When running in debug in visual studio and hovering over the model, digging down into the data shows the correct counts of the model lists.
Controller ActionMethod:
public ActionResult ExistingManager(string containerPrefix)
{
ViewData["ContainerPrefix"] = containerPrefix;
return PartialView("ExistingIpView", new IpAllocation { allocationType = "Existing" });
}
AJAX GET Method calling Controller ActionMethod:
$('#addItemEIpM').on('click', function () {
$.ajax({
url: '#Url.Action("ExistingManager")',
cache: false,
data: 'containerPrefix=' + $('#addItemEIpM').data('containerprefix'),
success: function (html) {
$("#editorRowsEIpM").append(html);
}
});
return false;
});

C# MVC - Razor Complex Nested Form Submission not binding

Long time lurker... first time question asker...
I have a complex form which returns null when being submitted. Essentially I am trying to build a database driven forms.
The form contains a list of either sections or questions
A section contains a list of either another section, or questions
Model 1:
public FormViewModel {
public List<FormSetsViewModel> formSets { get; set; }
}
Model 2:
public FormSetsViewModel{
QAViewModel questionAnswerViewModel { get; set; }
SectionViewModel sectionViewModel { get; set; }
bool isQuestion { get; set; }
bool isSection { get; set; }
}
Model 3:
public SectionViewModel {
public List<FormSectionQuestionsViewModel> formSectionQuestions { get; set; }
}
Model 4:
public FormSectionQuestionsViewModel {
public QuestionAnswerViewModel questionAnswers;
public SectionViewModel childSection;
int orderNumber;
}
Model 5:
public QAViewModel {
int id { get; set; }
string answer { get; set; }
string question { get; set;}
}
The views are as follows:
FormViewModel.cshtml
#model FormViewModel
#using (Html.BeginForm("Save", "Forms"))
{
<div class="row">
#Html.EditorFor(model => model.formSetsViewModels)
</div>
<div class="controls">
<input type="submit" value="Confirm" class="button" name="save" />
</div>
}
#model FormSetsViewModel
<div class="control-group">
#if (Model.isQuestion)
{
#Html.EditorFor(m => m.questionViewModel);
}
else
{
#Html.EditorFor(m => m.sectionViewModel);
}
</div>
SectionViewModel.cshtml
#model SectionViewModel
#Html.EditorFor(m => m.formSectionQuestions)
FormSectionQuestionsViewModel.cshtml
#model FormSectionQuestionsViewModel
#if (Model.childSection != null)
{
#Html.EditorFor(m => m.childSection)
}
else
{
#Html.EditorFor(m => m.questionAnswers)
}
QAViewModel.cshtml
#model QAViewModel
<p><div class="question-text-edit">#Html.Raw(Model.questionText)</div>
#Html.TextAreaFor(m => m.answer, new { style = "width: 90%; height: 80px;" })
The controller:
[HttpPost]
public ActionResult Save(int caseID, List<FormSetsViewModel> formSets = null)
{
return Index(caseID);
}
The view works great as a database driven form. However, when I submit the form, it seems that the formsets cannot bind, and returns null.
From Html, it created an input like this:
<input id="formSetsViewModels_d762713a-7a2f-497a-9417-4c6e91d33cb8__sectionViewModel_formSectionQuestions_48e738da-10d3-4518-be59-2493e2b7a7cc__questionAnswers_answer" name="formSetsViewModels[d762713a-7a2f-497a-9417-4c6e91d33cb8].sectionViewModel.formSectionQuestions[48e738da-10d3-4518-be59-2493e2b7a7cc].questionAnswers.answer" type="text" value="">
Finally found the answer!
The variable name for the FormSetsViewModel in the
public ActionResult Save(int caseID, List<FormSetsViewModel> formSets = null)
needs to be formSetsViewModel for the model to be able to be binded.
The other thing is that, some public variables in the class does not have { get; set; } method.
All variables that we want to be bind needs the { get; set; } method. Adding this solve the issue.

Categories