Can I edit multiple models in ASP.NET MVC using Partial Views? - asp.net-mvc-4

I'm new to ASP.NET MVC 4 and I'm struggling with a concept that would be easy in webforms. So, if I have a Customer class, and a Customer has an Address, how can I edit both the Customer and Address on the same form in the same submit action? I would like to create an '_Edit' partial view for the Address, but I don't know how to wire the controller up if there is no submit button for the Address. I just want a single button to save all the Models in a single View.
So, I could create a new CompanyView model, view, and controller and do it that way. However, if I have many objects having Addresses it seems like a lot of work to keep creating View Models to allow you to edit both the object and the address in the same form. Is there a way to create an Address partial edit view and somehow update the Address fields in the Company Edit Controller? Or, have it somehow pass the model.Address back to the Company Edit Controller instead of null?
Edit:
Models
public class Address
{
public Int32 Id { get; set; }
[Display(Name = "Address 1")]
public String Address1 { get; set; }
[Display(Name = "Address 2")]
public String Address2 { get; set; }
[Display(Name = "City")]
public String City { get; set; }
[Display(Name = "State")]
public String State { get; set; }
[Display(Name = "Postal Code")]
public String PostalCode { get; set; }
[Display(Name = "Country")]
public String Country { get; set; }
}
public class Company
{
public Int32 Id { get; set; }
[Display(Name = "Company Name")]
public String Name { get; set; }
public Int32 AddressId { get; set; }
public virtual Address Address { get; set; }
}
Address _Edit Partial View
#model Models.Address
<div class="well">
<fieldset>
<legend>Address</legend>
#Html.EditorFor(model => model.Address1)
#Html.EditorFor(model => model.Address2)
#Html.EditorFor(model => model.City)
#Html.EditorFor(model => model.State)
#Html.EditorFor(model => model.PostalCode)
#Html.EditorFor(model => model.Country)
</fieldset>
</div>
Company Edit View
#model Models.Company
#{
ViewBag.Title = "Edit";
Layout = "~/Views/shared/ContentLayout.cshtml";
}
<div class="row">
<div class="col-lg-12">
<div class="page-header">
<h2>Edit Company</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
#using (Html.BeginForm("Edit", "Company", new { #class = "bs-example form-horizontal" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Name)
#Html.HiddenFor(model => model.AddressId)
#Html.Partial("~/Views/Address/_Edit.cshtml", Model.Address)
<p>
<button name="button" type="submit" class="btn btn-primary" value="submit">Submit</button>
</p>
}
</div>
</div>
Company Edit Controller
[HttpPost]
public ActionResult Edit(Company model, int id)
{
if (ModelState.IsValid)
{
// model.Address = NULL here!
Success("Record updated!");
return RedirectToAction("Index");
}
}

For model binding to work properly, you need to post only a Company back to your controller. Just pass your whole model onto your partial :
#model Models.Company
#{
ViewBag.Title = "Edit";
Layout = "~/Views/shared/ContentLayout.cshtml";
}
<div class="row">
<div class="col-lg-12">
<div class="page-header">
<h2>Edit Company</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
#using (Html.BeginForm("Edit", "Company", new { #class = "bs-example form-horizontal" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Name)
#Html.HiddenFor(model => model.AddressId)
#Html.Partial("~/Views/Address/_Edit.cshtml", Model)
<p>
<button name="button" type="submit" class="btn btn-primary" value="submit">Submit</button>
</p>
}
</div>
</div>
_Edit
#model Models.Company
<div class="well">
<fieldset>
<legend>Address</legend>
#Html.HiddenFor(model => model.Address.Id)
#Html.EditorFor(model => model.Address.Address1)
#Html.EditorFor(model => model.Address.Address2)
#Html.EditorFor(model => model.Address.City)
#Html.EditorFor(model => model.Address.State)
#Html.EditorFor(model => model.Address.PostalCode)
#Html.EditorFor(model => model.Address.Country)
</fieldset>
</div>
Controller
[HttpPost]
public ActionResult Edit(Company model, int id)
{
if (ModelState.IsValid)
{
// model.Address should now be available
Success("Record updated!");
return RedirectToAction("Index");
}
}
You should now see the Address navigation property of your model properly bound on post.
Edit based on question in comment
How you set up your views and partials is up to you really. The important thing to remember is that model binding works based on the names given to the form elements by the HTML helpers.
So Html.HiddenFor(m => m.Id) will result in <input name="Id"> while Html.HiddenFor(m => m.Address.Id) will result in <input name="Address.Id">. First one won't be picked up by the model binder as a navigation property of Company, second one will.
The simple route would be to just duplicate your partial view. But if it gets to the point where your partial becomes quite large and complex with a high amount of fields, you could create a partial base class that both your entities inherit of.
BaseEntityWithAddress.cs
public partial class BaseEntityWithAddress
{
public virtual Address Address { get; set; }
}
Customer.cs
public class Customer : BaseEntityWithAddress
{
// your properties, no need to redefine Address here
}
Vendor.cs
public class Vendor: BaseEntityWithAddress
{
// your properties, no need to redefine Address here
}
And then your partial view would take BaseEntityWithAddress as a model to which you would still pass the whole model.
_Edit.cshtml
#model Models.BaseEntityWithAddress
<div class="well">
<fieldset>
<legend>Address</legend>
#Html.HiddenFor(model => model.Address.Id)
#Html.EditorFor(model => model.Address.Address1)
#Html.EditorFor(model => model.Address.Address2)
#Html.EditorFor(model => model.Address.City)
#Html.EditorFor(model => model.Address.State)
#Html.EditorFor(model => model.Address.PostalCode)
#Html.EditorFor(model => model.Address.Country)
</fieldset>
</div>
And that will generate form elements with the correct names for the model binder to pick up.

