Submit values from partial view to parent viewmodel - c#

Lets say I have a partial view that contains both a checkbox and a numerical value.
I have a ViewModel that contains a Model -- Terms -- that implements the partial view. When I submit it, the modifications made in the Terms Partial View does not reflect to the Terms property of ViewModel. I'm probably misunderstanding a concept or another on how it works, anyone care to point it out please?
View
#model ViewModel
#using (Html.BeginForm("ViewAction", "ViewController", FormMethod.Post))
{
// Other ViewModel Property Editors
#Html.Partial("Terms", Model.Terms)
<input type="submit" value="Submit" />
}
Partial View
#model Terms
#Html.CheckBoxFor(m => m.IsAccepted)
#Html.EditorFor(m => m.NumericalValue)
Controller
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult ViewAction(int id)
{
ViewModel vm = GetVmValues();
return View(vm);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ViewAction(ViewModel vm)
{
// Access ViewModel properties
}

The default model binder expects the control ids for your Terms model to be named Terms.IsAccepted and Terms.NumericalValue. You need to create an editor template for your Terms model and then call #Html.EditorFor(m=> m.Terms) instead of using a partial.
You can read more about editor templates here. Its from MVC 2, but should still be relevant.

Change the model type of your Partial View to 'ViewModel' instead of 'Terms'.
Here is the updated code:
View:
#model ViewModel
#using (Html.BeginForm("ViewAction", "ViewController", FormMethod.Post))
{
// Other ViewModel Property Editors
#Html.Partial("Terms", Model) //Set parent 'ViewModel' as model of the partial view
<input type="submit" value="Submit" />
}
Partial View:
#model ViewModel
#Html.CheckBoxFor(m => m.Terms.IsAccepted)
#Html.EditorFor(m => m.Terms.NumericalValue)
The generated Html will be:
<input id="Terms_IsAccepted" name="Terms.IsAccepted" type="checkbox" value="true">
While the DefaultModelBinder map values from value providers (e.g. form data/route data/query-stirng/http file) to a complex object, it searches values having the name as the properties of the object. In your case, to build the 'Terms' child object of your 'ViewModel', it will search for values with the names like 'Terms.IsAccepted', 'Terms.NumericalValue' etc.
The Html helper uses the property path expression to generate the html element's name, that is why you have to use the parent ViewModel as model of the partial view.
Hope this helps...

Related

How to Pass View Data to Partial View in Asp.net core?

I am to new .NET core using 2.2 version. I am trying to pass data to partial view with following code :
<partial name="_Emplyees" model="#Model.Employees" view-data="#new ViewDataDictionary(ViewData) { { "index", index }}"/>
but its giving syntax error. can someone guide how to pass data and use in partial view? Thanks in advance.
The issue is that you've got double quotes inside the view-data attribute. You need to use single quotes around the attribute value.
<partial name="_Emplyees" model="Employees" view-data='#new ViewDataDictionary(ViewData) { { "index", index } }'/>
Also, #Model is superfluous here, so I removed it.
You could pass the ViewData to partial view like below in ASP.Net Core MVC:
1.Model:
public class TestModel
{
public string Employees { get; set; }
}
2.View(Create.cshtml):
#model TestModel
#{
ViewData["index"] = true;
}
<partial name="_Emplyees" model="#Model" view-data="ViewData" />
3.Partial View:
<h3>Index: #ViewData["index"]</h3>
#model TestModel
#if ((bool)ViewData["index"])
{
#Model.Employees
}
else
{
<input asp-for="Employees" type="number" class="form-control" />
}
4.Controller:
public IActionResult Create()
{
var testmodel = new TestModel() { Employees = "aaa" };
return View(testmodel);
}
5.Result:
Reference:
How to use view-data pass the data to partial view
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-3.0#access-data-from-partial-views
See Partial views in ASP.NET Core
and Partial Tag Helper in ASP.NET Core.
<partial name="Shared/_ProductPartial.cshtml" for="Product">
<partial name="_ProductViewDataPartial" for="Product" view-data="ViewData">
#await Html.PartialAsync("_ProductPartial", product)
Note:
When a partial view is instantiated, it receives a copy of the
parent's ViewData dictionary.
As per the documentation, When a partial view is instantiated, it receives a copy of the parent's ViewData dictionary. Updates made to the data within the partial view aren't persisted to the parent view. ViewData changes in a partial view are lost when the partial view returns.
Checkout the documentation here
For me I have simply created a partial view, accessed it inside another view and used ViewData directly inside it.
~/Views/Shared/_PageTop.cshtml
<a asp-controller="Home" asp-action="Index">Go Back</a>
<h1>#ViewData["Title"]</h1>
~/Views/Create.cshtml
#{
ViewData["Title"] = "Create";
}
<partial name="_PageTop" />
So without passing anything to partial view _PageTop, I can access parent's Title property directly using #ViewData["Title"].

