I have a list of Objects which I want to modify on my View, I have built a View Model to contain the list of objects:
public class TrainerListViewModel
{
public List<Trainer> Trainers { get; set; }
}
and I send a list of Trainers to the view from the controller:
public virtual ActionResult Social()
{
var Trainers = new TrainerListViewModel();
Trainers.Trainers = (from t in _db.Trainers select t).ToList();
return View(Trainers);
}
and here is my view:
#model XStreamingFitness.ViewModels.TrainerListViewModel
#using (Html.BeginForm("Social", "Participant", FormMethod.Post))
{
for (int i = 0; i < Model.Trainers.Count; i++)
{
<div class="formSep">
<div class="editor-label">
#Html.LabelFor(m => m.Trainers[i].permissionPostFacebook)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.Trainers[i].permissionPostFacebook)
#Html.ValidationMessageFor(m => m.Trainers[i].permissionPostFacebook)
</div>
</div>
}
<input type="submit" value="Save Settings" name="Submit" />
}
now here is the POST controller method:
[HttpPost]
public virtual ActionResult Social(TrainerListViewModel Trainers)
{
return RedirectToAction("Profile");
}
but everytime I submit, the Trainers model is empty and I am not sure why this could be happening.
This question has been asked before here at SO I suggest you check mvc3 submit model empty, its the same principals as the problem you are having.
See the snippet from the post.
I see people writing the following lambda expression modelItem =>
item.SomeProperty in their views very often and asking why the model
binder doesn't correctly bind collection properties on their view
models.
This won't generate proper name for the checkbox so that the default
model binder is able to recreate the Settings collection. I would
recommend you reading the following blog post to better understand the
correct format that the model binder expects.
-By Darin Dimitrov
It is to do with the way you are building your form he goes to suggest you use Property Editor Templates for the Trainer object.
This should then work.
Hope it helps
Related
Please see the models below:
public class Apple //: Fruit
{
public string Description { get; set; }
public int Id { get; protected set; }
}
public class AppleModel
{
public int Id { get; set; }
public string Description { get; set; }
}
and the controller below:
[HttpPost]
public ActionResult Index(Apple apple)
{
return View();
}
[HttpGet]
public ActionResult Index()
{
var AppleModel = new AppleModel();
AppleModel.Id = 1;
AppleModel.Description = "Apple";
var Apple = AutoMapper.Mapper.Map<Apple>(AppleModel);
return View("View1",Apple);
}
and the view below:
#model PreQualification.Web.Controllers.Apple
#{
ViewBag.Title = "View1";
}
<h2>View1</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>AppleModel</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Id)
<div class="col-md-10">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Apple.Id is 0 in the HttpPost method because it is a protected variable in the model. Is there anyway around this?
The reason I ask is because I am trying to use this as a model: https://github.com/nhibernate/NHibernate.AspNet.Identity/tree/master/source/NHibernate.AspNet.Identity and the id contained in the superclass is protected.
There are a couple ways around this, including a child class that implements a public whose setter applies the value to the protected id field.
However these are bandaids. Generally these problems are encountered because of a difference in how people view models. Either it is a reusable Data Transfer Object or it is not.
In the world where it is not, you have to shoehorn business objects into bindable models and always run into these weird problems.
In the world where they are, they are custom tailored to fit the data needs and are mapped into business objects with something like an automapper. More importantly, by making a model to fit this request, you protect against attacks on accidentally exposed parameters.
If the makeshift business object has public properties that change your behavior, they can be exploited by sending additional parameters back with the post request.
I know this does not specifically answer your question, but following the path where models are not DTO's is probably not the right answer either.
As elaborated by peewee_RotA, it's better to separate out the conceptual differences between a model used for a view and a domain model that will actually perform an action or is used to directly perform an action. To that end, you need a view model such as Apple
public class Apple
{
public string Description { get; set; }
public int Id { get; set; }
}
In your post action, you'd have;
[HttpPost]
public ActionResult Index(Apple apple)
{
// translate your view model to your domain model
AppleModel model = new AppleModel(apple.Id, apple.Description);
model.DoStuff();
}
In terms of NHiberhate though, it's not easy to instantiate the required object with the view model's ID and this is the crux of your issue I think. You're trying to use your domain model as a view model and the domain model is locked down...legitimately. It's job is to control how IdentityUser and the like are instantiated as they're meant to be generated by the associated factory classes, not MVC's model binder.
To that end, leave your view model as simple as possible and leverage NHibernate's factory classes to create the necessary Identity objects by looking up the ID. This link may shed some light on how to look up the user ID passed in by Apple.Id.
EDIT
I've done a little more digging on looking up the entity in NHibernate and the following post seems to do the basics. Does this get what you need?
I'm just wondering what does the "For" in the .LabelFor or .EditorFor in the mvc html helper extension mean? I understand the parameter takes in a lambda expression but I can't work out what the "For" means?
Here is my simple cshtml file so you can see what I am looking at
#model MvcTest.Models.Update
#{
ViewBag.Title = "Edit";
}
<h1>Update</h1>
#using (Html.BeginForm()) {
#Html.ValidationSummary()
<fieldset>
<legend>Update profile</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Html.LabeFor() returns an HTML label element and the property name of the property that is represented by the specified expression.
For means to bind the control with that specific Model Property in strongly typed views.
For Example:
I have a model with only two properties.
public class MyModel
{
public string Name {get;set;}
public int Id { get; set;}
}
Now we create a view which is strongly typed:
#model AppNameSpace.Models.MyModel
#using (Html.BeginForm("MyAction","My",FormMethod.Post))
{
#Html.TextBoxFor(m=>m.Name)
#Html.HiddenFor(m=>m.Id)
<input type="submit" name="Save" value="Save"/>
}
Now in the from the model object will be posted with values of form elements in model properties.
public class MyController : Controller
{
[HttpGet]
public Action Result SomeAction()
{
return View();
}
[HttpPost]
public Action Result SomeAction(MyModel model)
{
string name = model.Name;// here you will have name value which was entered in textbox
return View();
}
}
If i say:
Html.TextBoxFor(x=>x.Name)
now when the from will be posted at the action with model , the text box value will be posted in the Name property whatever we have entered in the textbox. This is how strongly typed views work in asp.net mvc.
The same is the case for other Html helpers like Html.LabelFor(), Html.HiddenFor they are mostly used in strongly typed views, to reflect the values of from elements in action on form post of model.
For furthers detailed study,you might want to read more about Html Helpers here:
http://stephenwalther.com/archive/2009/03/03/chapter-6-understanding-html-helpers
http://www.dotnet-tricks.com/Tutorial/mvc/N50P050314-Understanding-HTML-Helpers-in-ASP.NET-MVC.html
https://www.simple-talk.com/dotnet/asp.net/writing-custom-html-helpers-for-asp.net-mvc/
The for is referring to the property that you're creating it on. #Html.LabelFor(model => model.Title) is a label for the Title field on the model. More specifically, it's for the Expression that you provide it.
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.
This is what I've had in mind but of course it doesn't work.
#{
var textBoxData = form.find('input[name="textboxList"]').val();
}
<input type="button" value="Add" title="Add" onclick="location.href='#Url.Action("Create_Add", "Controller", new { textboxList = textBoxData })'" />
How should I pass this? Controller action name and parameter are correct. Just that I don't know how to get the value entered in textbox...
I have trouble with saving a form within a form, so someone suggested this solution. Proxy code would be:
<firstForm>
textboxfor Name
dropdownfor DType
If DTypeDDL value is "List" then
<secondForm>
textboxfor nameOfItem
submitSecondForm (using that method i mentioned above)
</secondForm>
End If
submitFirstForm
</firstForm>
I've been trying to save 2 forms for quite a while now but no luck. This is basically my last resort.
First of all, you should go with a viewmodel oriented html file since you are using MVC (Model, View, Controller):
Create a viewModel:
public class ExampleViewModel
{
public ExampleViewModel()
{
}
public virtual string TextBoxData { get; set; }
}
After, code your html using the viewmodel as model:
#model Models.Views.ExampleViewModel
#using (Html.BeginForm())
{
<div class="editor-row">
<div class="editor-label">
#Html.LabelFor(model => model.TextBoxData)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TextBoxData)
</div>
</div>
<input type="submit" value="submit" />
}
and your controller:
public ActionResult Example()
{
ExampleViewModel model = new ExampleViewModel();
return This.View(model);
}
[HttpPost]
public ActionResult Example(ExampleViewModel model)
{
string infoEntered = model.TextBoxData;
// Do something with infoEntered
}
Hope this will help you!
If you're using view models, check out this answer: MVC sending data from View to Controller
If you're only interested in sending the data from an input to the action method without view models, you can do that as well:
View:
#using (Html.BeginForm("Edit", "Some", FormMethod.Post))
{
<input type="text" id="myTextBox" name="myTextBox" />
<input type="submit" value="Submit" />
}
Notice the BeginForm line. The first parameter is the Action I want the data to go to, which I named Edit. The next parameter is the Controller I am using, which I named SomeController. You don't add the Controller bit to the name when you're referencing the Controller in BeginForm. The third parameter is telling the form to use the POST method when sending the data to the server.
Controller:
public class SomeController
{
[HttpPost]
public ActionResult Edit(string myTextBox)
{
// Do what you want with your data here.
}
}
If you added more inputs (again, without a view model here), you can add them as parameters to the Edit method. This isn't really the preferred method, though. Look into using a view model. ScottGu has a nice blog post on doing what you need, using view models:
http://weblogs.asp.net/scottgu/archive/2007/12/09/asp-net-mvc-framework-part-4-handling-form-edit-and-post-scenarios.aspx
I am new to MVC3 and am trying to write a blog application as a learning tool.
I've created a database object for the blog post and generated a controller using the Controller with Read/Write actions and views using Entity Framework to control the entity.
I'm having troubles with the edit commands. There are about 6 properties for a blog post but I only want to allow the edit to modify the title and content of the post. My code is as follows:
public ActionResult Edit(int id)
{
blog_Post blog_post = db.blog_Post.Find(id);
return View(blog_post);
}
//
// POST: /Post/Edit/5
[HttpPost]
public ActionResult Edit(blog_Post blog_post)
{
if (ModelState.IsValid)
{
db.Entry(blog_post).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(blog_post);
}
#model BlogVersion1._0.blog_Post
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>blog_Post</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PostContent)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PostContent)
#Html.ValidationMessageFor(model => model.PostContent)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The problem that comes about is in the public ActionResult Edit(blog_Post blog_post) method. In the Edit(int id) method, I have put a breakpoint in and I can see that blog_post is being properly passed to the view (including all of its properties populated).
But the blog_post being returned to the [HttpPost] method is missing properties for UserId, DateCreated, etc. An exception is obviously thrown on the db.SaveChanges call as required foreign keys are missing.
How do I ensure that all properties are returned to the second edit method to properly make the update?
Because you are not sending the values of those elements from your form when you do the POST ing. One way to fix this is to keep them inside the form using Hidden Variables
#using(Html.BeginForm())
{
#Html.EditorFor(model => model.Title)
#Html.HiddenFor(model => model.UserId)
<input type="submit" />
}
I think the clean solution is to "Dont use the Domain model in the view, Use a ViewModel with necessary properties for the View. In this case, obviously CreatedDate should not be something the View should supply. It should be something the code will be filled to the object.
So create a ViewModel for this
public class BlogPostViewModel
{
public int ID { set;get;}
public string Title { set;get;}
public string Description { set;get;}
}
and use this for transfering data from View to controller and viceversa
public ActionResult Edit(int id)
{
var domainObject=repo.GetPost(id);
var viewModel=new BlogPostViewModel();
viewModel.ID=domainObject.ID;
viewModel.Title=domainObject.Title;
//map other REQUIRED properties also
return View(viewModel);
}
Your view will be strongly typed to this
#model BlogPostViewModel
#using(Html.BeginForm())
{
#Html.EditorFor(model => model.Title)
#Html.HiddenFor(model => model.Description)
<input type="submit" />
}
In the POST action,map it back to the domain object and save
[HttpPost]
public ActionResult Edit(BlogPostViewModel model)
{
if(ModelState.IsValid)
{
var domainObject=new blog_Post();
domainObject.Title=model.Title;
domainObject.ModifiedDate=DateTime.Now;
//set other properties also
repo.Update(domainObject);
return RedirecToAction("Success");
}
return View(model);
}
Instead of manually mapping properties one by one you can consider using AutoMapper library which does this for you in one line of code!
Just add hidden fields for all other, non-editable properties.
#Html.HiddenFor(model => model.Id)
These field will be included in POST and hence, model binder will correctly put them into your blog_post instance.
On the other side, you should really be using view models - simple POCO classes that will be models for your views. Using entity models directly is not recommended.
Here's some info on that:
ASP.NET MVC ViewModel Pattern
http://stephenwalther.com/archive/2009/04/13/asp-net-mvc-tip-50-ndash-create-view-models.aspx
The model binder will only populate the properties that are POSTed in the HTTP request. Your view only contains Title and PostContent.
You either need to include hidden fields for each of the missing properties. Or just the ID property and then do a database lookup for the rest.
For your case, I think You should rather use the HtmlHelper extension method "EditorForModel" instead of calling "EditorFor" for each property. You are complicating your life using EditorFor on each property (and as gred84 is saying, it doesn't post the non displayed properties in the HTTP request in you context).
In your blog_Post model class you should flag each property that you don't want to be edited with the attribute [HiddenInput(DisplayValue = false)]
Then instead of all your code in the view, you can simply have (simplified - without validation summary)
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="Save" />
}