So you would have 2 classes:
class Address
{
public string street {get;set;}
public string state {get;set;}
}
class Customer
{
public string name {get;set;}
public Address address {get;set;} // links to the above class.
}
your main customer view would be something like:
#model Models.Customer
#using (Html.BeginForm()
{
#Html.TextBoxFor(m => m.name)
#Html.Partial("_Edit", Model.address)
<button type="submit">Submit</button>
}
your partial:
#model Models.Address
#Html.TextBoxFor(m => m.street)
#Html.TextBoxFor(m => m.state)
then in your controller:
public ActionResult customer(Customer model)
{
// do whatever
}

Related

Want to add multiple textbox in mvc4 regor

I have two Model Class
public partial class EndUser
{
public EndUser()
{
this.Mobiles = new List<Mobile>();
}
public virtual long Id { get; set; }
public virtual string FullName { get; set; }
[DataType(DataType.Password)]
public virtual string UserPassword { get; set; }
public virtual string Email { get; set; }
public virtual IList<Mobile> Mobiles { get; set; }
public partial class Mobile
{
public virtual long Id { get; set; }
public virtual string Number { get; set; }
public virtual EndUser EndUser { get; set; }
}
My controller
public ActionResult CreateClient()
{
return View("CreateClient");
}
Scaffold Template Create My view
CreateClient
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>EndUser</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FullName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FullName)
#Html.ValidationMessageFor(model => model.FullName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UserPassword)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserPassword)
#Html.ValidationMessageFor(model => model.UserPassword)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
But I want to add my Client Mobile From this view. How can i create multiply(unknown) text field to add mobile numbers? If client have more then one mobile number he can add more textbox by clicking "Add More Number" button. I cant create textbox for mobile number field.
Please help me out. Im new in MVC.
It's not as easy as you think...
Check this for a quick info. Check this for a complete how-to
You can resume it in a few steps.
Create a view (ie) "DynamicMobileForm" where you will have a foreach Model.Mobiles, it should create a textbox for each item.
In the controller, create a Get method to retrieve only this partial DynamicMobileForm view (You can use the same model, should contain at least your Mobiles property loaded)
In your ABM form, remove any html regarding mobile, and paste this:
#{Html.RenderPartial("DynamicMobileForm", Model);} this will render this section only.
Now you just need a way to dynamically increment the amount of editors, this is very easy, create a button, and with jquery perform a call to controller with a POST Method, like DynamicMobileFormAddRow... Inside this method, you only need to Add() a new instance inside your Model.Mobiles list, and return the same model as your GetMethod.
When the page loads, will render your partial section only, adding a new textbox, and when you submit it, will automatically post it inside your Mobiles Array.
Hope it helps.

MVC "create view" when there is one to many relationship in model

