Complex model return null in action? - c#

A strongly typed view has a <form> and on submitting <form> I am expecting it should return updated value in model but it is not the case. I couldn't figure out what it is happening.
View Mode:
public class HomeLoanViewModel
{
public Lead_Options leadOption { get; set; }
public Lead HomeLoanLead {get;set;}
public HomeLoanViewModel()
{
this.leadOption= new Lead_Options();
this.HomeLoanLead= new Lead();
}
}
and Lead class inside ViewModel is as below:
public partial class Lead
{
public string Lead_id { get; set; }
public string Income { get; set; }
[NotMapped]
public List<SelectListItem> Income_Binder
{
get
{
return new List<SelectListItem>()
{
new SelectListItem(){ Value="", Text="Please select...", Selected=true },
new SelectListItem(){ Value="$0 - $30,000", Text="$0 - $30,000" },
new SelectListItem(){ Value="$30,001 - $40,000", Text="$30,001 - $40,000" },
new SelectListItem(){ Value="$40,001 - $50,000", Text="$40,001 - $50,000"},
new SelectListItem(){ Value="$50,001 - $60,000", Text="$50,001 - $60,000"},
new SelectListItem(){ Value="$60,001 - $70,000", Text="$60,001 - $70,000"},
new SelectListItem(){ Value="$70,001 - $80,000", Text="$70,001 - $80,000"},
new SelectListItem(){ Value="$80,001 - $90,000", Text="$80,001 - $90,000"},
new SelectListItem(){ Value="$90,001 - $100,000", Text="$90,001 - $100,000"},
new SelectListItem(){ Value="$100,001 - $150,000", Text="$100,001 - $150,000"},
new SelectListItem(){ Value="$150,000+", Text="$150,000+"}
};
}
}
}
Controller & Action:
public class HomeLoanController : ParentController
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Submit(FormCollection form)
{
Session["loan_type"] = form.GetValue("Type").AttemptedValue;
Session["m_I_need_to"] = form.GetValue("LoanPurpose").AttemptedValue;
Session["m_The_property_is_a"] = form.GetValue("LoanFor").AttemptedValue;
Session["m_My_time_frame_to_buy_or_refinance_is"] = form.GetValue("LoanTimeFrame").AttemptedValue;
// return View("LeadContact", new HomeLoanViewModel());
return RedirectToAction("LeadContact");
}
[HttpGet]
public ActionResult LeadContact(HomeLoanViewModel model)
{
return View(model);
}
}
View:
#using HMC.Common.Utilities;
#model HMC.Common.ViewModel.HomeLoanViewModel
#{
ViewBag.Title = "LeadContact";
Layout = "~/Views/_Base.cshtml";
}
<form class="wrapper minheight homeloan-form border-top" id="homeloan-form" method="post" action="" novalidate="novalidate">
<label>Total Annual Income</label>
#Html.DropDownListFor(m => m.HomeLoanLead.Income, Model.HomeLoanLead.Income_Binder, new { required = "", aria_required = "true", name = "interestratetype" })
#Html.HiddenFor(m => m.HomeLoanLead.Income)
<div class="formnav row">
<button class="btn btn-large">Show Top Home Loans <i class="fa fa-chevron-right"></i></button>
</div>
</form>
EDIT:
From previous page/ view request come to Submit Action of controller, then it redirect to LeadContact. This is view where I m passing model and post back want to receive updated values.

