I have an object passed to an "Edit" controller. The object is a "Component" which can also have "Component Properties" added to it: Manufacturer, Size, etc. I want to be able to add those attributes on the same page as the object's Edit view.
I have 2 forms. The first one functions correctly to edit the model.
The second one posts to the "Create" method of the ComponentPropertyValueController, but the only value that passes to the object parameter is the value entered (such as the manufacturer). The Parent object attributes needed to be associated with the added "Component Properties" are 0's and null.
What I have done is tried to instantiate a new ComponentPropertyValue object and set the id to the parent id. I also tried setting the parent object to the current view model.
I cannot figure out how to make the new ComponentPropertyValue object passed to the Create controller belong to the parent Component when it posts.
Here is a picture of the form. The form looks fine. It has the proper drop downs and seems to function correctly.
Screenshot
Picture of Entity Model
The model is purposely Generic. Allowing to add an type of thing and add attributes to them.
"Component" Controller Code:
//
// GET: /Components/Edit/5
public ActionResult Edit(int id = 0)
{
Component component = db.Components.Find(id);
if (component == null)
{
return HttpNotFound();
}
ViewBag.ComponentPropertyId = new SelectList(db.ComponentProperties, "Id", "Property");
ViewBag.ComponentTypeId = new SelectList(db.ComponentTypes, "Id", "Type", component.ComponentTypeId);
return View(component);
}
Razor View:
#model removed.com.Models.Component
#using removed.com.Models
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Component</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.ComponentTypeId, "ComponentType")
</div>
<div class="editor-field">
#Html.DropDownList("ComponentTypeId", String.Empty)
#Html.ValidationMessageFor(model => model.ComponentTypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<h2>Add Property</h2>
#{
ComponentPropertyValue componentPropertyValue = new ComponentPropertyValue() { Component = Model, ComponentId = Model.Id} ;
}
#using (Html.BeginForm("Create", "ComponentPropertyValue", FormMethod.Post, componentPropertyValue))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>ComponentPropertyValue</legend>
<div class="editor-label">
#Html.LabelFor(x => componentPropertyValue.ComponentPropertyId, "ComponentProperty")
</div>
<div class="editor-field">
#Html.DropDownList("ComponentPropertyId", String.Empty)
#Html.ValidationMessageFor(x => componentPropertyValue.ComponentPropertyId)
</div>
<div class="editor-label">
#Html.LabelFor(x => componentPropertyValue.Value)
</div>
<div class="editor-field">
#Html.EditorFor(x => componentPropertyValue.Value)
#Html.ValidationMessageFor(x => componentPropertyValue.Value)
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I have also tried to add hidden inputs, and here is the html for the form:
<form Component="" ComponentId="" ComponentProperty="" ComponentPropertyId="0" Id="0" Value="" action="/ComponentPropertyValue/Create" method="post"> <fieldset>
<legend>ComponentPropertyValue</legend>
<input id="ComponentId" name="ComponentId" type="hidden" value="36" />
36 is the correct id for the record. but it is never passed to the controller.
If after instantiating, I set the ComponentId:
#{
ComponentPropertyValue componentPropertyValue = new ComponentPropertyValue() { ComponentId = Model.Id };
}
#using (Html.BeginForm("Create", "ComponentPropertyValue", FormMethod.Post, componentPropertyValue))
The form is generated with the value set. Yet it still never reaches the controller.
<form Component="" ComponentId="36" ComponentProperty="" ComponentPropertyId="0" Id="0" Value="" action="/ComponentPropertyValue/Create" method="post">
My view model for the two interacting classes:
public partial class Component
{
public Component()
{
this.ComponentPropertyValues = new HashSet<ComponentPropertyValue>();
this.ComponentVideos = new HashSet<ComponentVideo>();
}
public int Id { get; set; }
public int ComponentTypeId { get; set; }
public string Name { get; set; }
public virtual ICollection<ComponentPropertyValue> ComponentPropertyValues { get; set; }
public virtual ComponentType ComponentType { get; set; }
public virtual ICollection<ComponentVideo> ComponentVideos { get; set; }
}
public partial class ComponentPropertyValue
{
public int Id { get; set; }
public int ComponentPropertyId { get; set; }
public int ComponentId { get; set; }
public string Value { get; set; }
public virtual ComponentProperty ComponentProperty { get; set; }
public virtual Component Component { get; set; }
}
Related
User Model
public class UserModel
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public IEnumerable<UserPets> UserPets { get; set; }
}
User Pets Model
public class UserPetsModel
{
public PetModel Pet{ get; set; }
public bool UserHasPet { get; set; }
}
Using these 2 models I am creating an edit page where a User can come in and edit which Pets they have.
To enable them to state which pets they have I am trying to use checkboxes.
Edit Page
#model Models.UserModel
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.FirstName)
#Model.FirstName
</div>
#foreach (var userPets in Model.UserPets)
{
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(u => userPets .UserHasPet)
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
The problem I am having is when trying to map the UserModel back to the controller action. When I press the save button, everything on the UserModel is being mapped back to the controller apart from the UserPetsModels which I believe is due to the use of the foreach.
Is there another way in which I can display a checkbox for each UserPetModel without using a foreach or a for loop.
Yes there is. You should create EditorTemplate for your UserPetsModel. It will look like:
#model UserPetsModel
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(model => model.UserHasPet)
</div>
And then you can simply do:
#Html.EditorFor(model => model.UserPets)
EditorFor will create right binding for you. Note that you should create EditorTemplate only for UserPets and it also will work for List<UserPetsModel> and IEnumarable<UserPetsModel> with the same syntax that i show.
I would suggest replace the loop with EditorTemplate. So your
#foreach (var userPets in Model.UserPets)
{
#Model.Pet.AnimalName
<div>
#Html.CheckBoxFor(u => userPets.UserHasPet)
</div>
}
would look like:
<div class="row">
#Html.EditorFor(m => m.UserPets)
</div>
And define a view in (~/Views/Shared/EditorTemplates/UserPets.cshtml) like:
#model UserPetsModel
#Html.HiddenFor(x => x.Pet.PetId)
#Html.LabelFor(x => x.UserHasPet, Model.Pet.AnimalName)
#Html.CheckBoxFor(x => x.UserHasPet)
For registering a new customer I have several partials on my view which refer to corresponding tables in my database.
Primary keys are identity fields, that's why no IDs on the view. To insert a new customer I need to insert 3 occurrences of address(visiting, postal and contact person's), then insert a row for contract, contact_person(with using addressId from one of already inserted addresses) and finally proceed with inserting a new customer which will contain foreign keys referencing to just inserted visiting and postal addresses, contract and contact person.
Could you recommend the best/easiest way to pass the data from these partial views as objects to CustomerController and handle the objects there please?
Links to examples of handling similar situations will be also highly appreciated.
Image of my page:
http://i1345.photobucket.com/albums/p673/swell_daze/customer_registration_zps563341d0.png
Image of tables:
http://i1345.photobucket.com/albums/p673/swell_daze/tables_zpsc60b644a.png
View code:
#model CIM.Models.customer
<h2>Register new customer</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New customer registration</legend>
<fieldset class="span3">
<legend>Basic information</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name, "Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.groupId, "Customer group")
</div>
<div class="editor-field">
#Html.DropDownList("groupId", String.Empty)
#Html.ValidationMessageFor(model => model.groupId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.status, "Status")
</div>
<div class="editor-field">
#Html.DropDownList("status")
#Html.ValidationMessageFor(model => model.status)
</div>
</fieldset>
<fieldset class="span3">
<legend>Visiting address</legend>
<div>
#{
Html.RenderPartial("AddressPartial");
}
</div>
</fieldset>
<fieldset style="width:270px">
<legend>Postal address</legend>
<div>
#{
Html.RenderPartial("AddressPartial");
}
</div>
</fieldset>
<fieldset class="span3">
<legend>Contract</legend>
<div>
#{
Html.RenderPartial("ContractPartial");
}
</div>
</fieldset>
<fieldset class="span3" style="width:540px">
<legend>Contact person</legend>
<div>
#{
Html.RenderPartial("ContactPersonPartial");
}
</div>
<p>
<input type="submit" class="btn btn-success" value="Submit" style="margin-right:1em"/>
#Html.ActionLink("Cancel", "Index", "Customer", new {#class="btn btn-danger", #type="button"})
</p>
</fieldset>
</fieldset>
}
you can wrap your view data in a viewmodel, then when you submit your data it will be mapped to your viewmodel, something like this:
Create a viewmodel:
public class MyViewModel
{
public string name { get; set; }
public string someprop1 { get; set; }
public string someprop2 { get; set; }
public MyAddress visitingaddress { get; set; }
public MyAddress postaladdress { get; set; }
public MyContactAddress contactaddress { get; set; }
}
public class MyAddress
{
public string town { get; set; }
public string street { get; set; }
public string housenumber { get; set; }
public string postalcode { get; set; }
}
public class MyContactAddress : MyAddress
{
public string firstname { get; set; }
public string lastname { get; set; }
public string email { get; set; }
public string phonenumber { get; set; }
}
Create your view just like you have done but by using your viewmodel instead of the model you are using now (CIM.Models.customer), and then in your partials, for example in the postal address partial view do something like this:
.......
#Html.EditorFor(x => x.postaladdress.town)
......
Create your controller actions by using your viewmodel:
public ActionResult Index()
{
MyViewModel vm = new MyViewModel();
//doing something here....
return View(vm);
}
[HttpPost]
public ActionResult Index(MyViewModel vm)
{
//save your data, here your viewmodel will be correctly filled
return View(vm);
}
hope this helps
I created a partial view that should display a list of user with a check box , so i can reuse this partial view in various pages.
The problem is that, i'm not able to have the correct htmlprefix the input generated
(I would like to remove the . of the prefix )
Model:
public class CircleEditViewModel
{
[Key]
public int CircleId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public bool IsSystem { get; set; }
public List<SimpleUserListViewModel> Users { get; set; }
public CircleEditViewModel()
{
Users = new List<SimpleUserListViewModel>();
}
}
public class SimpleUserListViewModel
{
public SimpleUserListViewModel()
{
}
public SimpleUserListViewModel(User user)
{
this.UserId = user.UserId;
FullName = user.FullName;
}
public int UserId { get; set; }
public byte[] Picture { get; set; }
public string FullName { get; set; }
public bool IsCheckedForAction { get; set; }
}
'Main view':
#model Wims.Website.ViewModels.CircleEditViewModel
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse('form');
});
</script>
#using (Ajax.BeginForm(Html.ViewContext.RouteData.Values["Action"].ToString(), null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "SaveDone(data)" }, new { id = "editform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Circle</legend>
#Html.Label(DateTime.Now.ToString());
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
#Html.Partial("~/Views/Shared/_UserList.cshtml", Model.Users,
new ViewDataDictionary(Html.ViewDataContainer.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Users" }
})
#Html.GenerateSecureDataControls(model => model.CircleId)
<input type="submit" value="Save" />
}
Partial view:
#model List<Wims.Website.ViewModels.Shared.SimpleUserListViewModel>
#{
if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<div class="userDetail">
<div>
<div>
#Html.CheckBoxFor(model => model[i].IsCheckedForAction)
</div>
<div class="iconDiv">
#Html.Image("~/Content/Images/defaultUser.jpg", Model[i].FullName, null)
</div>
<div>
#Html.TextBoxFor(model => model[i].FullName)
#Html.HiddenFor(model => model[i].UserId)
</div>
</div>
</div>
<div style="clear: both"></div>
}
}
}
I am almost there, the input generated id's are
id="Users.[0].FullName
Is there any way i can remove the first dot?
I've found some solution yesterday on a blog (which i can't find anymore...) but it was for MVC3 and I couldn't make it work anyway...
Thanks for the help!
EDIT:
Maybe I should use EditorFor instead of partial view:
.NET MVC 4 Strongly typed ViewModel containing Strongly typed Model with EditorFor and EditorTemplate partial view not binding
Will check tonight
Alrighty, The EditorFor worked perfectly..
I need to read more about this.
This is my first MVC3 project and thought it'd be a bit simpler to accomplish this, but I've had a lot of issues. This seems to be the one trouble for which I've not encountered a solution. Posting here as a last resort.
My issue is that the Ingredients list never seems to populate in the database when a new Recipe is created. I've even tried hard-coding a list in the controller when the rest of the data is saved, but it still doesn't do anything. As far as I can tell, there's not even a field for Ingredients in the DB anywhere. Can anyone point out what I'm doing wrong? Much thanks.
Model:
public class Recipe
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
[Required]
[Display(Name = "Type of Meal")]
public int TypeID { get; set; }
[Required]
public string Instructions { get; set; }
[Display(Name = "Submitted By")]
public string UserName { get; set; }
public IList<string> Ingredients { get; set; }
public virtual MealType Type { get; set; }
}
Create View:
#model final.Models.Recipe
#{
ViewBag.Title = "Recipe Finder - New Recipe";
}
<h2>New Recipe</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>
<script type="text/javascript">
//adds an ingredient field on the fly
var numIngredients = 0;
function addIngredient() {
$('#ingredientlist').append('<tr><td><input type="text" name="Ingredients[' + numIngredients++ + ']" value="' + $('#AddIngredient').val() + '" readonly="true" tabindex="-1" /></tr></td>');
$('#AddIngredient').val('');
$('#AddIngredient').focus();
}
function onKeyPress(e) {
var keycode;
if (window.event) {
keycode = window.event.keyCode;
}
else if (e) {
keycode = e.which;
}
else {
return true;
}
//for addingredient field, add ingredient and not submit
// else mimic submit
if (keycode == 13) {
if (document.activeElement.id == "AddIngredient") {
addIngredient();
return false;
}
document.getElementById('btnSubmit').click();
}
return true;
}
//intercepts form submit instead of using submit button to disable addingredient textbox
// this prevents the value/field from posting
function preSubmit() {
$('#AddIngredient').attr('disabled', 'true');
document.getElementsByTagName('form')[0].submit();
}
//intercepts users pressing the enter key
if (document.layers) document.captureEvents(Event.KEYPRESS);
document.onkeypress = onKeyPress;
</script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>by #User.Identity.Name</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TypeID, "Type")
</div>
<div class="editor-field">
#Html.DropDownList("TypeID", String.Empty)
#Html.ValidationMessageFor(model => model.TypeID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Ingredients)
</div>
<div class="editor-field">
#Html.TextBox("AddIngredient")
<input type="button" value="Add Ingredient" onclick="addIngredient()" tabindex="-1" />
<table id="ingredientlist">
</table>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Instructions)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Instructions, new { rows = "7", cols = "77" })
#Html.ValidationMessageFor(model => model.Instructions)
</div>
#Html.HiddenFor(model => model.UserName)
<p>
<input type="button" value="Submit" onclick="preSubmit()" id="btnSubmit" />
</p>
</fieldset>
}
Controller:
[HttpPost]
public ActionResult Create(Recipe recipe)
{
if (ModelState.IsValid)
{
db.Recipes.Add(recipe);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TypeID = new SelectList(db.MealTypes, "ID", "Type", recipe.TypeID);
return View(recipe);
}
The POST fields I can see send all the information successfully, as seen below, I'm not sure what the issue is, or at this point what I haven't already tried.
Title:test recipe
TypeID:2
Ingredients[0]:test0
Ingredients[1]:test1
Ingredients[2]:test2
Ingredients[3]:test3
Instructions:this is a test
UserName:tym
You may want to check this page about storing a list of strings associated with an entity.
I have a Model that contains a List of a custom type.
I want the data from this type to be passed back in when a model is submitted as a HttpPost call the the controller.
However, it does not seem to do what I want. I've got where I am so far by following Passing IEnumerable or list Model to Controller using HttpPost but I'm having a problem.
My controller method:
[HttpPost]
public ActionResult UpdateStock(int id, ProductModel model)
{
return View("UpdateStock", model);
}
Now, the View is like this (trimmed):
#using (Html.BeginForm())
{
<div>
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#for ( int i = 0; i < Model.Stock.Count; i++ )
{
var item = Model.Stock[i];
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Key"
value="#item.Key" />
</div>
<div class="editor-field">
<input type="text" name="Model.Stock[#i].Value"
value="#item.Value" />
</div>
}
}
My problem is, that it seems the #Html.EditorFor() and <input type=.../> tags don't seem to play well with each other. If I have it like above, then the ProductNo and other properties using #Html methods won't be passed through to the model.
Any advice much appreciated.
I would simply use editor templates:
Model:
public class ProductModel
{
public string ProductNo { get; set; }
public string ProductName { get; set; }
public IEnumerable<Stock> Stocks { get; set; }
}
public class Stock
{
public string Key { get; set; }
public string Value { get; set; }
}
Controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new ProductModel
{
ProductNo = "123",
ProductName = "p name",
Stocks = new[]
{
new Stock { Key = "key1", Value = "value1" },
new Stock { Key = "key2", Value = "value2" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(ProductModel model)
{
...
}
}
View:
#model ProductModel
#using (Html.BeginForm())
{
<p>
<input type="submit" value="Save" />
</p>
#Html.HiddenFor(m => m.ProductNo)
<div class = "title">
#Html.LabelFor(m => m.ProductName)
#Html.EditorFor(m => m.ProductName)
</div>
#Html.EditorFor(x => x.Stocks)
}
and then you define a custom editor template for the Stock type (~/Views/Shared/EditorTemplates/Stock.cshtml):
#model Stock
<div class="editor-field">
#Html.EditorFor(x => x.Key)
</div>
<div class="editor-field">
#Html.EditorFor(x => x.Value)
</div>