Render Partial View in parent view containing list of other model

I have a partial view: UnderAge.cshtml I'm getting a list of Artist(a model class) in that partial view. I have an Index parent view, which displays a list of Person(other model class), where I want to add the content from the Partial View UnderAge.cshtml. I've checked that the partial view works good and it has a list of two elements, the problem is when I try to show the list of Artist from partial view UnderAge.cshtml on Index parent view, maybe because Index is using elements from other model class (Person).
Partial View(I'm putting just the #model part), it display a list of Artist model class:
#model IEnumerable<AlexMusicStore.Models.Artist>
Parent view(has a different #model) And I'm getting this exception:
The model item passed into the dictionary is of type 'System.Collections.Generic.List'1[AlexMusicStore.Models.Person]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable'1[AlexMusicStore.Models.Artist]'.
#model IEnumerable<AlexMusicStore.Models.Person>
<div>
#{
Html.RenderPartial("~/Views/Artists/UnderAge.cshtml", Model);
}
</div>
Try this,
#model IEnumerable<AlexMusicStore.Models.Artist>
<div>
#Html.Partial("UnderAge"));
</div>
OR
Also you can create model like this,
public class Person
{
public IEnumerable<ArtistModel> Artists{ get; set;} //Passed to Partial
}
public class ArtistModel
{
//your values here
}
then display like this in Parent View
#model IEnumerable<AlexMusicStore.Models.Person>
<div>
#{
Html.RenderPartial("~/Views/Artists/UnderAge.cshtml", Model.Artists);
}
</div>

Partial view inside parent view not sending data to controller - asp.net MVC

I have a partial view name test/index
following is the code -
#model MyFirstMVC.Models.testModels
#using (Html.BeginForm())
{
#Html.TextBox("test1")
<input type="submit" value="click" />
#ViewBag.a
}
And the controller of this partial view is following -
[HttpPost]
public ActionResult Index(MyFirstMVC.Models.testModels m)
{
ViewBag.a = Request["test1"].ToString() + " from controler";
return View(m);
}
In my parent view I am embedding my partial view like below -
#Html.Partial("../test/index",new MyFirstMVC.Models.testModels())
When I run the application, the partial view do appear in the parent view. But when I enter value inside partial view and submit then nothing is happening (the value is not forwarding to the controller of partial view - I have checked that the controller action method related to the partial view is not invoking either by setting break-point).
Any help will be appreciated. Why my partial view is not sending value to its controller?
You need to specify the controller and action names in Html.BeginForm() if your not posting back to the same method that generated the view
#model MyFirstMVC.Models.testModels
#using (Html.BeginForm("Index", "Test"))
{
#Html.TextBox("test1")
<input type="submit" value="click" />
#ViewBag.a
}

Rename HTML name attribute to get data into model