My model is simple, one client can have many phone numbers :
I have represented this in Entity Framework
Generated client class is as below.
public partial class Client
{
public Client()
{
this.PhoneNumbers = new HashSet<PhoneNumber>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
}
And now I need to create a view page for "create client". This page should have space to enter PhoneNumbers also (ex: By default there should be two text boxes to enter phone numbers)
<fieldset>
<legend>Client</legend>
<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="Create" />
</p>
</fieldset>
as the above "create view" we can easily give an space for "model.Name", because it is a simple property. But how can i do something similar for collection of phone numbers..??
I know that we can achieve this with ugly javascript code, but I would like to know the best easy and simple way, that we can use with ASP.NET MVC ... ?
You have to do a few things:
First create a ViewModel that has the properties you need:
public class ClientViewModel
{
public int Id {get;set;}
public string Name {get;set;}
public PhoneNumber PhoneNumber1 {get;set;}
public PhoneNumber PhoneNumber2 {get;set;}
}
Change Create to return the ClientViewModel
[HttpGet]
public ActionResult Create()
{
return View(new ClientViewModel());
}
Map the HttpPost to use the ClientViewModel and map the values to it:
[HttpPost]
public ActionResult Create(ClientViewModel clientViewModel)
{
var client = new Client();
client.Name = clientViewModel.Name;
client.PhoneNumbers.Add(clientViewModel.PhoneNumber1);
client.PhoneNumbers.Add(clientViewModel.PhoneNumber2);
db.Clients.Add(client);
db.SaveChanges();
return RedirectToAction("Index", "Client");
}
Then, finally, modify your view:
<fieldset>
<legend>Client</legend>
<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>
<div class="editor-label">
#Html.LabelFor(model => model.PhoneNumber1.Number)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumber1.Number)
#Html.ValidationMessageFor(model => model.PhoneNumber1.Number)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PhoneNumber2.Number)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumber2.Number)
#Html.ValidationMessageFor(model => model.PhoneNumber2.Number)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
For the collection you can use something like this:
#for(int i = 0; i < Model.PhoneNumbers.Count; i++)
{
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumbers[i])
#Html.ValidationMessageFor(model => model.PhoneNumbers[i])
</div>
}

How to render a partial view from a different model/controller?

I have the following partial view called _Categories residing in ~/Views/Category/_Categories:
#model IEnumerable<MyBlogSite.Models.BlogPostCategory>
<ul>
#foreach (var item in Model)
{
<li>#Html.DisplayFor(modelItem => item.Name)</li>
}
</ul>
I have the following Index view at ~/Views/Blog/Index.cshtml:
#model IEnumerable<MyBlogSite.Models.BlogPost>
#{
ViewBag.Title = "Index";
}
#Html.RenderPartial("~/Views/Category/_Categories.cshtml", ---- );
<p>
#Html.ActionLink("New Blog Post", "Create")
</p>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
...
In the space of the dashes (----) above is where I have been trying to figure out how to pass the model in. I cannot use Model.BlogPostCategory because it only accepts Model.BlogPost. I have also tried "model" with a lower-case "m" as per a couple of posts that I saw here.
I create viewmodel for my partialview, then pass its data. For example:
my viewmodel
public class CompanyCreateViewModel
{
public Company Company { get; set; }
public IList<CompanyContact> CompanyContacts { get; set; }
public IQueryable<ContactType> ContactTypes { get; set; }
}
partialview
#model Invoice.Model.HelperClasses.CompanyCreateViewModel
---
<div class="editor-field">
#Html.TextBoxFor(model => model.Company.FullName)
#Html.ValidationMessageFor(model => model.Company.FullName)
#Html.TextBoxFor(model => model.Company.ShortName)
#Html.ValidationMessageFor(model => model.Company.ShortName)
#Html.TextBoxFor(model => model.Company.TIN)
#Html.ValidationMessageFor(model => model.Company.TIN)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Company.Address.Country)
#Html.TextBoxFor(model => model.Company.Address.City)
#Html.TextBoxFor(model => model.Company.Address.Street)
</div>
---
and View with calling partialview
#model Invoice.Model.HelperClasses.CompanyViewModel
---
<div id="CompanyCreateModal" class="modal hide fade">
#{Html.RenderPartial("CompanyParts/CompanyCreateModal", new Invoice.Model.HelperClasses.CompanyCreateViewModel() { ContactTypes = Model.ContactTypes });
}
</div>
----

Multi selectable enum

