I am having difficulty getting MVC to bind to a model I have created. I have done this quite a few times in the past successfully. As such, I am just not sure why it is not working in this project.
For example, I have the following View:
#model StoryWall.ViewModels.ViewPostViewModel
#{
ViewBag.Title = "View Post";
}
<article class="story">
<header>
<h1>#Model.story.Title</h1>
<spann class="text-muted">#Model.story.Store.StoreName</span>
<h2>Posted by #Model.story.PosterName</h2>
</header>
if(#Model.story.StoryImage != null) {
<div class="storyImageWrapper">
<img src="~/img/#Model.story.StoryImage" />
</div>
<p>#Model.story.StoryBody</p>
}
</article>
<div class="commentsSection">
<h2>Comments</h2>
<h3>Add a Comment</h3>
<form method="post" class="form-horizontal" name="CommentForm" action="/View/AddComment">
#Html.AntiForgeryToken()
<input type="hidden" name="newComment.StoryID" value="#Model.story.StoryID" />
<div class="form-group"><label class="control-label col-sm-2">Name </label><div class="col-sm-10">#Html.TextBoxFor(#m => m.newComment.CommenterName, new { #class = "form-control", #required = true, #ng_model = "CommenterName"}) <span class="text-warning" ng-show="CommentForm.newComment.CommenterName.$dirty && CommentForm.newComment.CommenterName.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommenterName) </span></div> </div>
<div class="form-group"><label class="control-label col-sm-2">Email </label><div class="col-sm-10">#Html.TextBoxFor(#m => m.newComment.CommenterEmail, new { #class = "form-control", #required = true, #ng_model = "CommenterEmail" }) <span class="text-warning" ng-show="CommentForm.newComment.CommenterEmail.$dirty && CommentForm.newComment.CommenterEmail.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommenterEmail) </span></div> </div>
<div class="form-group"><label class="control-label col-sm-2">Message </label><div class="col-sm-10">#Html.TextAreaFor(#m => m.newComment.CommentBody, new { #class = "form-control", #required = true, #ng_model = "CommentBody" }) <span class="text-warning" ng-show="CommentForm.newComment.CommentBody.$dirty && CommentForm.newComment.CommentBody.$invalid"> Required </span> <span class="text-warning"> #Html.ValidationMessageFor(#m => m.newComment.CommentBody) </span></div> </div>
<button type="submit" ng-disabled="CommentForm.$invalid">Submit</button>
</form>
<h3>Current Comments</h3>
#foreach(var comment in #Model.story.Comments) {
<blockquote>#comment.CommentBody</blockquote>
<span>Poster: #comment.CommenterName on #comment.DatePosted.ToString("MM-dd-yyyy")</span>
}
</div>
Even though I am specifically using Html.TextBoxFor() for my input boxes, the binding is still not working as expected.
This is my Controller. "comment" in the second Action method is not binding correctly; its properties are null.
public class ViewController : Controller
{
StoryModel dbContext = new StoryModel();
public ActionResult ViewPost(Int32 postID)
{
ViewPostViewModel vm = new ViewPostViewModel();
vm.story = dbContext.Stories.FirstOrDefault(s => s.StoryID == postID);
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddComment(Comment comment)
{
if (ModelState.IsValid)
{
dbContext.Comments.Add(comment);
dbContext.SaveChanges();
return RedirectToAction("ViewPost", new { storyID = comment.StoryID});
}
else
{
ViewPostViewModel vm = new ViewPostViewModel();
vm.story = dbContext.Stories.FirstOrDefault(s => s.StoryID == comment.StoryID);
vm.newComment = comment;
return View("ViewPost", vm);
}
}
}
I know this is not the first time a similar question has been asked, but I could not find a solution that solved my problem. Additionally, as stated, this is something I have done in the past with success.
The only "new" element in this scenaria for me is Angular.js. This is my first time using the framework. Could it be interfering with the binding somehow?
If it helps, the Comment model:
public partial class Comment
{
public int CommentID { get; set; }
public int? StoryID { get; set; }
public int UserID { get; set; }
[Required]
[StringLength(75)]
public string CommenterName { get; set; }
[Required]
[StringLength(75)]
public string CommenterEmail { get; set; }
[Required]
public DateTime DatePosted { get; set; }
[Required]
public string CommentBody { get; set; }
public virtual Story Story { get; set; }
public virtual User User { get; set; }
}
and the ViewPostViewModel
public class ViewPostViewModel
{
public Story story { get; set; }
public Comment newComment { get; set; }
}
}
Thanks much for any help.
One answer would be to use #Html.EditorFor()
#Html.EditorFor(m => m.newComment)
Then on the folder where the view is placed you create a new folder called EditorTemplates with a view that is named exactly as the object type. In this case, Comment.cshtml
The view could be something like this ->
#model StoryWall.ViewModels.Comment
#Html.TextBoxFor(m => m.CommenterName)
#Html.TextBoxFor(m => m.CommenterEmail)
#Html.TextAreaFor(m => m.CommentBody)
This approach is the one I normally use to work with lists (useful in surveys or tests) but it also works with a single item.
Another approach could be to just add everything to the viewmodel since the viewmodel doesn't need to be a one to one mapping of the business objects or the database models. :)
Edit: Forgot to add. I think using this approach the method that receives the post will have to receive the whole ViewModel instead of just the comment. ->
public ActionResult AddComment(ViewPostViewModel vm)
Related
I have a form in which user can select which shipping methods they want to support for they product that they are selling, e.g. first class letter, second class letter, parcel, etc. I only give users a collection of possible shipping methods, they declare how much each one will cost, so if someone wants to sell a toaster in a parcel, they will charge less than for a set of dumbbells.
My ProductViewModel:
public int Id { get; set; }
public ICollection<SelectedShippingMethodViewModel> SelectedShippingMethods { get; set; }
And SelectedShippingMethodViewModel:
public class SelectedShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In my form I create a section with possible options like this:
<h3>Add new product</h3>
<hr />
#using (Html.BeginForm("AddNew", "ProductCreator", null, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
<label class="col-sm-2 control-label">Shipping methods</label>
<div class="col-sm-10">
#foreach (ShippingMethod shippingMethod in ViewBag?.ShippingMethods)
{
<div class="row">
<div class="col-md-3">
// I don't know what should be here
#Html.CheckBox("SelectedShippingMethods", false)
#shippingMethod.Name
</div>
<div class="col-md-2">
// I don't know what should be here
#Html.TextBox("SelectedShippingMethods.Price")
</div>
</div>
}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Add product</button>
</div>
</div>
}
I have a database table with every possible shipping method that I acquire like this:
[HttpGet]
public async Task<ActionResult> AddNew()
{
ViewBag.ShippingMethods = await _shippingService.GetAllShippingMethodsAsync();
return View();
}
The problem is if checkbox is selected I have to bind Price and Name for each individual SelectedShippingMethodViewModel and I have no idea how to make it work.
Your view models are incorrect. To allow users to select the shipping methods they want and add a price, that view model needs to be
public class ShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsSelected { get; set; } // your checkbox binds to this property
}
and the ProductViewModel should be
public class ProductViewModel
{
public int Id { get; set; }
....
public List<ShippingMethodViewModel> ShippingMethods { get; set; }
}
Then in the GET method, initialize your ProductViewModel and populate the ShippingMethods based on all available ShippingMethods, for example
var shippingMethods = await _shippingService.GetAllShippingMethodsAsync()
ProductViewModel model = new ProductViewModel
{
....
ShippingMethods = shippingMethods.Select(x => new ShippingMethodViewModel
{
Name = x.Name
}).ToList()
};
return View(model);
and in the view, use a for loop or EditorTemplate for typeof ShippingMethodViewModel to correctly generate your form controls
#for (int i = 0; i < Model.ShippingMethods.Count; i++)
{
#Html.LabelFor(m => m.ShippingMethods[i].IsSelected, Model[0].ShippingMethods.Name)
#Html.CheckBoxFor(m => m.ShippingMethods[i].IsSelected)
#Html.LabelFor(m => m.ShippingMethods[i].Price)
#Html.TextBoxFor(m => m.ShippingMethods[i].Price)
#Html.HiddenFor(m => m.ShippingMethods[i].Name) // if you want this to be submitted as well
}
Then in the POST method
public ActionResult AddNew(ProductViewModel model)
{
// Get the selected Shipping Methods and the associated price
var selectedMethods = model.ShippingMethods.Where(x => x.Selected);
I would like to create a view which is linked to multiple tables. From what I understand I need to create a View Model and link that to the page.
I get a couple of errors using the below
'PaymentViewModel' is a type, which is not valid in the given context.
An expression tree may not contain a dynamic operation (related to first error?)
I am new to MVC - come from ASP....Any help is appreciated
public class PaymentViewModel
{
public string playername { get; set; }
public DateTime dob { get; set; }
public string phone { get; set; }
public string email { get; set; }
public string clubname { get; set; }
public string productname { get; set; }
public decimal amount { get; set; }
public int transactionID { get; set; }
public bool approved { get; set; }
public string subtype { get; set; }
public DateTime subdate { get; set; }
}
Controller
I need to start with a blank view as this is the first step to register a player so the information is not in the database.
Below is the code I use to get a populated View.
public ActionResult Payment()
{
DateTime blank = Convert.ToDateTime("01-01-1900");
var prod = from p in db.Product
join c in db.Club on p.clubname equals c.clubname
where p.clubname == "Club1"
select new PaymentViewModel
{
productname = p.prodname,
clubname = c.clubname,
playername = c.add1,
dob = blank,
phone = c.phone,
email = c.email,
transactionID = 0,
amount = p.amount,
approved = Convert.ToBoolean("1"),
subtype = c.city,
subdate = blank
};
return View(prod);
}
View
#S4C.BAL.PaymentViewModel;
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Player Name</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<b class="control-label col-md-2" style="">Full Name</b>
<div class="col-md-10">
#Html.EditorFor(model => model.playername, new { htmlAttributes = new { autofocus = "autofocus", #maxlength = "25", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.playername, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<br /><br />
<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>
}
<div>#Html.ActionLink("Back to List", "Index")</div>
#section Scripts {#Scripts.Render("~/bundles/jqueryval")}
You cannot do approved = Convert.ToBoolean("1") in your select because the whole projection will happen at the database side and it does not know what Convert.ToBoolean() is. You need to do this in your view model:
public class PaymentViewModel {
// other properties ...
public string approved { get; set; }
public bool IsApproved {get {return this.approved == "1" }}
}
Also change the first line in your view to this:
#model S4C.BAL.PaymentViewModel
Not sure if I am understanding this correctly so please tell me if I'm wrong here.
Sounds like you know how to get a view filled with data from your database and you want to get an empty view without the data filled. To get an empty view request with just return a view without the model.
// Must request with /{Controller}/PaymentEmpty
Public ActionResult PaymentEmpty()
{
return View("Payment", new PaymentViewModel());
}
If you look at the default templates for ASP MVC applications the controller contains actions for Index, Details, Create, Edit and Delete. Thinking of actions in this manner can help with structuring your requests. Maybe place Payment into its own controller named PaymentsController and having the actions from the controller follow the default template.
Model.cs
A campaign can have multiple images, that's why IEnumerable<int> ImageIdList.
public class Campaign
{
public int Id { get; set; }
public int CreatedBy { get; set; }
public int UpdatedBy { get; set; }
public IEnumerable<int> ImageIdList { get; set; }
}
View.cshtml
I want to download all the images related to a campaign, based on the ImageIdList, that's why I need to post all these ImageIds when a particular Campaign is checked and download button is clicked.
#model Campaign
#{
Layout = "....";
var assets = Model.AssetsInCampaign.ToList();
}
#using (Html.BeginForm("action-method", "controller", FormMethod.Post))
{
<div class="btnSubmit">
<input type="submit" value="Download Asset(s)" />
</div>
#foreach(var i in assets)
{
<div class="s_checkcol">
<input type="checkbox" name="ids" />
#foreach (var imageId in i.Where(c => c.AssetId == doc.FileDataId).SelectMany(c => c.ImageIdList))
{
<input type="hidden" name="ids" value=#(imageId)>
}
</div>
}
}
Controller.cs
public ActionResult DownloadFiles(IEnumerable<int> ids)
{
// code
}
NOTE: Only a part of code(where I'm facing the problem) is provided here. Its a DB first approach and in no way I can alter that (ORDERS).
I tried the above, but all of the ids are posted to the controller no matter how many checkboxes are selected.
Question: How should I bind the IEnumerable<int> ImageIdList property to a checkbox in View.cs and post the data to Controller.cs so that only the ids of selected checkboxes are posted?
This is a nice practice... it will work and Iam working with such a
manner (Iam sure that it will work very well) but one thing you have to be very carefull while coding this, little bit
complicated
Iam taking this effort not only for as an answer to this particular question.
Its for all stackoverflow users. Because i never found the below method anyware in stackoverflow.
I get this method by a long search. You people can use this.
It will help you to avoid for loops to bind the Checkboxlist
Its the best good for re-usability (need a single line (max: 20-25 chars to bind a CheckBoxList in Razor))
CheckBoxListItem.cs
create a New Class CheckBoxListItem //you can use any other names
public class CheckBoxListItem
{
public int ID { get; set; }
public string Display { get; set; }
public bool IsChecked { get; set; }
}
MyModel.cs
This is modelclass
public class MyModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<CheckBoxListItem> ChkList { get; set; }
}
HomeController.cs
This is controller
public ActionResult Index()
{
var model = new MyModel(){
Id = 0,
Name = "Your Name",
ChkList = dbContext.myTable.Select(x => new CheckBoxListItem { ID = x.MyTableFieldID, Display = x.MyTableFieldName, IsChecked = true })
//If you need only int part, then just avoid to bind data on Display field
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel myModel) //Your model object on PostAction
{
IEnumerable<CheckBoxListItem> ChkList = myModel.ChkList;
// Here is your answer, You can see all your check box items (both checked and unchecked) in ChkList, it will shows all your checked items are true and non-checked items are false in IsChecked field
}
Here you have to give more patiance
Goto the Folder View>Shared>EditorTemplates and RightClick Add>View... and Create a new View with the same name CheckBoxListItem.cshtml
CheckBoxListItem.cshtml
#model Project.Models.CheckBoxListItem
<div class="">
#Html.HiddenFor(x => x.ID)
<div class="">
#Html.CheckBoxFor(x => x.IsChecked)
</div>
#Html.LabelFor(x => x.IsChecked, Model.Display, new { #class = "" })
</div>
Create your View
Index.cshtml
#model #model Project.Models.MyModel
<div class="form-group">
#Html.LabelFor(model => model.Id, htmlAttributes: new { #class = "" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id, new { htmlAttributes = new { #class = "" } })
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "" })
</div>
</div>
#Html.EditorFor(model => model.ChkList) //This only one line of code is enough to bind a checkBoxList in future
<input type="submit" value="Create" class="" />
You will get all these in your post action
I have the following ViewModel:
public class ActivityReportViewModel
{
public Dictionary<int, List<string>> Periods { get; set; }
public List<Project> Projects { get; set; }
public List<Templates> Templates { get; set; }
public DateTime TimePeriod { get; set; }
}
public class Project
{
public string Customer { get; set; }
public string ProjectNumber { get; set; }
public string ProjectDescription { get; set; }
public bool IsSelected { get; set; }
public int TemplateId { get; set; }
public bool XLSX { get; set; }
public bool PDF { get; set; }
}
I fill this ViewModel in my controller and then send it to my Create view, which works fine and the values of the Projects property are all there. However, when I postback the data to the server, the values are gone. I tried supplying HiddenFields to all properties of each Project to no avail. Here's my relevant view markup:
<div>
#Html.LabelFor(model => model.Projects, htmlAttributes: new { #class = "ms-Label" })
<ul class="ms-List" style="list-style:none;">
#for (int x = 0; x < Model.Projects.Count; x++)
{
<li class="ms-ListItem">
<span class="ms-ListItem-primaryText">#Model.Projects[x].ProjectDescription</span>
<span class="ms-ListItem-secondaryText">#Model.Projects[x].Customer</span>
<span class="ms-ListItem-tertiaryText">#Model.Projects[x].ProjectNumber</span>
#*<div class="ms-ListItem-selectionTarget js-toggleSelection"></div>*#
#Html.HiddenFor(m => Model.Projects[x].IsSelected)
#Html.HiddenFor(m => Model.Projects[x].ProjectDescription)
#Html.HiddenFor(m => Model.Projects[x].Customer)
#Html.HiddenFor(m => Model.Projects[x].ProjectNumber)
#Html.HiddenFor(m => Model.Projects[x].XLSX)
#Html.HiddenFor(m => Model.Projects[x].PDF)
<div class="ms-Dropdown">
<i class="ms-Dropdown-caretDown ms-Icon ms-Icon--caretDown"></i>
#Html.DropDownListFor(m => m.Projects[x].TemplateId, new SelectList(Model.Templates, "Id", "Name"), new { #class = "ms-Dropdown-select" })
</div>
<div class="ms-ChoiceField">
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].XLSX" type="checkbox">
<label for="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">Excel</span></label>
</div>
<div class="ms-ChoiceField">
<input id="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].PDF" type="checkbox">
<label for="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">PDF</span></label>
</div>
</li>
}
</ul>
<div>
#Html.ValidationMessageFor(model => model.Projects, "", new { #class = "text-danger" })
</div>
</div>
EDIT:
Here's my POST action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ActivityReportViewModel report)
{
using (AppContainer _db = new AppContainer())
{
if (ModelState.IsValid)
{
_db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView(report);
}
}
The DefaultViewModel Binder uses the HTML attribute name to determine which property to bind back to on the server. I cannot see name attribute specified on your input element. Please specify name attribute on the elements you wish to post back to the server with the property of the view model.
Specifiy name attribute as below. Notice I have added name attribute with value as the property of your view model
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" name="#Model.Projects[x].ProjectNumber" value="#Model.Projects[x].XLSX" type="checkbox">
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;
});