This type of issue is common when using ViewModels or strongly typed views with models. Here's why...
When a post back happens the model binder eventually gets called prior to the controller getting called for the post back. If the controller is accepting a class (model or viewmodel) as an input parameter, then MVC will first create an instance of the class using the null constructor. Then the model binder will take the HTML Query string (name value pairs) and attempt to fill in the model/view model properties based on the name value pairs. If the binder cannot find the proper Name matches it simply doesn't do anything (which is proper behavior), as a result the symptoms of this are "Hey I missing data that I know I posted from the view".
The best way to debug this is 1) Set a breakpoint on the controller's action method. 2) Go to the client side and press F12 to view the network post. 3) Start the post back and notice the name value pairs sent from the browser. Upon breaking at the controller entry go do Debug/Windows/Locals and look at the model portion of the controller parameters. You will see the same thing there as in the network side (proof they all made it back). Now look at the typed value, if anything is not there that should be there, it is simply because the post back and the model (Name values) did not match.
90% of the time the root cause is the first parameter of the DisplayFor, HiddenFor or other "For" elements is not pointing to the correct thing to be returned. This is why I like to use ViewModels..
Given:
public class HomeLoanViewModel
{
public Lead_Options leadOption { get; set; }
public Lead HomeLoanLead {get;set;}
public String SelectedValue {get;set;}
public HomeLoanViewModel()
{
this.leadOption= new Lead_Options();
this.HomeLoanLead= new Lead();
}
public void Post(){
//do all your post logic here
if(SelectedValue > XYZ) //do this
}
}
I would create the controller signature like this:
[HttpPost]
public ActionResult Submit(HomeLoanViewModel vm)
{
if (ModelState.IsValid){
vm.Post();
return View(vm);
}
return View(vm);
}
I would create the view like this:
#using HMC.Common.Utilities;
#model HMC.Common.ViewModel.HomeLoanViewModel
#{
ViewBag.Title = "LeadContact";
Layout = "~/Views/_Base.cshtml";
}
#Using Html.BeginForm(){
<label>Total Annual Income</label>
#Html.DropDownListFor(m => m.HomeLoanLead.SelectedValue, Model.HomeLoanLead.Income_Binder, new { required = "", aria_required = "true"})
<input type="submit/>
}
<div class="formnav row">
<button class="btn btn-large">Show Top Home Loans <i class="fa fa-chevron-right"></i></button>
</div>
Finally one other pointer, no sense in the SelectListItems Text and Value being the same, you could just as easily have set the value to in Int such as [1,2,3,4,5] for each of the choices. Look at the value as the identifier of the text field, this works especially well with databases that have ID for looking up field values.

Related

get a variable passed to my controller from a textbox

I have a simple model I am using for a search page to do some validation:
public class Search {
[Required]
[DisplayName("Tag Number")]
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Tag must be a number")]
public int HouseTag { get; set; }
i then have a simple view with a textbox and a submit button:
#model Search
#{
Layout = "~/_Layout.cshtml";
}
#using (Html.BeginForm("Search", "Inquiry", FormMethod.Get)){
#Html.LabelFor(m =>m.HouseTag)
#Html.TextBoxFor(m=>m.HouseTag, new { type = "Search", autofocus = "true", style = "width: 200px", #maxlength = "6" })
<input type="submit" value="Search" id="submit"/>
my controller is expecting a parameter of an id:
[HttpGet]
public ActionResult Search(int id){
ViewBag.Tag = id;
return View();
}
when i execute it with a number i get a null value being passed to the controller, causing things to blow up. I am using the model to control some of the properties of the search box for validation. I used to just have #Html.TextBox and it returned fine, but now that ive added the model, it doesnlt return anything.
You can set your parameter to a type of Search and then access the property in your action
[HttpGet]
public ActionResult Search(Search model){
ViewBag.Tag = model.HouseTag;
return View();
}
If it were me I'd make this a HttpPost or create a seperate action for this form so I wouldn't see the HouseTag text in the URL..
#using (Html.BeginForm("Search", "Inquiry", FormMethod.Post))
{
#Html.LabelFor(m => m.HouseTag)
#Html.TextBoxFor(m => m.HouseTag, new { type = "Search", autofocus = "true", style = "width: 200px", #maxlength = "6" })
<input type="submit" value="Search" id="submit" />
}
[HttpPost]
public ActionResult Search(Search model){
ViewBag.Tag = model.HouseTag;
return View();
}
You are expecting a parameter named id and you are passing HouseTag as the name of that parameter you should rename id to houseTag inside the Search method.
There's a couple of things going on here. First you are going to want to split your Get and Post actions. Also forms are only used in conjunction with POST's. You also don't need to name your action or controller unless you are sending the post to a different controller or action then the GET.
This is the get. It renders the form on the page. You don't need to put [HttpGet] on there, it is the default.
public ActionResult Search()
{
return View();
}
The following is going to post the form back to the server. the model binder will wire up the html form fields with your view model. since you have validators on the view model, you'll want to check that the model state is valid and re-show the view with the associated errors. You will need to add an #Html.ValidationMessageFor(...) into your view so that you actually see those errors.
[HttpPost]
public ActionResult Inquiry(Search search)
{
if (!ModelState.IsValid)
{
return View(search);
}
//so something with your posted model.
}

MVC4 model binding - Passing Custom View Models & values from view to controller