I am working on a tool so i can keep things organized in a game.
This is my class:
//The item
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Value { get; set; }
public ItemLabel Label { get; set; }
public ItemType Type { get; set; }
public ItemTradeType TradeType { get; set; }
public Trade Trade { get; set; }
}
The Label / Type / TradeType / Trade are enums.
View:
#model EveMonitorV2.Models.Item
#{
ViewBag.Title = "AddItem";
}
<h2>AddItem</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Item</legend>
<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>
<div class="editor-label">
#Html.LabelFor(model => model.Value)
</div>
//What should be done here?
<div class="editor-field">
#Html.EditorFor(model => model.Value)
#Html.ValidationMessageFor(model => model.Value)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Trade)
</div>
<div class="editor-field">
#Html.CheckBoxFor()
#Html.ValidationMessageFor(model => model.Trade)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The enum has a whole list of possibilities and I want to make an Item Create view
The problem i run into:
I want to be able to select more options from the enums. (Like this)
Where the categories are my enums.
Is this possible at all in asp.net mvc 4?
(little note: I am still a student, but it isn't a school project)
Create View\Shared\EditorTemplates\Options.cshtml
#using System.ComponentModel.DataAnnotations
#using System.Reflection
#model Enum
#{
var name = ViewData.TemplateInfo.HtmlFieldPrefix;
var type = Model.GetType();
}
#foreach (Enum e in Enum.GetValues(type))
{
var display = type.GetField(e.ToString()).GetCustomAttribute<DisplayAttribute>();
if (display != null && (display.GetAutoGenerateField() ?? true))
{
<label class="checkbox" title="#display.GetDescription()">
<input type="checkbox" name="#name" value="#e.ToString()" checked="#Model.HasFlag(e)" />
#display.Name
</label>
}
}
your enum may be described as next:
[Flags]
public enum MyOptions
{
[Display(AutoGenerateField = false)]
None = 0,
[Display(Name = "Option 1 name")]
Opt1 = 1 << 1,
[Display(Name = "Option 2 name")]
Opt2 = 1 << 2,
[Display(Name = "Option 3 name")]
Opt3 = 1 << 3,
}
than, using:
<div class="editor-field">
#Html.LabelFor(m => m.Trade)
#Html.EditorFor(m => m.Trade, "Options")
</div>

.NET MVC 4 Strongly typed ViewModel containing Strongly typed Model with EditorFor and EditorTemplate partial view not binding

There has been a lot of questions about this... but somehow I can't get this binding to work and I'm still getting null values in my posted View Model. This is MVC 4.
Here is the Main View Model
public class RoleVM {
[Required]
[Display(Name = "Name of the Role")]
public string Role {get; set;}
public IEnumerable<RolePermission> permissions { get; set; }
}
Here is the RolePermission Class
public class RolePermission {
public int id;
public bool permission_value;
public string name { get; set; }
}
Here is GET Create Method in the controller
public ActionResult Create() {
RoleVM role_vm = new RoleVM();
var allpermissions = from p
in permission_repo.GetPermissions()
select p;
role_vm.permissions = from p
in allpermissions
select new RolePermission
{ name = p.name, id = p.PermissionId, permission_value = false };
return View(role_vm);
}
Here is the Create.cshtml file
#model RoleVM
#using (Html.BeginForm("Create", "Role",
FormMethod.Post, new { #class = "permission_form" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>RoleVM</legend>
<div class="form-item">
#Html.LabelFor(model => model.Role)
#Html.EditorFor(model => model.Role)
#Html.ValidationMessageFor(model => model.Role)
</div>
#Html.EditorFor(model => model.permissions)
<p>
<input class="submit-btn" type="submit" value="Create" />
</p>
</fieldset>
}
Next here is the rolepermissions.cshtml file located in ~/Views/Shared/EditorTemplates
#model RolePermission
<div class="form-item">
#Html.HiddenFor(modelItem => modelItem.id)
#Html.LabelFor(modelItem => modelItem.permission_value, Model.name)
#Html.CheckBoxFor(modelItem => modelItem.permission_value)
</div>
Here is an example of one of the html items that is rendered on page
<div class="form-item">
<input data-val="true" data-val-number="The field Int32 must be a number." data-val-required="The Int32 field is required." id="permissions_2__id" name="permissions[2].id" type="hidden" value="3" />
<label for="permissions_2__permission_value">Role-Edit</label>
<input data-val="true" data-val-required="The Boolean field is required." id="permissions_2__permission_value" name="permissions[2].permission_value"
type="checkbox" value="true" /><input name="permissions[2].permission_value" type="hidden" value="false" />
</div>
Finally here is the Create POST method
[HttpPost]
public ActionResult Create(RoleVM rolevm)
{
//In here rolevm.role is populated based on the textbox input
//However rolevm.permissions is there with the correct
//number of items, but the values all are not binded
// id is null, name is empty, and permission_value is false
// regardless of which checkboxes were checked
return RedirectToAction("Index");
}
Any help on the binding issue with the posted model would be really great.
This was a simple issue, if someone else comes across it maybe this will help them. I didn't have the properties on the class declared as properties with get; set;
public class RolePermission {
public int id { get; set; }
public bool permission_value { get; set; }
public string name { get; set; }
}