Here is my model:
public string CustomerNumber { get; set; }
public string ShipMethod { get; set; }
public string ContactPerson { get; set; }
public string ShipToName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3{ get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
Here part of my view:
<table class="table table-condensed">
<thead>
<tr>
<td>Customer Number</td>
<td>Ship Method</td>
<td>Contact Person</td>
<td>Ship to Name</td>
<td>Address 1</td>
<td>Address 2</td>
<td>Address 3</td>
<td>City</td>
<td>State</td>
<td>Zip</td>
</tr>
</thead>
<tbody>
<tr>
<td>#Html.LabelFor(x => x.CustomerNumber, Model.CustomerNumber)</td>
<td>#Html.LabelFor(x => x.ShipMethod, Model.ShipMethod)</td>
<td>#Html.LabelFor(x => x.ContactPerson, Model.ContactPerson)</td>
<td>#Html.LabelFor(x => x.ShipToName, Model.ShipToName)</td>
<td>#Html.LabelFor(x => x.Address1, Model.Address1)</td>
<td>#Html.LabelFor(x => x.Address2, Model.Address2)</td>
<td>#Html.LabelFor(x => x.Address3, Model.Address3)</td>
<td>#Html.LabelFor(x => x.City, Model.City)</td>
<td>#Html.LabelFor(x => x.State, Model.State)</td>
<td>#Html.LabelFor(x => x.ZipCode, Model.ZipCode)</td>
</tr>
</tbody>
</table>
The data gets displayed in the view correctly, but when a post happens on my page, the data comes back null. I need the data in those LabelFors to be sent back to my controller, so I dont have to store it in a session. I thought MVC was supposed to bind to your model automagically with the labelfors. What am I doing wrong?
EDIT
I guess the reason I asked this question, is because I just moved from webforms to MVC, and I am pretty lost without the viewstate. I figured if the values in my model kept posting back to me from the view, I wouldn't have to store my model in a session object. On my page, I need to be able to persist my model during page cycles, so I can store my model data into some sql tables after the user clicks the save button. What are some options to persist your model in MVC?
It only binds input elements inside a form (because the browser posts these). Label's aren't POST'ed.
You can use HiddenFor.. since they are input elements:
#Html.HiddenFor(x => x.City)
..etc. You just can't use labels for sending back to the Controller.
LabelFor:
<label></label> <!-- Not POST'ed by the browser -->
HiddenFor:
<input type="hidden" /> <!-- POST'ed by the browser -->
MVC will work according to HTML standards which means it won't postback a label element.
Use a HiddenFor or TextboxFor with a readonly attribute.
But if you just display the values, why not putting them in a session before sending it to the page?
If you have like a wizard style form where you need to save changes inbetween steps before committing the data to a database for example your best bet would be to save the submitted values in a session which you read out again in the next step in the controller. Otherwise you can put hidden inputs on your form with the values but that is prone to manipulation by the user.
Like Simon and others have explained labels are not posted. A post request should be used when you want to change something. You don't need to store it in session if you're just viewing and editing your model.
In your view, you'll need a link to edit your model:
#Html.ActionLink("edit", "Edit", new {id = Model.CustomerNumber});
In your controller implement your Edit action method:
public ViewResult Edit(int customerNumber) {
var customer = _repository.Customers.FirstOrDefault(c => c.CustomerNumber == customerNumber);
return View(customer);
}
You'll need a view for the Edit action method and in it goes your form to post your update.
#using (Html.BeginForm()) {
<label>Contact Person:</label>
<input name="ContactPerson" value="#Model.ContactPerson" />
// the rest of your form
<input type="submit" value="Save" />
}
Implement the method to handle the post. Make sure to add the HttpPost attribute.
[HttpPost]
public ViewResult Edit(Customer customer) {
if (ModelState.IsValid) {
// Save your customer
return RedirectToAction("Index");
}
else {
return View(customer);
}
}
Hope this helps.
Related
I send from controller to view a list of objects, viewmodel is the object with some properties and pagedList, that need to be presented on page. And by pressing the button, this list need to be exported as file, that is, it need to go back to the controller and be processed there.
Model:
public class ProductsList : ListViewModel<Product>
{
public ProductsList(string prefix) : base(prefix){ }
public ProductsList(PagedList<Product> products)
{
List = products;
}
public int? ProductTypeFilter {get;set; }
public string ProductTypeFilterName {get; set;}
public string FilterBy { get; set; }
}
ListViewModel just contain PagedList.
My controller
[HttpPost]
public FileResult SaveAsFile(PagedList<Product> viewmodel)
{
...
}
And my view
#model MyProject.ViewModels.ProductsList
if (Model.List.Count > 0)
{
<table id="products_table">
<colgroup>
<col class="productType"/>
</colgroup>
<thead>
<tr>
<th >
Product type
</th>
</tr>
</thead>
<tbody>
#{ var i = 0; }
#foreach (var item in Model.List)
{
<tr>
<td onclick="window.location='#Url.Action("Details", new {id = item.Id})'">
<p>
#item.Type
</p>
</td>
}
</tr>
i++;
}
</tbody>
</table>
}
<form asp-action="SaveAsFile" enctype="multipart/form-data" method="post">
#Html.HiddenFor(m => list);
<input type="submit" value="Save as File"/>
</form>
I already have tried add to controller params tags [FromForm], [FromBody] (actually all available tags).
In view tried with hidden field in form, without it just with submit; put form on partial view; other forms: ajax, Html.ActionLink("Save as File", "SaveAsFile", new {Model}).
On debug mod Model.List has 21 items (but it can has more, like 2000 items), but when I press the button, viewmodel is creating newly.
Problem: viewmodel is creating newly and i cannot get back my full viewmodel to controller
I will be grateful for any help :)
You can set your ViewModel data in a Session variable when you send the data to your View from Controller method:
In order to setup your Session, you can follow this S.O answer
Once your Session is setup, then you can put your ViewModel in it like:
HttpContext.Session.SetObjectAsJson("ProductsList", productslist);
And then retrieve it in your POST method like this:
[HttpPost]
public FileResult SaveAsFile(PagedList<Product> viewmodel)
{
//Get your viewmodel here
var list = HttpContext.Session.GetObjectFromJson<ProductsList>("ProductsList");
}
You can also serialize your ViewModel and then send it your Controller method without using form:
Create an ActionLink:
#Html.ActionLink("Submit", "SaveAsFile", "Home", new { jsonModel= Json.Encode(Model.list) }, null)
And your Controller method:
public FileResult SaveAsFile(string jsonModel)
{
var serializer= new DataContractJsonSerializer(typeof(Model.Product));
var yourmodel= (Product)serializer.ReadObject(GenerateStreamFromString(jsonModel));
}
How do I get the entered values of textboxes to post to my controller with values. Thought of using arrays but have no idea where to start. Textboxes are dynamic due to each have counter number
#{
var counter = 0;
foreach (var addedProduct in Model)
{
counter++;
<tr class="default" data-ng-controller="deleteItemsController">
<td>#addedProduct.name</td>
<td>#addedProduct.price</td>
<td>
<input type="text" id="quantity_#counter" name="quantity_#counter"/>
</td>
</tr>
}
}
<tr>
<td><b>Total:</b>{{#ViewBag.total}}</td>
<td>
#using (Html.BeginForm("Index", "Payment"))
{
//how do i get quantity values//
<input type="submit" value="pay">
}
</td>
my model:
public class ProductVar
{
public int productID { get; set; }
public string name { get; set; }
public int price { get; set; }
public int quantity { get; set; }
public int total { get; set; }
}
I have not implemented method yet, trying to figure out how to post all values first.
You are pretty close. Read this blog post from Phil Haack and things will be clear how your input fields should be named. You will understand how model binding works in ASP.NET MVC as well.
Also you should place your <input> fields inside the HTML <form> or nothing will ever be sent to the server when this form gets submitted.
So start by modifying your view code so that you use strongly typed HTML helpers for generating your input fields. They will take car of properly naming them:
#using (Html.BeginForm("Index", "Payment"))
{
for (var i = 0; i < Model.Count; i++)
{
<tr class="default" data-ng-controller="deleteItemsController">
<td>#Html.DisplayFor(x => x[i].name)</td>
<td>#Html.DisplayFor(x => x[i].price)</td>
<td>
#Html.EditorFor(x => x[i].quantity)
</td>
</tr>
}
<tr>
<td>
<b>Total:</b>
#ViewBag.total
</td>
<td>
<input type="submit" value="pay" />
</td>
</tr>
}
Now you can have your controller action that will be triggered when the form is submitted:
[HttpPost]
public ActionResult Index(ProductVar[] model)
{
...
}
In this example only the quantity field of the model will be sent to the server because that's the only input field present inside the HTML form and so the only values sent to the server. If you want to bind the other values you might include them as hidden fields as well:
#Html.HiddenFor(x => x[i].name)
#Html.HiddenFor(x => x[i].price)
Of course in most cases this is bad practice because the user can manipulate those values. The best approach is to retrieve them from the db in your POST action using the same id that you used to retrieve them in the GET action that you used to render this form.
I have got this problem that I am having a difficulty to solve. I am creating a page where the user will be presented with a list of items (Product Types). Each item will have a dropdown list next to it so that the user can make appropriate selection to create a mapping. After making selection then the user submits the form, and the value will be written to the database.
The problem is that when it is submitted, I am not getting any values back. Specifically, 'Mappings' is empty in the model that is returned by the POST action. The GET action works fine. The following is the essence of what I have written:
Model:
public class ProductTypeMappingViewModel
{
//this is empty in the POST object
public List<ProductTypeMapping> Mappings { get; set; }
public ProductTypeMappingViewModel()
{
Mappings = new List<ProductTypeMapping>();
}
public ProductTypeMappingViewModel(string db)
{
//use this to populate 'Mappings' for GET action
//works fine
}
public void UpdateDB()
{
//to be called on the object
//returned from POST action
foreach(var mapping in Mappings)
{
//Mappings is always empty after POST
//Suppose to add to db
}
}
}
public class ProductTypeMapping
{
public string ProductTypeName { get; set; }
public int SelectedStandardProductTypeKey { get; set; }
public SelectList StandardProductTypes { get; set; }
public ProductTypeMapping()
{
StandardProductTypes = new SelectList(new List<SelectListItem>());
}
public int GetSelectedProductTypeKey() { //return selected key}
public string GetSelectedProductTypeName() { //return selected name}
}
View:
#model CorporateM10.Models.ProductTypeMappingViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<table class="table">
#foreach (var dept in Model.Mappings)
{
<tr>
<td>
#Html.DisplayFor(model => dept.ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
<div>
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
Any insight will be greatly appreciated.
foreach here causes select element in final HTML to have incorrect name attribute. Thus nothing is posted to the server. Replace this with for loop:
<table class="table">
#for (int i=0; i<Model.Mappings.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.Mappings[i].ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey, model.Mappings[i].StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
As #Andrei said the problem relies on the name attribute.
But to add a little bit to his answer, here's the parameter names in the request that the default model binder expects for your case.
Mappings[0].SelectedStandardProductTypeKey
Mappings[1].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
...
Without any breaks in the numbering, i.e.:
Mappings[0].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
Won't work because of the missing Mapping[1]...
When you use the dropdown helper like this:
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
It generates an input with name="SelectedStandardProductTypeKey" (you need it to be Mappings[0].SelectedStandardProductTypeKey)
If you use a for loop and use the dropdown helper like this:
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey
You'll get the input with the correct name.
Any parameter in the request for which the model binder cannot find a property in the model, it will ignore, that's why the Mappings property is null in your case.
Here are two great resource that explain all this (and that provide alternative ways to represent collections that might be useful if you can't a the for loop to generate a numbered index without breaks):
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
I have an MVC view
<%# Page Language="C#" MasterPageFile="PathToMaster" Inherits="System.Web.Mvc.ViewPage<ModelData>" %>
and I have a form with HTML markup for a set of checkboxes:
<label for="MyCheckbox">Your choice</label>
<input type="checkbox" id="Option1" class="checkbox" name="MyCheckbox" value="Option one" />
<label for="Option1">Option one</label><br />
<input type="checkbox" id="Option2" class="checkbox" name="MyCheckbox" value="Option two" />
<label for="Option2">Option two</label><br />
and I have a controller-action pair
class MyController : Controller {
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult RequestStuff( ModelData data )
{
}
}
and that action is invoked when the form is submitted.
How do I map the checkboxes onto members of ModelData (and what members I have to add to ModelData) so that when the form is submitted data stores information on which checkboxes are checked?
OK, this one will be for MVC3, but - save for syntax changes - should work in MVC2 too. The approach is essentially the same.
First of all, you should prepare an appropriate (view)model
public class MyViewModel
{
[DisplayName("Option 1")]
public bool Option1 { get; set; }
[DisplayName("Option 2")]
public bool Option2 { get; set; }
}
Then you pass this model to the view you're showing (controller):
public ActionResult EditMyForm()
{
var viewModel = new MyViewModel()
return View(viewModel);
}
with the form:
#model MyViewModel
#using( Html.BeginForm())
{
#Html.Label("Your choice")
#Html.LabelFor(model => model.Option1) // here the 'LabelFor' will show you the name you set with DisplayName attribute
#Html.CheckBoxFor(model => model.Option1)
#Html.LabelFor(model => model.Option2)
#Html.CheckBoxFor(model => model.Option2)
<p>
<input type="submit" value="Submit"/>
</p>
}
Now here the HTML helpers (all the CheckBoxFor, LabelFor, EditorFor etc) allow to bind the data to the model properties.
Now mind you, an EditorFor when the property is of type bool will give you the check-box in the view, too. :)
And then, when you submit to the controller, it will auto-bind the values:
[HttpPost]
public ActionResult EditMyForm(MyViewModel viewModel)
{
//And here the view model's items will be set to true/false, depending what you checked.
}
First you define SelectList for Options. This will be used just to render checkboxes
public IList<SelectListItem> OptionsSelectList { get; set; }
Than, you define model that will hold value of single chosen option after post
public class ChooseOptionViewModel
{
public int OptionIdentifier { get; set; } //name or id
public bool HasBeenChosen { get; set; } //this is mapped to checkbox
}
Then IList of those options in ModelData
public IList<ChooseOptionViewModel> Options { get; set; }
And finally, the view
#for (int i = 0; i < Model.OptionsSelectList.Count(); i++)
{
<tr>
<td class="hidden">
#Html.Hidden("Options[" + i + "].OptionIdentifier", Model.OptionsSelectList[i].Value)
</td>
<td>
#Model.OptionsSelectList[i].Text
</td>
<td>
#Html.CheckBox("Options[" + i + "].HasBeenChosen", Model.Options != null && Model.Options.Any(x => x.OptionIdentifier.ToString().Equals(Model.OptionsSelectList[i].Value) && x.HasBeenChosen))
</td>
</tr>
}
After post, you just inspect Options.Where(x => x.HasBeenChosen)
This is full-functional, and it will allow you to redisplay view when validation errors occur, etc. This seems a bit complicated, but I haven't come up with any better solution than this.
The base functionality I wish to achive is that the contents of a table are updated when a dropdownlist item is selected. This will update when the user makes a new selection and retrieve new information from the database and repopulate the table.
It's also worth noting that the DropDownListFor that I want the .change() to work with is not contained within the AjaxForm but appears elsewhere on the page (admittedly in another form)
To achieve this I looked at this question: Rendering partial view dynamically in ASP.Net MVC3 Razor using Ajax call to Action which does a good job of going part the way of what I want to do.
So far, I have a controller method which handles populating a customized viewmodel for the partial view:
[HttpPost]
public ActionResult CompanyBillingBandDetails(int id = 0)
{
var viewModel = new BillingGroupDetailsViewModel();
var billingGroupBillingBands =
_model.GetAllRecordsWhere<BillingGroupBillingBand>(x => x.BillingGroupId == id).ToList();
foreach (var band in billingGroupBillingBands)
{
viewModel.BillingBands.Add(band.BillingBand);
}
return PartialView("BillingGroupDetailsPartial", viewModel);
}
The ViewModel I wish to populate each call:
public class BillingGroupDetailsViewModel
{
public List<BillingBand> BillingBands { get; set; }
}
The strongly typed model I'm using as a model for the partial view
public class BillingBandsObject
{
public int BillingBandId { get; set; }
public int RangeFrom { get; set; }
public int RangeTo { get; set; }
public Decimal Charge { get; set; }
public int BillingTypeId { get; set; }
public bool Delete { get; set; }
}
The partial view it populates and returns:
#model xxx.xxx.DTO.Objects.BillingBandsObject
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeFrom)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeTo)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.Charge)
</td>
</tr>
The Razor code for this section of the page:
<table>
<thead>
<tr>
<th>
Range From
</th>
<th>
Range To
</th>
<th>
Charge
</th>
</tr>
</thead>
<tbody>
#using (Ajax.BeginForm("CompanyBillingBandDetails", new AjaxOptions() { UpdateTargetId = "details", id = "ajaxform" }))
{
<div id="details">
#Html.Action("CompanyBillingBandDetails", new { id = 1 })
</div>
}
</tbody>
</table>
and finally the function I lifted almost straight from Darin's answer:
$(function () {
$('#billinggrouplist').change(function () {
// This event will be triggered when the dropdown list selection changes
// We start by fetching the form element. Note that if you have
// multiple forms on the page it would be better to provide it
// an unique id in the Ajax.BeginForm helper and then use id selector:
var form = $('#ajaxform');
// finally we send the AJAX request:
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#details').html(result);
}
});
});
});
At the moment I have fought through a number of errors, currently I am faced with :
"Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
However, i feel my understanding of the problem as a whole may be lacking.
Any help appreciated!
This error means that there was an exception while rendering your child view. Probably something related to your data, ie. NulLReferenceException.
Just attach your debugger and set to to break when an exception is thrown.