I have a strongly typed view with the following model.
public class ProductViewModel
{
public Product Product { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
public class ProductOptionWithValues
{
public ProductOption ProductOption;
public List<AllowedOptionValue> AllowedOptionValues;
}
I'm using this Model To populate a form where a user can select the options they want for a product.
This is the view.
#model AsoRock.Entities.ViewModels.ProductViewModel
#{
ViewBag.Title = "Details";
}
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h3>
#Html.DisplayFor(model => model.Product.ProductName)
----> #Html.DisplayFor(model => model.Product.Price)
</h3>
<br/>
foreach (var item in Model.ProductOptionsWithValues)
{
<b>#Html.DisplayFor(modelItem => item.ProductOption.Option.OptionName)</b>
<br/>
#Html.DropDownListFor(m => m.ProductOptionsWithValues,
new SelectList(item.AllowedOptionValues,
"Id", "DisplayString",
item.AllowedOptionValues.First().Id))
<br/>
}
<input type="submit" value="Add to cart" />
}
In my controller I am trying to pass the model back. When I set a break point in the controller it hits it but the Product view model is empty, any ideas how I can get the values that are selected in the view back in to my controller?
[HttpPost]
public ActionResult Details(ProductViewModel ProductViewModel)
{
return View();
//return View();
}
As mentioned in the comments, you need to change the name of the viewmodel parameter from ProductViewModel to something else e.g.
[HttpPost]
public ActionResult Details(ProductViewModel viewModel)
{
}
Now it's very odd that the viewModel param is not set to an instance of the class. The MVC model binder will still create an instance of ProductViewModel even if none of it's properties are set to anything. You're not using a custom model binder by any chance?
Also I would very strongly suggest that your viewmodel class does not have a Product property. Instead, create properties in the viewmodel specifically for the Product properties you intend to use e.g.
public class ProductViewModel
{
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
Using Product in the viewmodel sort of defeats the point of having a viewmodel. The viewmodel should contain only the bare minimum that the view needs. Including Product means the viewmodel is now bloated with extra data it does not use/need.
EDIT:
In your shoes, I would strip down the view itself, using only little bits of the viewmodel, and POST to the controller to see what happens. If the viewmodel calss is not NULL, go back to the view and add another bit back. Keep doing this until the viewmodel is NULL again. Doing this bit by bit should help.

MVC 4 ViewModel not being sent back to Controller

I can't seem to figure out how to send back the entire ViewModel to the controller to the 'Validate and Save' function.
Here is my controller:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
Here is the form in the view:
<li class="check">
<h3>Transaction Id</h3>
<p>#Html.DisplayFor(m => m.Transaction.TransactionId)</p>
</li>
<li class="money">
<h3>Deposited Amount</h3>
<p>#Model.Transaction.Amount.ToString() BTC</p>
</li>
<li class="time">
<h3>Time</h3>
<p>#Model.Transaction.Time.ToString()</p>
</li>
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
When i click on submit, the conroller contains the correct value of the walletAddress field but transaction.Transaction.Time, transaction.Transaction.Location, transaction.Transaction.TransactionId are empty.
Is there a way i could pass the entire Model back to the controller?
Edit:
When i dont even receive the walletAddress in the controller. Everything gets nulled!
When i remove this line alone: #Html.HiddenFor(m => m.Transaction.TransactionId);
it works and i get the Token property on the controller, but when i add it back, all the properties of the transaction object on the controller are NULL.
Here is the BitcoinTransactionViewModel:
public class BitcoinTransactionViewModel
{
public string Token { get; set; }
public string WalletAddress { get; set; }
public BitcoinTransaction Transaction { get; set; }
}
public class BitcoinTransaction
{
public int Id { get; set; }
public BitcoinTransactionStatusTypes Status { get; set; }
public int TransactionId { get; set; }
public decimal Amount { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Any ideas?
EDIT: I figured it out, its in the marked answer below...
OK, I've been working on something else and bumpend into the same issue all over again.
Only this time I figured out how to make it work!
Here's the answer for anyone who might be interested:
Apparently, there is a naming convention. Pay attention:
This doesn't work:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
This works:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel **RedeemTransaction**)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { **RedeemTransaction** = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
In other words - a naming convention error! There was a naming ambiguity between the Model.Transaction property and my transaction form field + controller parameter. Unvelievable.
If you're experiencing the same problems make sure that your controller parameter name is unique - try renaming it to MyTestParameter or something like this...
In addition, if you want to send form values to the controller, you'll need to include them as hidden fields, and you're good to go.
The signature of the Send method that the form is posting to has a parameter named transaction, which seems to be confusing the model binder. Change the name of the parameter to be something not matching the name of a property on your model:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}
Also, remove the htmlAttributes parameter from your BeginForm call, since that's not doing anything useful. It becomes:
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
Any data coming back from the client could have been tampered with, so you should only post back the unique ID of the transaction and then retrieve any additional information about it from your data source to perform further processing. You'll also want to verify here that the user posting the data has access to the specified transaction ID since that could've been tampered with as well.
This isn't MVC specific. The HTML form will only post values contained within form elements inside the form. Your example is neither inside the form or in a form element (such as hidden inputs). You have to do this since MVC doesn't rely on View State. Put hidden fields inside the form:
#Html.HiddenFor(x => x.Transaction.Time)
// etc...
Ask yourself though.. if the user isn't updating these values.. does your action method require them?
Model binding hydrates your view model in your controller action via posted form values. I don't see any form controls for your aforementioned variables, so nothing would get posted back. Can you see if you have any joy with this?
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
#Html.Hidden("Time", Model.Transaction.Time)
#Html.Hidden("Location", Model.Transaction.Location)
#Html.Hidden("TransactionId", Model.Transaction.TransactionId)
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
Try to loop with the folowing statement not with FOREACH
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Try Form Collections and get the value as. I think this may work.
public ActionResult Send(FormCollection frm)
{
var time = frm['Transaction.Time'];
}
Put all fields inside the form
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
and make sure that the model
BitcoinTransactionViewModel
included in view or not?
Can you just combine those 2 models you have? Here's how I do it with one model per view...
1. I use Display Templates from view to view so I can pass the whole model as well as leave data encrypted..
2. Setup your main view like this...
#model IEnumerable<LecExamRes.Models.SelectionModel.GroupModel>
<div id="container">
<div class="selectLabel">Select a Location:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
3. Create a DisplayTemplates folder in shared. Create a view, naming it like your model your want to pass because a DisplayFor looks for the display template named after the model your using, I call mine GroupModel. Think of a display template as an object instance of your enumeration. Groupmodel Looks like this, I'm simply assigning a group to a button.
#model LecExamRes.Models.SelectionModel.GroupModel
#using LecExamRes.Helpers
#using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.GroupKey)
#Html.EncryptedHiddenFor(model => model.GroupName)
<p>
<input type="submit" name="gbtn" class="groovybutton" value=" #Model.GroupKey ">
</p>
</div>
}
4. Here's the Controller.
*GET & POST *
public ActionResult Index()
{
// Create a new Patron object upon user's first visit to the page.
_patron = new Patron((WindowsIdentity)User.Identity);
Session["patron"] = _patron;
var lstGroups = new List<SelectionModel.GroupModel>();
var rMgr = new DataStoreManager.ResourceManager();
// GetResourceGroups will return an empty list if no resource groups where found.
var resGroups = rMgr.GetResourceGroups();
// Add the available resource groups to list.
foreach (var resource in resGroups)
{
var group = new SelectionModel.GroupModel();
rMgr.GetResourcesByGroup(resource.Key);
group.GroupName = resource.Value;
group.GroupKey = resource.Key;
lstGroups.Add(group);
}
return View(lstGroups);
}
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Index(SelectionModel.GroupModel item)
{
if (!ModelState.IsValid)
return View();
if (item.GroupKey != null && item.GroupName != null)
{
var rModel = new SelectionModel.ReserveModel
{
LocationKey = item.GroupKey,
Location = item.GroupName
};
Session["rModel"] = rModel;
}
//So now my date model will have Group info in session ready to use
return RedirectToAction("Date", "Home");
}
5. Now if I've got alot of Views with different models, I typically use a model related to the view and then a session obj that grabs data from each model so in the end I've got data to submit.
The action name to which the data will be posted should be same as the name of the action from which the data is being posted. The only difference should be that the second action where the data is bein posted should have [HttpPost] and the Posting method should serve only Get requests.

How do I keep the clicked state of a checkbox in asp.net

I'm really having problems with keeping the state of my checkbox in my mvc4 application. I'm trying to send its value down to my controller logic, and refresh a list in my model based on the given value, before I send the model back up to the view with the new values. Given that my checkbox is a "show disabled elements in list" type function, I need it to be able to switch on and off. I've seen so many different solutions to this, but I can't seem to get them to work :(
Here's a part of my view:
#model MyProject.Models.HomeViewModel
<div class="row-fluid">
<div class="span12">
<div class="k-block">
<form action="~/Home/Index" name="refreshForm" method="POST">
<p>Include disabled units: #Html.CheckBoxFor(m => m.Refresh)</p>
<input type="submit" class="k-button" value="Refresh" />
#* KendoUI Grid code *#
</div>
</div>
HomeViewModel:
public class HomeViewModel
{
public List<UnitService.UnitType> UnitTypes { get; set; }
public bool Refresh { get; set; }
}
The HomeViewController will need some refactoring, but that will be a new task
[HttpPost]
public ActionResult Index(FormCollection formCollection, HomeViewModel model)
{
bool showDisabled = model.Refresh;
FilteredList = new List<UnitType>();
Model = new HomeViewModel();
var client = new UnitServiceClient();
var listOfUnitsFromService = client.GetListOfUnits(showDisabled);
if (!showDisabled)
{
FilteredList = listOfUnitsFromService.Where(unit => !unit.Disabled).ToList();
Model.UnitTypes = FilteredList;
return View(Model);
}
FilteredList = listOfUnitsFromService.ToList();
Model.UnitTypes = FilteredList;
return View(Model);
}
You return your Model to your view, so your Model properties will be populated, but your checkbox value is not part of your model! The solution is to do away with the FormCollection entirely and add the checkbox to your view model:
public class HomeViewModel
{
... // HomeViewModel's current properties go here
public bool Refresh { get; set; }
}
In your view:
#Html.CheckBoxFor(m => m.Refresh)
In your controller:
[HttpPost]
public ActionResult Index(HomeViewModel model)
{
/* Some logic here about model.Refresh */
return View(model);
}
As an aside, I can't see any reason why you'd want to add this value to the session as you do now (unless there's something that isn't evident in the code you've posted.

No post values when using html helpers

I have a problem in mvc3. Im not sure this is specifically mvc3 but, Im currently using that with razor engine. Anyway, the problem that I am facing is, I have a form, I use the TextBoxFor, CheckBoxFor, etc. to render it. The rendering is working flawless, except, when I try to post the data, I basically post an empty form with null values.
Here is my model:
public class SendReplyPmForm : PM
{
public new string Text { get; set; }
public bool IsOriginalDelete { get; set; }
public int ReplyNr { get; set; }
public string ReceiverName { get; set; }
}
I have an extra viewmodel layer between the view and the model and it contains an extra paramater regarding this model
public class IndexViewModel
{
public SendReplyPmForm SendReplyPmForm { get; set; }
...
here is my view
#using (Html.BeginForm("SendReply", "Pm", FormMethod.Post, new { id = "formSendMsg" }))
{
#Html.TextBoxFor(model => model.SendReplyPmForm.ReceiverName, new { id = "ReceiverName" })
<span id="spanChkText">Delete Original Message: #Html.CheckBoxFor(model => model.SendReplyPmForm.IsOriginalDelete, new { id = "chkIsOriginalDelete", value = 1 })</span>
#{Html.RenderPartial("~/Areas/Forums/Views/Shared/Toolbar.cshtml");}
<span class="spanLabel">Message</span>
#Html.TextAreaFor(model => model.SendReplyPmForm.Text, new { id = "Text", rows = "10", cols = "65" })
#{Html.RenderPartial("temp.cshtml");}
#Html.HiddenFor(model => model.SendReplyPmForm.ReplyNr, new { id = "inputReplyNr", value = 0 })
<input type="submit" value="Send" />
}
and here i have the controller
[HttpPost]
public ActionResult SendReply(SendReplyPmForm SendReplyForm) {
var ViewModel = new IndexViewModel();
.
.
.
return View("Index", ViewModel);
}
The strange thing is, if I use pure HTML instead of the Html helpers, then the post is going smoothly without any problem.
I read this article (ASP.NET MVC’s Html Helpers Render the Wrong Value!) before I post this, but im not quite sure, that I have the same issue. (f.e.: I dont have action in my controller, that has the same name), but the fact that is also working with pure Html that makes me think.
Do you guys have any idea, how can I use this form with the Html helpers?
I believe your problem is the same as here, so you need to add a prefix:
[HttpPost]
public ActionResult SendReply([Bind(Prefix="SendReplyPmForm")]SendReplyPmForm SendReplyForm)
{
...
}

Categories