ASP.Net MVC3 Parent Child Model Binding - asp.net-mvc-3

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

Related

Pass data from Thymeleaf template to springboot controller

I have simple web application written using Springboot and Thymeleaf templates. Report controller receives the data from form and builds the TestPlanReportResponse object which is added as model attribute like this:
#PostMapping("/report")
public String homeSubmit(#ModelAttribute HomeFormInput homeFormInput, Model model, Errors errors) {
final TestPlanReportResponse response = new TestPlanReportResponse(homeFormInput);
model.addAttribute("allData", response);
return "charts";
}
I can work with that data in "charts" thymeleaf template and show the data I need, but I need to send exactly the same object back to controller when button is clicked, but i getting TestPlanReportResponse
object as parameter with nulls set.
#PostMapping("/report/send")
public String sendReport(#ModelAttribute TestPlanReportResponse reportData, Model model) {
//reportData contains just nulls
}
Here is how my button is set in charts template:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<button type="submit">Send the report</button>
</form>
So my question is how to send the object back from thymeleaf template? Should i create a hidden input and put there the "allData" object just to send it back? It looks for me like dirty hack. What would be the appropriate way to pass data back? I want to have this app stateless so don't to store the data on a server side.
When I used to work with Spring and Thymeleaf and form, we had the same issue, passing the data back and forth between a form, the template, and different controllers.
And what you suggest is what we did, we used hidden input as dirty as it may look,it was the standard suggested answer, we did not find anything better.
You need to create an input, with a type a value and link it to a field, like this:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<input type="hidden" th:value="*{allDataValue1}" th:field="*{allDataField1}" />
//Do this for all your attributes/values that you wish to pass to the controller
<button class="btn btn-info btn-lg btn-block" type="submit">Send the report</button>
</form>
Though, i found this answer, you can try looking into this thread

Thymeleaf, Spring nested backing object is not binding the values on form submit

I have a nested object and I'm using it as a model for a form.
public AgeBracketSet implements Serializable{
private String id;
private List<AgeBracket> ageBrackets;
/* Getters and Setters */
}
I have successfully bound all the properties of this object to the form and I can visualize their values when the view state is rendered. Here's a simplified version of how I'm doing it with Thymeleaf. Essentially, I loop through the items of the list and get their attributes.
<form id="bracketForm" role="form" th:action="${flowExecutionUrl}" th:object="${ageBracketSet}" method="post">
<input th:id="'bracketSet_'+*{id}" th:field="*{id}" />
<th:block th:each="bracket,loop : *{ageBrackets}" th:id="'bracket_'+${bracket.id}">
<input th:id="'fromAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].fromAge}" />
<input th:id="'toAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].toAge}" />
</th:block>
</form>
However, when I make changes in the form and submit it, the model remains unchanged. I have confirmed this by debugging the service that receives the form data. The model does not have the changes made in the form. Am I doing anything wrong here?
I am embarrassed to say I've found the solution. The values simply weren't posted to the webflow for a lack of a 'name' attribute in each input. Using the same dynamically generated ID as a name did the job, and the bindings were correct for each item of the list. Like this:
<input th:id="'fromAge_'+${bracket.id}" th:name="'fromAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].fromAge}" />
Thanks to everyone who took the time to read this silly post. I'll be more careful next time ;)

MVC matching ModelState keys to ViewModel collection

Is it possible to match a ViewModel property to the matching ModelState.Key value when the ViewModel is a (has a) collection?
Example: To edit a collection of viewmodel items, I am using the extension found here.
That adds a GUID to the id of the fields on the page.
example:
class Pets
{
string animal;
string name;
}
For a list of Pets, the generated html source is like this:
<input name="Pets.index" autocomplete="off" value="3905b306-a9..." type="hidden">
<input value="CAT" id="Pets_3905b306-a9...__animal" name="Pets[3905b306-a9...].animal" type="hidden">
<input value="MR. PEPPERS" id="Pets_3905b306-a9...__name" name="Pets[3905b306-a9...].name" type="hidden">
<input name="Pets.index" autocomplete="off" value="23342306-b4..." type="hidden">
<input value="DOG" id="Pets_23342306-b4...__animal" name="Pets[23342306-b4...].animal" type="hidden">
<input value="BRUTICUS" id="Pets_23342306-b4...__name" name="Pets[23342306-b4...].name" type="hidden">
So when this gets bound on post, the ModelState gets loaded with all the form fields.
In ModelSTate.Keys, there is:
Pets[23342306-b4...].name
Pets[23342306-b4...].animal
Pets[3905b306-a9...].name
Pets[3905b306-a9...].animal
Everything good so far, but I am doing some business logic validation, things like, cant add new animal if one exists with the same name. In that case, I want to be able to highlight the input field that is in error.
So if my create function fails, it will return an error/key value pair like this:
{ error = "Duplicate Name", key="name" }
So I at least will now what property caused the problem.
But since my repository functions don't know about the view field ids, how can I match the key "name" to the appropriate ModelState key (in this case, either Pets[23342306-b4...].name or Pets[3905b306-a9...].name)?
If you used the built in functionality of MVC for displaying collections (Html.DisplayFor(m => m.Pets) or Html.EditorFor(m => m.Pets)) with appropriate display/editor template, MVC would render something like this:
Pets[0].name
Pets[0].animal
Pets[1].name
Pets[1].animal
This maps to IEnumerable<Pets> and you know that first item has index of 0, second item 1 etc.
So if the second item has an error, you can set error for the ModelState key "Pets[1].name" for example.
If you are using the Html.BeginCollectionItem extension method, like I was, I was able to get around this by not using the GUID. I need the dynamic add and delete, but I was always looking up known items, persons that have an ID, which I had in my editor. So instead of using the GUID, I just assign the ID (uniqueId) in the code below. I could then find the key because I knew it was Person[234232]. Of course if you are adding new items and not displaying selected items, it might not work for you.
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, string uniqueId)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : uniqueId;
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}

MVC3 - Pass back a model from RenderPartial

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

Posting an array of Guid pairs to an Action

As you can see here, I'm allowing a user to dynamically create a table of data, and storing the ids of the table in a hidden field (in the example it's a text area so you can see it, and the final solution will be Guid rather than integers).
My question is simply this: What data type should I use on the server/MVC action to take the data held in the textarea/hidden field?
At the moment I have a string, and am contemplating doing a load of .split()'ing and whatnot, but it doesn't feel right!
Ultimately I need some sort of IEnumerable<Guid, Guid> thing?!?! so I can do a foreach and get each pair of Ids.
I'm sure the answer will be simple, but I can't think of what to do.
Any help appreciated.
If your UI has multiple, like-named form fields, they will be submitted to your action method and bound properly to an array. We could use string[] for this case.
<form action="">
<input type="text" name="guids"/>
<input type="text" name="guids"/>
<input type="text" name="guids"/>
<input type="text" name="guids"/>
<input type="submit" value="Submit"/>
</form>
Then your controller could handle them like so:
public ActionResult MyAction(string[] guids)
{
guids.Count == 4 // if all four fields were filled in.
}
Note that if there is just a single guids value sent by the form, the string[] guids will still work - it will contain just a single item.
Finally, note that if no values are entered, the array value will be null, not an empty array.
You can actually bind to a list from your model, take a look at this post
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Resources