I have a strongly typed razor view for a model in my MVC 3 project. Basically its for editing the model.
The model contains an Id field for the database key and some other string fields (Its a viewModel and all but thats not the point of the question).
In the view I just have a form and a submit button and nothing else. When the View is posted to the controller the model in the controller has all fields empty EXCEPT for the Id field which seems to have been auto-magically filled up.
How and where does the Id field gets populated in the model without there being a corresponding 'input' element for it in the view.
This is probably a dumb question but I would appreciate even just a link to what I should read up on. Thanks.
I bet it comes from the url as route parameter.
For example you have the following controller:
public class HomeController: Controller
{
public ActionResult Index(int id)
{
vqr model = GetModel(id);
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// the model.Id property will be automatically populated here
// because the request was POST /home/index/123
...
}
}
and the following view:
#model MyViewModel
#using (Html.BeginForm())
{
<button type="submit">OK</button>
}
Now you navigate to GET /home/index/123 and you get the following markup:
<form action="/home/index/123" method="post">
<button type="submit">OK</button>
</form>
Notice the action attribute of the form? That's where the id comes from. Basically the Html.BeginForm() helper uses the current url when generating the action attribute, and since the current url is /home/index/123 it is what gets used.
And because if you have left the default routes in your Global.asax, the {id} route token is used at the end of the url, the default model binder successfully binds it to the Id property of your view model.
You are probably hitting a URL similar to the following: /MyObject/Edit/15
This is then returning the page that you have your blank form on.
What happens next is you have an HTML.BeginForm() which is posting BACK to /MyObject/Edit/15
Now because of the post back having the same format your routing rules are picking up the '15' and binding it back to your id.
Have you added the ID field as a hidden field?
e.g.
#Html.HiddenFor(x=> x.ID)
Related
I am wondering about a couple variations of forms and partial forms. The submit is on the parent page and I have varied what I pass to the partial view. I have a parent view with a related HomeViewModel (which has public properties Name and public Person Employee {get;set;}
1.) Scenario 1: The main view has something like the following
#model MasterDetailSample.Models.HomeViewModel
#using (Html.BeginForm()) {
<div>
#{Html.RenderPartial("_PersonView", #Model);}
</div>
<input type="submit" value="Save" />
}
In this scenario I am passing to the partial view _PersonView the entire HomeViewModel. Within _PersonView partial view I have to reference a property of the HomeViewModel i.e. Person object via #model.Employee.Name (in this scenario the submit is on the parent form (not within the partial view))
When I hit submit on the form (POST) in the controller i have to access the property of Employee "Name" via the following model.Employee.Name
This seems to work however notice the following variation scenario 2 (where I only pass to the partial the Employee object)
2.) Scenario 2
In this scenario I only want to send the Employee object to the partial view. Again the begin form and submit is on the parent form.
So from the parent form i have
#{Html.RenderPartial("_MasterView", #Model.Employee);}
and so within the partial view i reference the Name property of the Person object via #Employee.Name Now when I submit the form within the controller the Employee object is not available from the auto model binder. I can access the properties via formcollection but not from the model parameter
i.e.
[HttpPost]
public ActionResult Index(ModelViewModel model) {
**//model.Employee is null!**
return View();
}
Why? (is model.Employee null) I would like my partial view to only accept an object of type Person however after submitting from the parent page, the Employee property is null. On the partial view I am using the following on the #model line
#model MasterDetailSample.Models.Person
I would like the partial to only need a Person object to be sent to it, but I would like the submit on the main form. If i do it this way I can re-use the partial view in a few situations however IF i must send HomeViewModel i have significantly limited how I can use this partial view. So, again, I only want to use Person as the model with the partial view but I need to be able to access the properties when submitted from the parent view.
Can this be done? If yes how?
thx
You have a couple of options:
1) One I recommend -> Dont use partial views, instead use EditorFor and create an editor template for Person. With partial views the context is whatever model you pass into the view, this is why your example (1) works and (2) not. However with editor templates the parent context is taken into consideration with the html helpers and will generate the correct input names. Have a look at Darin Dimitrov's answer to a similar question.
2) Use your second example as is, but change the post action to look something like this:
[HttpPost]
public ActionResult Index(ModelViewModel model) {
TryUpdateModel(model.Employee);
**//model.Employee should now be filled!**
return View();
}
3) Use custom html helpers that accepts prefix for input, see this answer I posted a while back for example code. You could then use this inside your partial view.
I have the following code in a partial view:
#model MyOrganization.MyApp.Models.ProductListing
#using (Ajax.BeginForm("TagProduct", new AjaxOptions() { UpdateTargetId = "FormContainer" , OnSuccess = "$.validator.unobtrusive.parse('form');" }))
{
<p>
#Html.LabelFor(m => m.ModelNumber):
#Html.EditorFor(m => m.ModelNumber)
Tag Product with This Model Number
#(Html.ValidationMessageFor(m => m.ModelNumber))
</p>
}
The viewmodel that this partial view gets is instantiated and many of its properties are hydrated by the view that contains this partial view. However, when the submit is called here, the viewmodel that the controller gets has only the ModelNumber property hydrated. All the other properties are null as if a new instance is created by the partial view with only the property that was edited (ModelNumber) getting a value.
I know that the viewmodel instance passed to the partial view has all the other property values, because if I add an #Html.EditorFor(m => m.SerialNumber) I can see the SerialNumber value that the containing view had rendered in the browser's textbox and I also get it back in the controller when the form is submitted. However, I don't want an editor for the SerialNumber property on the form - I just want it back in the controller when it is submitted.
How can I can I get the entire model back to the controller as it was passed in to the partial view with only the changes that the partial view made?
When it posts the form, it DOES create a new copy of the model with whatever values you posted to it. If you need the other values, put #Html.HiddenFor the other elements you need posted.
Im creating an application that allows me to record recipes. Im trying to create a view that allows me to add the basics of a recipe e.g. recipe name,date of recipe, temp cooked at & ingredients used.
I am creating a view that contains some jquery to load a partial view clientside.
On post im having a few troubles trying to get the values from the partial view that has been loaded using jquery.
A cut down version of my main view looks like (I initially want 1 partial view loaded)
<div id="ingredients">
#{ Html.RenderPartial("_AddIngredient", new IngredientViewModel()); }
</div>
<script type="text/javascript">
$(document).ready(function () {
var dest = $("#ingredients");
$("#add-ingredient").click(function () {
loadPartial();
});
function loadPartial() {
$.get("/Recipe/AddIngredient", {}, function (data) { $('#ingredients').append(data); }, "html");
};
});
</script>
My partial view looks like
<div class="ingredient-name">
#Html.LabelFor(x => Model.IngredientModel.IngredientName)
#Html.TextBoxFor(x => Model.IngredientModel.IngredientName)
</div>
<div class="ingredient-measurementamount">
#Html.LabelFor(x => Model.MeasurementAmount)
#Html.TextBoxFor(x => Model.MeasurementAmount)
</div>
<div class="ingredient-measurementtype">
#Html.LabelFor(x => Model.MeasurementType)
#Html.TextBoxFor(x => Model.MeasurementType)
</div>
Controller Post
[HttpPost]
public ActionResult Create(RecipeViewModel vm,IEnumerable<string>IngredientName, IEnumerable<string> MeasurementAmount, IEnumerable<string> MeasurementType)
{
Finally my viewmodel looks like
public class IngredientViewModel
{
public RecipeModel RecipeModel { get; set; }
public IEnumerable<IngredientModel> Ingredients { get; set; }
}
My controller is pretty ugly......im using Inumerble to get the values for MeasurementAmount & MeasurementType (IngredientName always returns null), Ideally I thought on the httppost Ingredients would be populated with all of the on I would be able Ingredients populated
What do I need to do to get the values from my partial view into my controller?
Why don't you take a look at the MVC Controlstoolkit
I think they would do what you want.
Without getting in too much detail. Can you change the public ActionResult Create to use FormCollection instead of a view model? This will allow you to see what data is coming through if any. It would help if you could post it then.
Your view model gets populated by using Binding - if you haven't read about it, it might be a good idea to do that. Finally I would consider wrapping your lists or enums into a single view model.
Possible Problem
The problem could lay with the fact that the new Partial you just rendered isn't correctly binded with your ViewModel that you post later on.
If you inspect the elements with firebug then the elements in the Partial should be named/Id'ed something like this: Ingredients[x].Property1,Ingredients[x].Property2 etc.
In your situation when you add a partial they are probably just called Property1,Property2.
Possible Solution
Give your properties in your partial the correct name that corresponds with your List of Ingredients. Something like this:
#Html.TextBox("Ingredients[x].Property1","")
Of, after rendering your partial just change all the names en ID's with jquery to the correct value.
It happens because fields' names from partial view do not fit in default ModelBinder convention. You should analyze what names fields have in your partial view.
Also you should implement correct way of binding collections to MVC controller. You could find example in Phil's Haack post
Assuming RecipeViewModel is the model being supplied to the partial view, try just accepting that back in your POST controller like this:
[HttpPost]
public ActionResult Create(RecipeViewModel vm)
{
//
}
You should get the model populated with all the values supplied in the form.
I have layout with login partial view (username, password and submit button) and models ( some controls and submit button)with validation(server side and client side) displayed in normal views (#RenderBody() in layout).
My problem is when do server side validation in any of my views it also validate the login partial view because it execute the httppost function of the login. how can I stop that??
login view controller
[HttpGet]
public ActionResult LogOn()
{
return PartialView();
}
//
// POST: /Account/LogOn
[HttpPut]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
ViewBag.UserName = model.UserName;
}
else
{
ModelState.AddModelError("", Resources.Account.Account.LoginFailureText);
}
}
return PartialView(model);
}
and model controller
public ActionResult MyModel()
{
ViewBag.DisplayThxMsg = false;
return View();
}
[HttpPost]
public ActionResult MyModel(Models.FeedbacksModel feedback)
{
if (ModelState.IsValid)
{
//do something
}
else{
//do another thing
}
return View(feedback);
}
I find your question very difficult to understand. Im guessing your problem is you have a login partial control displayed as part of site layout and is shown on all pages. So while submitting any page, the username password validation kicks in, and you want to prevent that.
Understand that all validation # server - side happens while model binding, As properties are bound to the posted fields, the attributes on the fields are looked at and honored / catered to. So to prevent server side validation simply put the login partial view in it's own form so it is not sent while submitting other forms on the page.
In short have 2 forms - one form for login and one for feedback. Don't put all input fields in the same form.
If you still have validation errors after that, then it is because of other reasons like, type conversion problems. The default model binder will add some errors for basic type conversion issues (for example, passing a non-number for something which is an "int").The sample DataAnnotations model binder will fill model state with validation errors taken from the DataAnnotations attributes on your model.
EDIT
If you look at line number 125
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
You have the above code which will render the login form.
It will do so inside the other form at line 45
<form id="form1" runat="server" method="post">
This has no end tag therefore it will encompass the whole document till </html>
You should change the structure from
<form id="form1" runat="server" method="post">
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
</form
to
<form id="form1" runat="server" method="post">
</form>
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
This line #using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");} will render this form <form id="LoginView1" action="LogOn"> and all child elements of it.
LATEST EDIT
In your layout page use this :
#Html.Partial("~/Views/Shared/LogOnPartial.cshtml", new LogOnModel())
instead of this :
#Html.Action("LogOnPartial", "Account")
The reason why it all works is, the LogOnPartial method marked with [HttpPost] is called because the request was in a POST context. What you want is, You just need the view without the action executing even when POSTing. The above code does that. It renders the view without calling the action method. MVC3 is sort of a stupid servent : It only knows that it should call the Action method marked with [HttpPost] when the request is in a post context. It doesn't know that the request is in a post context for another action (index) and not this one (logonpartial). So now you can remove this method
public ActionResult LogOnPartial()
{
return PartialView();
}
which will no longer be used.
Note that you need to change the account controller's LogOnPartial Method to return
return RedirectToAction("Index","Home"); instead of return PartialView(model); on successful login. And on FAILURE you cannot render a partialview as you have coded. You must return an entirely new View. It must neither be index nor LogonPartails - just return return View("Login_Error_View"); which has its own layout. Otherwise it will be difficult to control the workflow.
I am still new to MVC, so sorry if this is an obvious question:
I have a page where the user can choose one of several items. When they select one, they are taken to another form to fill in their details.
What is the best way to transfer that value to the form page?
I don't want the ID of the item in the second (form) pages URL.
so it's /choose-your-item/ to /redemption/ where the user sees what was selected, and fills the form in. The item selected is displayed, and shown in a hidden form.
I guess one option is to store in a session before the redirect, but was wondering if there was another option.
I am using MVC3
Darin Dimitrov's answer would be best if you don't need to do any additional processing before displaying the /redemption/ page. If you do need to some additional processing, you're going to have to use the TempDataDictionary to pass data between actions. Values stored in the TempDataDictionary lasts for one request which allows for data to be passed between actions, as opposed to the values stored in the ViewDataDictionary which only can be passed from an action to a view. Here's an example below:
public ActionResult ChooseYourItem()
{
return View();
}
[HttpPost]
public ActionResult ChooseYourItem(string chosenItem)
{
TempData["ChosenItem"] = chosenItem;
// Do some other stuff if you need to
return RedirectToAction("Redemption");
}
public ActionResult Redemption()
{
var chosenItem = TempData["ChosenItem"];
return View(chosenItem);
}
If you don't want the selected value in the url you could use form POST. So instead of redirecting to the new page, you could POST to it:
#using (Html.BeginForm("SomeAction", "SomeController"))
{
#Html.DropDownListFor(...)
<input type="submit" value="Go to details form" />
}
To help others, this is how I resolved my issue of needing multiple buttons posting back, and wanting to pass a different Id each time.
I have a single form on the page, that posts back to my controller:
The Form:
#using (Html.BeginForm("ChooseYourItem", "Item", FormMethod.Post))
{
And the code
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ChooseYourItem(string itemId)
{
TempData["ITEMID"] = itemId
return RedirectToAction("Create", "Redemption");
}
Then, inside the form, I create buttons whose name == "itemId", but has a different value each time.
For example
<strong>Item 1</strong>
<button value="123" name="itemid" id="btn1">Select</button>
<strong>Item 2</strong>
<button value="456" name="itemid" id="btn2">Select</button>