Multiple same fields from one ViewModel. MVC3 - asp.net-mvc-3

I want to be able to add multiple (zero or more) phones to my contact via it's ViewModel.
I'd write this:
public class ContactsViewModel
{
public string Skype { get; set; }
public string Email { get; set; }
public string Addr { get; set; }
public IEnumerable<PhoneViewModel> Phones { get; set; }
}
public class PhoneViewModel
{
public string Number { get; set; }
}
But, as expected, it does not work.
What is the best way to organize this?
EDIT. Here is my views:
#model RentSite.Web.UI.Models.ContactsViewModel
#{
ViewBag.Title = "AddContact";
}
<h2>AddContact</h2>
#using (Html.BeginForm())
{
#Html.EditorForModel(Model)
#Html.Action("AddPhone")
<input type="submit" value="Add"/>
}
and
#model RentSite.Web.UI.Models.PhoneViewModel
#Html.EditorForModel(Model)
Here is my Controller:
public ActionResult Index()
{
return View(unitOfWork.contactRepository.All().ToList());
}
[Authorize]
public ActionResult AddContact()
{
return this.View();
}
[Authorize]
public ActionResult AddPhone()
{
return PartialView();
}
[Authorize]
[HttpPost]
public ActionResult AddContact(ContactsViewModel contact, IEnumerable<PhoneViewModel> phones)
{
contact.Phones = phones;
return RedirectToAction("AddContact");
}

The rendering of multiple phone number will work as you expect. I'm guessing that you are having an issue "posting" multiple phone numbers.
Well the solution depends on what you exactly want to achieve. Here are two examples:
Solution 1: The user can only post one phone number at the time. Render all the existing phone numbers, and create a form based on PhoneViewModel.
Solution 2: The user can add many phone numbers and post them all at the same time. You need some Javascript here. Implement client side functionality allowing the user to "add" a phone number field. Using javascript, uniquely name each input field (i.e phone1, phone2, ... phonen). Use the same pattern server side to get all the phone numbers from the POST data.
There are more solutions. Please specify what you exactly would like to achieve.
Edit: Sample code for Solution 2
[HttpPost]
public ActionResult PostPhoneNumbers(FormCollection formCollection)
{
var phoneNumbers =
from k in formCollection.AllKeys
where k.StartsWith("phoneNumber")
select formCollection[k];
// validate and process the phone numbers
return View("your view");
}
enter code here

Related

context.SaveChanges() works, but database is never updated? (MVC 3)

