I have a page in MVC3 with a model of "pageModel".
In this page I have:
#{ Html.RenderPartial("_subPage", Model.subModel); } (Pagemodel.submodel)
In my controller I am doing:
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Results(pagemodel model, string frmAction)
{
}
The page loads fine the first time, but when I postback into the httpPost action, model.submodel is always null.
My question is, how do I return an updated model from the RenderPartial (if at all). I can get my model INTO the partial, but not back!
The problem with partials is that they do not preserve the navigational context. This means that any input fields that you might have put inside this partial will have incorrect names and the default model binder will not be able to retrieve the values back when you POST. Your HTML will look like this:
<input type="text" name="Prop1" value="property 1 value" />
<input type="text" name="Prop2" value="property 2 value" />
whereas the correct is:
<input type="text" name="subModel.Prop1" value="property 1 value" />
<input type="text" name="subModel.Prop2" value="property 2 value" />
In order to achieve this correct markup I would recommend you using editor templates.
So you replace:
#{ Html.RenderPartial("_subPage", Model.subModel); }
with:
#Html.EditorFor(x => x.subModel)
and then you move your _subPage.cshtml partial into ~/Views/Shared/EditorTemplates/SubModelType.cshtml where SubModelType is the type of the subModel property:
#model SubModelType
#Html.EditorFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop2)
Now when you look at the generated HTML the corresponding input field names should be prefixed with subModel and inside the POST controller action the model.subModel property will this time be properly initialized and populated from the values that were entered by the user in the input fields.
you'll need to change your partialview to accept the top level model, i.e:
#{ Html.RenderPartial("_subPage", Model); }
which would then render your properties in the partialview with the correct property names i.e. :
<input type="text" name="subModel.MyProperty" value="somevalue" />
It would also mean that your returned model in the HttpPost action will have to correct navigational relationship intact.
this is just one of those caveats related to viewmodels and hierarchies. Oh, btw, in mvc3, you don't need the verbose [AcceptVerbs(HttpVerbs.Post)] for posts. You can simply use [HttpPost]
You can also perform the following.
#Html.RenderPartial(
"_subPage",
Model.subModel,
new ViewDataDictionary
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = "subModel"
}
});
Your partial view will remain as is, using the #model SubModel
Related
I have two action methods. One of them submits the inserted data of a "new product", and the other form must upload the photos of that product. Each one has it's own Model, View, and each one calls it's own Action from controllers, which are completely separate.
But I need to have the forms both in just one view.
I've done this by using #html.action() to render the "Upload" action's View in the "Insert New Product" action's View.
The problem is, both of the submit buttons call the same "Insert New Product" action :|
Take a look. Here's the first View:
#using (Html.BeginForm("Insert_New_Product", "Admin", FormMethod.Post))
{
// Inputs, Validation Messages and all those stuff ...
<input type="submit" name="Insert_New_Product" value="Add New Product" />
// Here, I render the "Upload" View :
#Html.Action("Upload", "UploadImage")
}
The "Upload" View looks like this :
#using (Html.BeginForm("Upload", "UploadImage", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
// Inputs and stuff ...
<input type="submit" value="Upload" name="Upload"/>
}
So how is this possible to have two (or more) forms, each one calling it's own ActionResult on submit?
I'd appreciate your help.
I think this #Html.Action("Upload", "UploadImage") is the problem. You're essentially rendering the second form inside of the first one. That's not going to work. Try changing it to this:
#using (Html.BeginForm("Insert_New_Product", "Admin", FormMethod.Post))
{
// Inputs, Validation Messages and all those stuff ...
<input type="submit" name="Insert_New_Product" value="Add New Product" />
}
// Here, I render the "Upload" View :
#Html.Action("Upload", "UploadImage")
Also, you should really be using Html.RenderAction instead of Html.Action as it writes directly to the response stream. See here for more information. Like so:
#{ Html.RenderAction("Upload", "UploadImage"); }
i have problem to pass data from view to controller , i have view that is strongly typed with my viewmodel "TimeLineModel", in the first i passed to this view my viewmodel from action on my controller
public ActionResult confirmation(long socialbuzzCompaignId)
{
return View(new TimeLineModel() { socialBuzzCompaignId = socialbuzzCompaignId, BuzzMessages = model });
}
with this i can get info from my action and display it on view , but i have other action POST which i won't get my view model to do some traitement
[HttpPost]
public ActionResult confirmation(TimeLineModel model)
{
}
i can get some propretie of the model but in others no , for example i can get the properti "socialBuzzCompaignId" of model , but other propertie like "IEnumerable BuzzMessages" i can't get it , i dont now why !!
this is the content of my view
#model Maya.Web.Models.TimeLineModel
#{
ViewBag.Title = "confirmation";
}
#using (Html.BeginForm())
{
<h2>confirmation</h2>
<fieldset>
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
</div>
<br />
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
You need to include BuzzMessages properties within a form element. Since it's not editable, you'd probably want to use hiddens. There are two ways to do this. Easiest is instead of doing a foreach loop, do a for loop and insert them by index.
#for (int i =0; i<Model.BuzzMessages.Count(); i++v)
{
<div class="editor-label">
#Model.BuzzMessages[i].LongMessage
#Html.HiddenFor(m => m.BuzzMessages[i].LongMessage);
</div>
<br />
}
but to do this you'd need to use an IList instead of an IEnumerable in your view model to access by index.
Alternatively, you could create an Editor Template named after your BuzzMessages class (whatever its name is).
#model BuzzMessagesClass
#Html.HiddenFor(m => m.LongMessages)
<!-- Include other properties here if any -->
and then in your main page
#Html.EditorFor(m => m.BuzzMessages)
Check out http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/ or search stack overflow if the details of editor templates confuse you.
Just like any HTML POST method, you have to get the data back to the Controller somehow. Just simply "showing" the data on the page doesn't rebind it.
You have to put the data in an input (or a control that will post back) to the appropriate model property name.
So, if you have a model property with name FirstName and you want this data to be rebound to the model on POST, you have to supply it back to the model by placing an "input hidden" (or similar control that postbacks) with the ID of FirstName will rebind that property to the model on POST.
Hope that explains it.
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
<input type="hidden" name="BuzzMessages.LongMessage" value="#msg.LongMessage" />
</div>
}
It will post array of LongMessages. Get values like this:
[HttpPost]
public ActionResult confirmation(TimeLineModel model, FormCollection collection)
{
var longMessages = collection["BuzzMessages.LongMessage"];
}
I've got a form (here's some of its code):
#model Discussion
#{
ViewBag.Title = "Edit Discussion";
Layout = "~/Views/Shared/_App.cshtml";
}
#using (Html.BeginForm("Update", "Discussion", FormMethod.Post, new { id = Model.discussionId, #class = "update" }))
{
... rest of the view code
}
My related controller's update method has:
[HttpPost]
[ValidateInput(false)]
public ActionResult Update(FormCollection col, int id = 0)
{
... rest of code
}
when the form is submitted, I keep getting an id of 0 instead of the ID I'm seeing from the model that when I debug is clearly there inside being used during the form rendering. IT's just when I submit, it's not passing the id correctly.
Am I doing something wrong in terms of syntax here? the new{} in the Html.BeginForm I guess I don't understand how the new anonymous type is matching up to this id when the form is submitted when it's a collection of id and a class here as you can see.
When you do the new {id = Model.discussionId ...} you are setting the id attribute of the form element. To get the id to come across properly you would have to make it an input of the form. So inside the form you would put a hidden input element with something like this: #Html.Hidden("id", Model.discussionId)
So your current form is rendering something like this:
<form id="theDiscussionId" class="update" action="/Discussion/Update">
... rest of the view code
</form>
And you need it to be like this:
<form class="update" action="/Discussion/Update">
<input type="hidden" name="id" value="theDiscussionId" />
... rest of the view code
</form>
I have a partial template that uses a User object as a model. The user has a collection of Accounts. On this partial template I have a loop as follows. The _Account partial template is bound to the Account class
#foreach (var item in Model.Accounts)
{
<tr>
<td colspan="6">
<div>
#Html.Partial("_Account", item)
</div>
</td>
</tr>
}
In my controller method I initially tried
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UserDetails(User user, string actionType)
But the User.Accounts collection is empty. Then I tried this. Still the Accounts collection is empty.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UserDetails(User user,
[Bind(Prefix="User.Accounts")]
FixupCollection<Account> Accounts,
string actionType)
Can I use the default Modelbinder implementation to achieve this or do I need to do anything different?
Yep, you can use the default model binder. You just need to name your fields correctly. So you need your loop to output something like this:
...
<input type="text" name="user.Accounts[0].SomeTextField" />
<input type="text" name="user.Accounts[0].SomeOtherTextField" />
...
<input type="text" name="user.Accounts[1].SomeTextField" />
<input type="text" name="user.Accounts[1].SomeOtherTextField" />
...
If you need to add/remove accounts, the hardcoded indexes get a little trickier. You could re-assign the names using javascript before postback. But it's all possible. This question gives more detail on model binding:
ASP.NET MVC: Binding a Complex Type to a Select
Use Editor Templates instead of a partial view - no need to hard code your indexes as the template will automagically index all your objects correctly, even when you add and remove Accounts. See my answer to this question:
Pass values from multiple partial views
Small write up on Editor Templates here:
codenodes.wordpress.com - MVC3 Editor Templates
I have a simple MVC3 app with an EF4 Model
Log
.Name
.CreatedDate
.LogTypeId
LogTypes
.Id
.Description
and a ViewModel
LogViewModel
Log MyLog
List<SelectListItem> Options
LogViewModel(){
Log = new Log();
}
This displays in my view correctly and I can edit/update the values, display the drop down list and set the name to "MyTestValue".
However, in my controller's HttpPost Create method the properties for logVm.Log are not set?
[HttpPost]
public ActionResult Create(LogViewModel logVm){
logVm.Log.Name == "MyTestvalue"; //false - in fact its null
}
What am I doing wrong?
That's probably because in your edit form you don't have corresponding values. So if yuor view is strongly typed to LogViewModel the form input names must be appropriately named:
#model LogViewModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Log.Name)
#Html.EditorFor(x => x.Log.Name)
</div>
<div>
#Html.LabelFor(x => x.Log.SomeOtherProperty)
#Html.EditorFor(x => x.Log.SomeOtherProperty)
</div>
...
<input type="submit" value="OK" />
}
sop that when the form is submitted the POSTed values look like this:
Log.Name=foo&Log.SomeOtherProperty=bar
Now the default model binder will be able to successfully bind your view model. Also make sure that the properties you are trying to assign are public and that have a setter.
The controller method should have a property named model
[HttpPost]
public ActionResult Create(LogViewModel **model**){
**model**.Log.Name == "MyTestvalue"; //true }