I have problems to pass data from a form using post into my model.
I have the following code:
public ActionResult Edit(int? id)
{
...
return this.View("Edit", Tuple.Create(staff, team);
}
As you can see I am returning a Tuple to the View, because I need to have more than one model. I guess I would normally create a ViewModel, but in this case this would be to much I think.
Is there generally something wrong returning a Tuple, List, or even a Dictionary to a View? Should I always create a ViewModel?
This is the View:
#model Tuple<Staff, List<Team>>
#{
var staff = Model.Item1;
var teams = Model.Item2;
}
#using(Html.BeginForm())
{
...
#Html.LabelFor(model => staff.Foo)
#Html.EditorFor(model => staff.Bar)
}
#using(Html.BeginForm())
{
...
#Html.LabelFor(model => team.Foo)
#Html.EditorFor(model => team.Bar)
}
Anyway, this code renders like so:
<input type="text" ... name="staff.Foo" ... />
and
<input type="text" ... name="team.Foo" ... />
This is my target controller (when I submit form "staff"):
[HttpPost]
public ActionResult Edit([Bind(Include = "foo,bar")] Staff staff)
{
...
this.DbContext.SaveChanges();
...
}
The problem is, that the data will be send via post, but my models keeps beeing empty. I guess this is due to the fact I passed my models as a Tuple to the view.
Even though I change
#Html.EditorFor(model => model.Item1.Foo)
will be
<input type="text" ... name="Item1.Foo" ... />
How can I fix this. I couln't find a solution to rename the name attribute to simply "Foo" instead of "staff.Foo". I guess this would fix the problem. Do I really have to create a ViewModel?
Best regards
If you are only posting one complex property of the model/tuple, you can use the Bind.Prefix property
[HttpPost]
public ActionResult Edit([Bind(Prefix="staff")] Staff model)
This effectively strips the staff. prefix from the properties so staff.Foo becomes Foo and will be bound to class Staff, however I strongly recommend using a view model rather than Tuple (and its actually less code anyway).

Incorrect model property value rendered in partial view

I have a strongly-typed partial view whose model contains a property with the same name as the parent page's view model. For some reason the rendering engine is rendering the parent view model value, not the expected value (well, the value I expect at least!)
Parent page view model extract:
public class ParentPageViewModel
{
public int Id { get; set; } // problem property
...
public IEnumerable<ChildViewModel> Children { get; set; }
}
Child page view model extract:
public class ChildViewModel
{
public int Id { get; set; } // problem property
...
}
Parent page extract (Razor):
#model ParentPageViewModel
...
#foreach (var item in Model.Children)
{
#Html.Partial("MyPartialView", item)
}
...
Partial view extract:
#model ChildViewModel
...
<form ...>
#Html.HiddenFor(m => m.Id) // problem here - get ParentPageViewModel.ID not ChildViewModel.Id
</form>
...
So basically in my rendered output, my hidden field has the value of the parent view model element, NOT the value passed to the partial view. It's definitely being caused by the name, as changing the name of #ChildViewModel.Id# to something like #ChildViewModel.ChildId# makes it work as expected. Interestingly, when inspecting the view model values in the debugger I do see the correct values; it's only the rendered output that's wrong.
Is there a way round this or 'correct' way of doing what I'm trying to do (I'm rendering mini forms in a table for ajax validation/posting of updates to table rows)
Thanks,
Tim
I think changing your call to this will solve the problem:
#Html.Partial("MyPartialView", item, new ViewDataDictionary())
The child view is picking up the value from the ViewData dictionary - so this passes in a new dictionary to the child view (hattip danludwig).
Found a solution, just manually creating the hidden field, e.g.:
<input type="hidden" name="Id" value="#Model.Id" />
instead of using Html.HiddenFor.
(I won't mark this as answered for a while in case there are any other solutions or anyone can explain the problem)
I know this an old post. But I figured since I landed here when I was facing the same problem, I might as well contribute.
My issue was a little different. In my case, the main view's Id was incorrect after a partial view action that required the whole page/view to be refreshed was triggered. I solved the problem with ModelState.Clear
ModelState.Clear();
return View("MyPartialView", model); //call main view from partial view action
Create a file named ChildViewModel.cshtml in Views/Shared/EditorTemplates. Put your partial view into that file:
in ~/Views/Shared/EditorTemplates/ChildViewModel.cshtml
#model ChildViewModel
...
<form ...>
#Html.HiddenFor(m => m.Id)
</form>
...
Then, render it like this:
#model ParentPageViewModel
...
#foreach (var item in Model.Children)
{
#Html.EditorFor(m => item)
}
...
Or, if you'd rather keep the view as a partial and not as an editor template, use Simon's answer.

Categories

Resources