I've written a form in ASP.NET MVC3, and I can't get the entry to save the changes I make in the database, but while debugging, I noticed that the changes were reflected in the data context. I am experiencing no errors running this code. Let me know if you need more. Thanks!
Controller
[HttpPost]
public ActionResult Edit(Tool tool, FormCollection collection)
{
if (collection["Tool.Person.PersonID"] != "")
{
tool.Person= context.People.Find(
Convert.ToInt32(collection["Tool.Person.PersonID"])
);
}
if (collection["Tool.Company.CompanyID"] != "")
{
tool.Company = context.Companies.Find(
Convert.ToInt32(collection["Tool.Company.CompanyID"])
);
}
if (ModelState.IsValid)
{
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
The first two if statements are checking to see if the user inputted a person or company, and the information is passed via the FormCollection. PersonID and CompanyID are primary keys for Person and Company, respectively. I went through the method line by line multiple times and achieve the same result - after context.SaveChanges();, the context reflects the changes, but the database entries remain null for both Person_PersonID and Company_CompanyID.
Try using a view model and accessing the database after the user submits the form.
This should get you well on your way.
ViewModel
using System.ComponentModel.DataAnnotations;
namespace Project.ViewModels
{
public class _tools
{
[Required(ErrorMessage="ToolID is required")]
public int32 ToolID{ get; set; } //whatever ID you use to retrieve the Tool from the database.
[Required(ErrorMessage="PersonID is required")]
public int32 PersonID{ get; set; }
[Required(ErrorMessage="CompanyID is required")]
public int32 CompanyID{ get; set; }
}
}
Controller Post
[HttpPost]
public ActionResult Edit(_tool viewModel)
{
if (ModelState.IsValid)
{
Tool tool = db.GetTool(viewModel.ToolID) //whatever method you use to get a current version of the row. You already do this before you send the data to the client, so just copy that code
tool.Person = viewModel.PersonID
tool.Company = viewModel.CompanyID
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
View
#model = _tool
#using(Html.BeginForm("Edit", "ControllerNameHere", FormMethod.Post, null))
{
#Html.HiddenFor(model => model.ToolID)
#*Also add whatever inputs you use to get PersonID and CompanyID from the user.
Make sure to either use the #Html helpers or to give them names that correspond.
i.e. name one input PersonID and the other CompanyID*#
<input type="submit" value="Edit">
}

Trying to Extend ProDinner Chef class with collection of Phone Numbers

I am trying to extend ProDinner by adding phone numbers to Chef.
ChefInput view model:
public class ChefInput :Input
{
public string Name { get; set; }
public ChefInput()
{
PhoneNumberInputs = new List<PhoneNumberInput>(){
new PhoneNumberInput()
};}
public IList<PhoneNumberInput> PhoneNumberInputs { get; set; }
}
PhoneInput view model:
public class PhoneNumberInput :Input
{
public string Number { get; set; }
public PhoneType PhoneType { get; set; } <-- an enum in Core project
}
Chef Create.cshtml file:
#using (Html.BeginForm())
{
#Html.TextBoxFor(o => o.Name)
#Html.EditorFor(o => o.PhoneNumberInputs)
}
PhoneNumberInput.cshtml in EditorTemplate folder:
#using (Html.BeginCollectionItem("PhoneNumberInputs"))
{
#Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(typeof(PreDefPhoneType))))
#Html.TextBoxFor(m => m.Number)
}
When debugging and I stop it at Create in Crudere file, the Phone collection is null.
Anyone have any ideas?
Thanks in Advance.
Joe,
You don't show your controller logic but I've got a feeling you're getting null because you're not populating the PhoneNumberInputs ViewModel. From what I can see, all you're doing is newing up the list in the model. Ensure that you fill this 'list' in your controller from the database (with the appropriate values) and i'm certain all will work as planned.
[edit] - in answer to comment. don't know what the prodinner controllers etc look like but something alsong these lines:
public ActionResult Edit(int id)
{
var viewModel = new ChefInput();
viewModel.ChefInput = _context.GetById<ChefModel>(id);
viewModel.PhoneNumberInputs = _context.All<PhoneNumberInput>();
return View(viewModel);
}
as i said, not sure of the prodinner setup, but this is what i meant.

To show Create and detail view in one view using mvc3

Hi i want to show a page where i'm allowing user to create a new record and show the other related records of same table below .....
i need to add data in Hobbydetail class:
public class HobbyDetail
{
public virtual HobbyMasters Hobbymaster { get; set; }
public virtual Course course { get; set; }
public virtual StudyMedium StudyMedium { get; set; }
public virtual decimal Fees { get; set; }
}
I want my view "Create" to let the user create a new record and to also to show existing record below it...
I Dont want to use a viewmodel...
Can sumbody help me
Thanx in advance
One way to accomplish this is: In your controller, create a child action that renders the list, then render that action in your "Create" view using Html.RenderAction (also see this). I have included some code below (I have not tested this, but it should give you the basic idea). Please note this is not the only way to accomplish this - You could use a partial view see this. Please also understand the difference between html.RenderAction and html.Action, see this.
//In HobbyDetail Controller
[HTTPGet]
public ActionResult Create()
{
var model = new HobbyDetail ();
return View(model);
}
[HTTPPost]
public ActionResult Create(HobbyDetail model)
{
if(ModelState.isValid)
{
//logic to persist model
}
else
{
//logic when validation fails...
}
}
[ChildActionOnly]
public ActionResult ListAll()
{
List<Hobbydetail> model = //query to DB, or Data store to get Hobbydetails
return View(model);
}
//View for ListAll
#model List<HobbyDetail>
{
Layout = null; //No layout here...
}
<ul>
#foreach(var h in Model)
{
<li>#h.HobbyMasters.Name</li> //for example...
}
</ul>
//View for Create
#model HobbyDetail
...
#{html.renderAction("ListAll");}

Two render bodies in layout page?

I understand that only 1 RenderBody can exist in the MVC3 layout page however I want to attempt to create another. Maybe I'm looking at it the wrong way... Ideally I want to add a testimonial section that pulls in from the DB and display 1 testimonial at a time and a different 1 for each page refresh or new page. What is the best way to go about this?
Controller
CategoryDBContext db = new CategoryDBContext();
public ActionResult Testimonial(int id)
{
TestimonialModel model = db.Testimonials.Find(id);
return View(model);
}
Model
public class TestimonialModel
{
public int ID { get; set; }
public int CategoryID { get; set; }
public string Data { get; set; }
}
public class CategoryDBContext : DbContext
{
public DbSet<TestimonialModel> Testimonials { get; set; }
}
The View is in a folder called CategoryData.
You need to be use:
Layout:
#RenderSection("Testimonial", false) #*false means that this section is not required*#
and in you View
#section Testimonial{
}
I would use #Html.Action()
Here is a great blog post about using them: https://www.c-sharpcorner.com/article/html-action-and-html-renderaction-in-Asp-Net-mvc/
This would allow you to have a TestimonialController that can take in values, query for data and return a partial view.

MVC3 Only posted form values retained?

I am using strongly typed views in an MVC3 web app. I've noticed that when a form is submitted, the ViewModel that is passed to the controller only has values for properties that have form elements associated with them. For instance, the example below shows a simple confirmation View with a checkbox and a phone number that the user must confirm before proceeding. When the form is submitted to the controller action, the UserConfirmed property contains a value, but the PhoneNumber property is null.
Is there any way for the ViewModel to retain all of its values or do I have to repopulate the ViewModel properties that do not have form elements associated with them?
The View
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
The Controller
[HttpPost]
public ActionResult ScheduleConfirmation(ScheduleConfirmationViewModel model)
{
if (model.UserConfirmed)
{
// add ViewModel data to repository
}
else
{
ModelState.AddModelError("ERROR", WebResources.strERROR_ConfirmSchedule);
}
return View(model);
}
Since your writing the phonenumber as output to the page it won't be automatically posted back (you've found out that part) What you can do is populate an hidden or read-only field with the phonenumber so that it will be posted back to your controller. An second option is to make a new call to your datasource and repopulate your object before saving it back to your datasource.
I generally POST back information like this in a hidden input. I personally use this heavily to pass data needed to return the user exactly where they where before pressing edit.
In your case it's as simple as
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
#Html.HiddenFor(m => m.PhoneNumber)
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
For future reference:
If your passing complex objects back you need one hidden field per attribute (Hiddenfor does NOT iterate)
View
WRONG
#Html.HiddenFor(m => m.PagingData)
RIGHT
#Html.HiddenFor(m => m.PagingData.Count)
#Html.HiddenFor(m => m.PagingData.Skip)
#Html.HiddenFor(m => m.PagingData.PageSize)
Action
public HomeController(AViewModel Model)
{
PagingData PagingData = Model.PagingData;
Skip = PagingData.Skip;
}
If your passing Arrays you can do it like this
View
#if (Model.HiddenFields != null)
{
foreach (string HiddenField in Model.HiddenFields)
{
#Html.Hidden("HiddenFields", HiddenField)
}
}
Action
public HomeController(AViewModel Model)
{
String[] HiddenFields = Model.HiddenFields;
}
Well, the form will only POST elements that you have created. As you found out, simply writing the phone number out to the page will not suffice. The model binder can only bind those properties which exist in the posted data.
Generally you have a couple of options here:
1) You can create Input elements for all of the properties in your model, using visible elements (like a textbox) for those properties you want to edit, and hidden elements which should be posted back but have no UI
2) Post back a partial representation of your model (as you are doing now), read the entity back in from it's data source (I assume you're using some kind of data source, EF maybe) and then alter the properties of that entity with the ones from your form.
Both scenarios are common but it really depends on the complexity of your model.
I know this thread is a bit old, but thought I'd resurrect it to get feed back on my solution to this.
I'm in a similar situation where my objects are passed to a view, and the view may only display part of that object for editing. Obviously, when the controller receives the model back from the default model binder, and values not posted back become null.. and saving this means that a DB value becomes null just because it wasn't displayed/returned from a view.
I didn't like the idea of creating a model for each view. I know it's probably the right way... but I was looking for a reusable pattern that can be implemented fairly quickly.
See the "MergeWith" method... as this would be used to take a copy of the object from the database and merge it with the one returned from the view (posted back)
namespace SIP.Models
{
[Table("agents")]
public class Agent
{
[Key]
public int id { get; set; }
[Searchable]
[DisplayName("Name")]
[Column("name")]
[Required]
[StringLength(50, MinimumLength = 4)]
public string AgentName { get; set; }
[Searchable]
[DisplayName("Address")]
[Column("address")]
[DataType(DataType.MultilineText)]
public string Address { get; set; }
[DisplayName("Region")]
[Searchable]
[Column("region")]
[StringLength(50, MinimumLength = 3)]
public string Region { get; set; }
[DisplayName("Phone")]
[Column("phone")]
[StringLength(50, MinimumLength = 4)]
public string Phone { get; set; }
[DisplayName("Fax")]
[Column("fax")]
[StringLength(50, MinimumLength = 4)]
public string Fax { get; set; }
[DisplayName("Email")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[Column("email")]
[StringLength(50, MinimumLength = 4)]
public string Email { get; set; }
[DisplayName("Notes")]
[Column("notes")]
[DataType(DataType.MultilineText)]
public string Notes{ get; set; }
[DisplayName("Active")]
[Column("active")]
public bool Active { get; set; }
public override string ToString()
{
return AgentName;
}
public bool MergeWith(Agent a, string[] fields)
{
try
{
foreach (PropertyInfo pi in this.GetType().GetProperties())
{
foreach (string f in fields)
{
if (pi.Name == f && pi.Name.ToLower() != "id")
{
var newVal = a.GetType().GetProperty(f).GetValue(a,null);
pi.SetValue(this, newVal, null);
}
}
}
}
catch (Exception ex)
{
return false;
//todo: Log output to file...
}
return true;
}
}
}
And to use this in the controller.. you'd have something like..
[HttpPost]
public ActionResult Edit(Agent agent)
{
if (ModelState.IsValid)
{
Agent ag = db.Agents.Where(a => a.id == agent.id).ToList<Agent>().First<Agent>();
ag.MergeWith(agent, Request.Params.AllKeys);
db.Entry(ag).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(agent);
}
This way, during post back, it takes the object from the database, and updates it with object from view... but only updates the values that were posted back.. So if you have a field like "address" or something that doesn't appear in the view.. it doesn't get touched during the update.
I've tested this so far and i works for my purposes, tho i welcome any feedback as I'm keen to see how others have overcome this situation. It's a first version and i'm sure it can be implemented better like through an extension method or something.. but for now the MergeWith can be copy/pasted to each model object.
Yes, Just place hidden fields in the form for those values which you are not using and want to return to server control.
Thanks

Resources