I am having problems sending a model from a form on my view to my controller. The main class I has the data in it but the sub classes have lost their values.
I have a main Quotation class like this:
public class Quotation
{
public string QuotationID { get; set; }
public TaxiPartner taxiPartner { get; set; }
public TaxiCompany taxiCompany { get; set; }
public int maxNumberOfSeats { get; set; }
public int maxNumberOfBags { get; set; }
public double price { get; set; }
}
Then for example I have the TaxiPartner class:
public class TaxiPartner
{
public string Id { get; set; }
public string Name { get; set; }
public List<Address> Addresses { get; set; }
public bool supportsPasswordReset { get; set; }
public bool supportsValidatebysms { get; set; }
}
Here is the form in my view
#if (Model.listOfQuotations.Count > 0)
{
<h3>You can book online with:</h3>
foreach (var item in Model.listOfQuotations)
{
using (Html.BeginForm("BookingPage1", "SearchResults", FormMethod.Post))
{
<div class="well">
<div>
<h4>#item.taxiCompany.Name</h4>
</div>
<div style="float: right;">
<h2>#string.Format("{0:C}", item.price)</h2>
</div>
<div>
<b>Product Type:</b>
<img style="width: 15px;" src="~/Content/images/icon-#item.taxiPartner.cabforceProductType-orange-40px.png" />
(1 = Best Value, 2 = Executive, 3 = Minibus)
</div>
<div><b>Services:</b> #item.taxiCompany.services (0 = Outdoor pickup, 1 = Meet & greet pickup)</div>
<div><b>Maximum Number of Seats:</b> #item.maxNumberOfSeats <b>Maximum Number of Bags:</b> #item.maxNumberOfBags</div>
<div>#Html.ActionLink("Book Now", "BookingPage1", item, new { #class = "btn btn-success" })</div>
</div>
}
}
}
I then have the following action in my controller:
[HttpPost]
public ActionResult BookingPage1(Quotation chosenQuote)
{
return View();
}
When my controller action is called I can see the values for QuotationID, maxNumberOfSeats, maxNumberOfBags, and price but the values that were contained in taxiPartner and taxiCompany are all NULL?
I have debugged and the value of "item" in the view does contain the values I need but they have disappeared once it gets to the controller.
Can anyone see what I am doing wrong please?
Only the model fields assigned to input types will available in the controller
Merely displaying the values isn't enough for it to be passed back. If you have a value that you don't have an input for, you should use a hidden value:
#Html.HiddenFor(x=>x.TaxiPartner.Name)
Also, something you may want to think about: if you have a bunch of properties that aren't being used/displayed in this view, you may want to think about creating a separate view model that only holds the values you need for that view rather than putting a bunch of hidden input fields to pass around values that aren't ever needed.
Related
I know one view can have only one model, so I put these two models in one file SurveyForm. How to get the results, the answered question and send them to the controller?
public class SurveyForm
{
public List<Question> SurveyQuestions { get; set; }
public List<Answer> SurveyResults { get; set; }
}
public class Question
{
public int Id { get; set; }
public int? ParentID { get; set; }
public string Text { get; set; }
public List<Response> Responses { get; set; }
public List<Question> SubQuestions { get; set; }
public string RenderType { get; set; }
}
public class Answer
{
public int ResponseID { get;
public string Value { get; set; }
}
I render Questions looping through Questions, there are some subQuestions so I have nested loops (three levels of loops). I am able to render these questions, but getting the results is not really easy for me. If I make a SurveyForm model, consisting of List of Questions and Answers, how to set the ResponseID and Value for each Result item inside the list? Using for loop will not work, because there are 20 responses, but 7 questions and 13 subquestions.
Here is a part of the view that I'm rendering:
#model MyApp.Models.SurveyForm
#{
ViewBag.Title = "Survey";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("SubmitForm", "Survey"))
{
int orderNumber = 1;
foreach (var item in Model.SurveyQuestions)
{
<div> #(orderNumber++)) #item.Text </div> //just a title of the question
if (item.RenderType == "Radiobutton")
{
<div>
#foreach (var subitem in item.Responses)
{
<div>
#Html.RadioButton(subitem.QuestionID.ToString(), subitem.ResponseID)
#Html.Label(subitem.Text)
</div>
}
</div>
<div #Html.TextArea(item.SubQuestions.FirstOrDefault().Answers.FirstOrDefault().ResponseID.ToString(), null, new { #class = "form-control" })</div>
}
if (item.RenderType == "Textbox")
{
<div>
#Html.TextArea(item.Id.ToString(), null, new { #class = "form-control" })
</div>
}
}
}
Two points here I think:
You don't have to use the same model when posting back to the server as you used when setting up the page. i.e, you can pass your view a model of List<Question>, which you loop through to build the HTML. And then in your POST Controller method (SubmitForm), can have a signature like the below
Which might help simplify your model objects but isn't necessary.
public async Task<IActionResult> SubmitForm(List<Answer> model)
{
}
2.
In order to bind a list of items in your HTML to an object, you need to give the name property of the html element an 'index' value, that represents the index of the item in the list, after the name of the Object so it will be like
"Answer[index].ResponseId", where Answer is the name of the Object you're trying to bind to.
Your HTML can be something like this:
for(int i =0; i < Model.SurveyQuestions; i++)
{
var item = Model.SurveyQuestions[i];
if (item.RenderType == "Textbox")
{
<div>
<textarea name="SurveyResults[i].ResponseID" value="#item.Id.ToString()" class = "form-control" >
</div>
}
}
Have a look at this similar question I answered previously
Hope that points you in the right direction.
This question already has answers here:
Multiple models in a view
(12 answers)
Closed 6 years ago.
I am devolaping a 2nd hand item selling like OLX, I have two models
1: Product_model
2: Customer_model
so i want to use the the product description and customer information in a single view.
Models:
[Table("Product")]
public class ProductModel
{
[Key]
public string ProductId { get; set; }
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public string ProductDescription { get; set; }
public string ProductCategory { get; set; }
public string ProductAddress { get; set; }
public string ProductImage1 { get; set; }
public string ProductImage2 { get; set; }
public string ProductImage3 { get; set; }
public string CustomerId { get; set; }
// public DateTime ProductSubTime { get; set; }
}
[Table("Customer")]
public class CustomerModel
{
[Key]
[Required(ErrorMessage = "Please provide Customer ID",
public string CustomerFullName { get; set; }
public int CustomerContact { get; set; }
public string CustomerEmail { get; set; }
public string CustomerGender { get; set; }
public string CustomerAddress { get; set; }
}
My controller:
public ActionResult Index()
{
return View(cm.Products.ToList());
}
My view:
#model IEnumerable<WebApplication11.Models.ProductModel>
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/MyTemplate.cshtml";
}
<div class="col-md-4"style="border: 2px solid yellow" >
<div class="single-product-widget" style="border: 2px solid black">
<h2 class="product-wid-title">Recently Viewed</h2>
View All
#{ int i = 0;}
#foreach (var item in Model)
{
<div class="single-wid-product" style="border: 2px dashed black">
<img src="~/images/#item.ProductImage1" alt="No Photo" class="product-thumb">
<h2>#Html.DisplayFor(model => item.ProductName)</h2>
</div>
}
<div>
</div>
For understanding purpose i deleted some part of view.
Please guide me accessing both models in particular view.
I tried also some methods but not able to get it.
Thanks in advance...
You can create a view model with 2 properties like following code
public class MyModel
{
public List<ProductModel> Products { get; set; }
public List<CustomerModel> Customers { get; set; }
}
In controller:
public ActionResult Index()
{
var model = new MyModel
{
Products = cm.Products.ToList(),
Customers = cm.Customers.ToList()
};
return View(model);
}
In Veiw
#foreach (var item in Model.Products)
{
#*Write your razor code here*#
}
There are several options to access several models in a particular view. I assume that you just want to show some customer information in page corner or something. Your layout view may have a requirement to include this customer info (but it does not matter anyway).
Most preferable option from my point of view is to use child action.
Create CustomerController
public class CustomerController : Controller {
[ChildActionOnly]
public ViewResult CustomerInfo() {
CustomerModel customer = ...
return PartialView("_CustomerPartial", customer);
}
}
.chstml view will have #Html.Action("CustomerInfo", "Customer") where you want customer info to be displayed.
This way you will have a separate CustomerModel creation logic.
Another option is to use ViewData or ViewBag to store secondary model information like customer info.
Controller code:
public ActionResult Index()
{
var products = cm.Products.ToList();
ViewBag.Customer = ... // get customer
return View(products);
}
View code:
#model IEnumerable<WebApplication11.Models.ProductModel>
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/MyTemplate.cshtml";
var customer = (CustomerModel)ViewBag.Customer;
}
#* display customer info *#
#* either { *#
<div>Welcome #customer.CustomerFullName. You email is #customer.CustomerEmail</div>
#* } or { *#
#Html.Partial("_CustomerPartial", customer)
#* } *#
#foreach(var product in Model) {
#* display product info *#
}
Previous option can be changed in a way to use strongly typed model. It is also can be known as view model - usually a class that exists only to store necessary info for the view. In your case these are some customer and products information.
ViewModel:
public class ProductListViewModel {
public CustomerModel Customer { get; set; }
public IEnumerable<ProductModel> Products { get; set; }
}
I have the following:
View model:
public ICollection<SelectConfigurableDataSyncOption> SelectConfigurableOptions { get; set; }
SelectConfigurableDataSyncOption:
public class BaseConfigurableDataSyncOption
{
public string OptionText { get; set; }
public string OptionName { get; set; }
}
public class SelectConfigurableDataSyncOption : BaseConfigurableDataSyncOption
{
public Dictionary<string, string> OptionValue { get; set; }
}
.cshtml:
#foreach (SelectConfigurableDataSyncOption option in Model.SelectConfigurableOptions)
{
<div class="span6">
<h4>#option.OptionText?</h4>
<p>#Html.DropDownListFor(m => {WHAT TO DO}, new SelectList(option.OptionValue, "Key", "Value"), new { #class = "m-wrap span12" })</p>
</div>
}
The problem I have is posting the values of the selected drop down lists back to the controller. With a normal drop down list, I would declare a variable to store the value and bind the DropDownList to that which would be fine but because I can have any number posting back, I don't know what to do.
I am happy to be told I'm doing this completely wrong but essentially I am trying to allow a list of dropdownlists to be presented on the front end.
Take a look at the BeginCollectionItem Html Helper. We use this with success on a daily basis for generating a collection of Html inputs (regardless of type):
https://github.com/danludwig/BeginCollectionItem
Nuget Package: https://www.nuget.org/packages/BeginCollectionItem/
I've managed to sort this so am posting for future queries on the subject:
I changed my class in the collection to this:
public class SelectConfigurableDataSyncOption : BaseConfigurableDataSyncOption
{
//Added this in to track the selected option
public string SelectedOptionValue { get; set; }
public Dictionary<string, string> OptionValue { get; set; }
}
Changed the ICollection to a List in my ViewModel:
public List<SelectConfigurableDataSyncOption> SelectConfigurableOptions { get; set; }
Then changed my .cshtml file to use a for loop instead of foreach:
#for (int i = 0; i < Model.SelectConfigurableOptions.Count; i++)
{
<div class="span6">
<h4>#Model.SelectConfigurableOptions[i].OptionText?</h4>
<p>#Html.DropDownListFor(m => Model.SelectConfigurableOptions[i].SelectedOptionValue, new SelectList(Model.SelectConfigurableOptions[i].OptionValue, "Key", "Value"), new { #class = "m-wrap span12" })</p>
</div>
}
This gave me access to Model.SelectConfigurableOptions[i].OptionValue which allows the value to post back to the controller.
Hi I have included nested class within a class to use it in a view but it doesnt show up the properties of that nested class below is the class, I want to use sku in the view :
View:
#model Nop.Web.Models.Catalog.CategoryModel
<div class="product-item">
<h2 class="product-title">
#Model.Name
</h2>
<div class="description">
**#Model.ProductVariantModels.Select(x => x.Sku)//doesnt works** // partial class productvariant
</div>
<div class="add-info">
#Model.Name <br/> #Model.FullDescription //values from class CategoryModel
</div>
</div>
Model:
public class CategoryModel : BaseEntityModel
{
public CategoryModel()
{
ProductVariantModels = new List<ProductVariantModel>();
}
public string Name { get; set; }
public string FullDescription { get; set; }
public string MetaKeywords { get; set; }
public string MetaDescription { get; set; }
public string MetaTitle { get; set; }
public IList<ProductVariantModel> ProductVariantModels { get; set; }
public class ProductVariantModel : BaseNopEntityModel
{
public string Name { get; set; }
public bool ShowSku { get; set; }
public string Sku { get; set; }
public string Description { get; set; }
}
}
ProductVariantModels is a List. You have to enumerate the List.
#foreach (var pvModel in Model.ProductVariantModels) {
#pvModel.Sku
}
If you're using HtmlHelpers, you have to use a for loop with an index rather than a foreach loop.
As explained really well here, the automatic model binding expects field input names to have a 'dot' notation like Property.SubProperty.SubSub... to match the instance properties when assigning -- but if you render them in a foreach loop they won't have the full expression, and thus won't output the full 'dot' notation.
Also see MVC 4 binding nested list of lists result
I can't seem to get the value of a select list to populate the value of the parent object.
I'm using a wrapper object to bind to so that I have access to the values needed for the SelectList as well as the object which needs the value.
I'm willing to bet I'm missing something basic but I can't find it.
I have these models:
public class Status
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual bool IsClosed { get; set; }
public override string ToString()
{
return Name;
}
}
public class Issue
{
public virtual int Id { get; protected set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual Status Status { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual DateTime UpdatedOn { get; set; }
}
And a FormViewModel (from NerdDinner examples) to contain the list of Statuses
public class IssueFormViewModel
{
public Issue Issue { get; set; }
public SelectList Statuses { get; set; }
public IssueFormViewModel(Issue issue, IList<Status> statuses)
{
Issue = issue;
Statuses = new SelectList(statuses, "Id", "Name", statuses[1].Id );
}
public IssueFormViewModel() { }
}
My Create Actions on the Controller look like...
public ActionResult Create()
{
IList<Status> statuses;
Issue issue = new Issue();
// NHibernate stuff getting a List<Status>
return View(new IssueFormViewModel(issue,statuses));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(IssueFormViewModel issueFormView)
{ // At this point issueFormView.Issue.Status == null
// ...
}
My Issue object is bound partially except the status using these names in the View:
<p>
<label for="Issue.Title">Title:</label>
<%= Html.TextBox("Issue.Title", Model.Issue.Title)%>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="Statuses">Status:</label>
<!-- also tried "Issue.Status" -->
<%= Html.DropDownList("Statuses", Model.Statuses)%>
<%= Html.ValidationMessage("Status", "*")%>
</p>
I tried the dropdown list on my computer and it works, you should make sure NHibernate is bringing back more than 1 item as your code is trying to set the selected item to be the second item.
Statuses = new SelectList(statuses, "Id", "Name", statuses[1].Id);
Remember that Lists a re zero based indexed.
As expected - it WAS something simple.
I changed the view to look like:
<label for="Status.Id">Status:</label>
<%= Html.DropDownList("Status.Id", Model.Statuses)%>
<%= Html.ValidationMessage("Status.Id", "*")%>
and the controller signature to take an "issue" (can't bind to a selectlist!)
[AcceptVerbs(HttpVerbs.Post)]
//public ActionResult Create(IssueFormViewModel issueFormView)
public ActionResult Create(Issue issueToAdd)
{
And in my Post-Create action I have an Issue with a Status. Albeit the status is invalid (it only contains the Id). So before commiting the Issue to the db, I set the issueToAdd like this:
issueToAdd.Status = (from p in GetAllStatuses()
where p.Id == issueToAdd.Status.Id
select p).First();
Edit: And it turns out I didn't even need to fetch a "proper" Status object. It's bound to Id and that's good enough.