Hihi,
Having trouble model binding with this class (the following concrete, not the abstract).
Ignore the basic properties, its the Lists that I'm interested in binding.
public abstract class MessageModel
{
public string Tag { get; set; }
public string Message { get; set; }
public int Id { get; set; }
public const int DefaultIdValue = Int32.MinValue;
public List<LinkModel> Linked { get; set; }
public List<LinkModel> NotLinked { get; set; }
protected MessageModel()
{
Id = DefaultIdValue;
Linked = new List<LinkModel>();
NotLinked = new List<LinkModel>();
}
protected MessageModel(string tag, string message):this()
{
Tag = tag;
Message = message;
}
}
public class TextModel:MessageModel
{
public int TextId { get; set; }
public TextModel()
{
TextId = DefaultIdValue;
}
}
This is the submission I get on the server side on submit (formatted for sanity):
Tag=
&Message=
&NotLinked.index=35fda83a053645e6809bbb8b0ea00103
&NotLinked.index=14c2e286e28b4c9d8f889fb3eb437e5f
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientId=1
&NotLinked.%5b35fda83a053645e6809bbb8b0ea00103%5d.RecipientName=Bob+Biggins
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientId=2
&NotLinked.%5b14c2e286e28b4c9d8f889fb3eb437e5f%5d.RecipientName=Billy+Oswold
&Submit=Submit
When the function is called that accepts that model the NotLinked collection is set to null. D:
The (relevant) output html looks like this (im trying to "faux" bind to:
<ol> and <li>
with jQuery doing the work of moving stuff around)
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked.[a0ab331bee2a461084b686e13a87090b].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked.[d7d294d3174c4bd98d583e92010359e7].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Any ideas? Haven't done this sort of complex binding before so I'm at a loss at the simple mistake I've probably made.
Try removing the dot notation before the brackets and since this isn't a dictionary, but a list, you need to use indices, not keys. The correct syntax for a list in a razor view should look more like this:
<div id="NotLinkedContainer">
<ol id="NotLinked" name="NotLinked" style="width: 500px;height: 200px">
<li value="1">Bob Biggins
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="a0ab331bee2a461084b686e13a87090b" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientId" name="NotLinked[0].RecipientId" type="hidden" value="1" />
<input id="NotLinked__a0ab331bee2a461084b686e13a87090b__RecipientName" name="NotLinked[0].RecipientName" type="hidden" value="Bob Biggins" />
</li>
<li value="2">Billy Oswold
<input id="NotLinked_index" name="NotLinked.index" type="hidden" value="d7d294d3174c4bd98d583e92010359e7" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientId" name="NotLinked[1].RecipientId" type="hidden" value="2" />
<input id="NotLinked__d7d294d3174c4bd98d583e92010359e7__RecipientName" name="NotLinked[1].RecipientName" type="hidden" value="Billy Oswold" />
</li>
</ol>
</div>
Here is an article that covers what you are attempting to do:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Related
In the post "Hello World" phase of my Spring journey. Creating an app that POSTS two fields which RESULT in three fields.
My controller has three fields. Two are populated on POST, and all three should populate the RESULT fields.
The Controller:
#Slf4j
#Controller
public class GreetController {
#GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
}
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting, Model model) {
model.addAttribute("greeting", greeting);
}
}
The Model:
public class Greeting {
//fields correspond to greeting.html
private long id;
private String content;
private String numbah;
public String getNumbah() { return numbah; }
public void setNumbah() {
this.numbah = SomeFunctions.functionOne(this.getContent());
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getContent() { return content; }
public void setContent(String content) { this.content }
}
The View:
<!DOCTYPE HTML>
this is greeting.html
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
</html>
Note:
The ${greeting.numbah} field, as configured above, is resulting in null. I had a log statement in that function that never returned anything, indicating that the function in the model is never firing. However, when the view was configured like this...
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p>Some Number: <input type="text" th:field="*{numbah}"></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
...the field was populating, so the function itself....functions.
Long short, what I'm attempting to do here is take id and content, do someFunction(content)=numbah and return id, content, and numbah.
Thank you in advance.
It always gets down to something simple, doesn't it?
There was a typo in the setNumbah constructor in the model.
This is why I hardly ever post on sites like this: because you almost always figure out what you did wrong if you just sleep on it and have another look.
Thanks for checking this out and apologies that it's not a "high quality" problem/answer. Will delete if that's the protocol for a self-answered question.
I'm using unobtrusive ajax inside a kendo window to update a form once it's been filled in. The form should update the Id's of the DOM elements dynamically and also show the relative data inside the form as returned from the controller.
Let's start, here is the window element that houses the partial view which is a form:
#model Requirement
#{
int localId = Model.Id;
}
<div class="window-content">
<form method="post" data-ajax-url="/Home/Process_Requirement" data-ajax="true" data-ajax-method="post" data-ajax-loading="#spinner" data-ajax-update="#update-form-requirement" data-ajax-success="form_requirement_success">
<div id="update-form-requirement">
<partial name="_Form_Requirement" />
</div><!--/update-panel-->
<div class="window-footer">
<div class="container">
<div class="row no-gutters">
<div class="col">
<button type="submit" class="input-submit float-right">Save</button>
</div>
</div>
</div>
</div>
</form>
</div>
_Form_Requirement
#model Requirement
#{
int localId = Model.Id;
}
<div class="tab-content">
<div class="tab-pane fade show active" id="form-requirement-basic-#localId" role="tabpanel" aria-labelledby="requirement-tab">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" readonly />
<div class="container">
<div class="row">
<div class="col">
<div class="form-group">
<label>Vessel Type</label>
<kendo-dropdownlist name="VesselType"
for="VesselType"
id="VesselType_#localId"
datatextfield="TypeName"
datavaluefield="Id"
min-length="3"
style="width: 100%"
value-primitive="true"
option-label="Select vessel type"
footer-template="<button class='dropdown-button k-icon k-i-plus-outline' data-object-title='Add vessel type' data-object-function='_Window_Vessel_Type' onclick='open_data_window(this, event)'></button>"
filter="FilterType.Contains">
<datasource type="DataSourceTagHelperType.Ajax" page-size="80">
<transport>
<read url="/Vessel/ReadVesselTypes" />
</transport>
</datasource>
<popup-animation>
<open duration="300" effects="fadeIn" />
<close duration="300" effects="fadeOut" />
</popup-animation>
</kendo-dropdownlist>
</div>
</div>
</div>
</div>
</div>
</div>
When the form is submitted the controller action does the following:
/Home/Process_Requirement
public IActionResult Process_Requirement(Requirement model)
{
//Define the partial view we want
string modal = "_Form_Requirement";
//If the Id is 0 then create a new requirement
if (model.Id == 0)
{
if (ModelState.IsValid)
{
try {
_requirementService.InsertRequirement(model);
var getData = _requirementService.GetRequirement(model.Id);
return PartialView(modal, getData);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
else
{
try
{
_requirementService.UpdateRequirement(model);
return PartialView(modal, model);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
return PartialView(modal);
}
So, when you first open a window to create a new requirement the localId is 0 because you're not editing one, that's expected behavior.
When you then fill in all the details and submit the form, the data is posted to the database and saved correctly but the model doesn't seem to update with the new data, for example the input for the Id remains as 0:
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
yet the id for the input updates to the newly created id number. Why is this happening and can I do anything about it?
you have a bug. fix the last return
return PartialView(modal,model);
Is the Id primary key? If so, it is not recommend to change the primary key, you should add a new column to be the key.
the data is posted to the database and saved correctly but the model doesn't seem to update with the new data
This could be caused by the concurrency issues. I can't see your update code now, but you can
try to change your update code like this:
public async Task<IActionResult> Process_Requirement(Requirement model)
{
string modal = "_Form_Requirement";
if (model.Id == 0)
{
//....
}
else //to update
{
await TryUpdateModelAsync<Requirement>(_context.Requirements.FirstOrDefault(x => x.RId ==model.RId),"",c => c.Id, c => c.CreatedUser);
_context.SaveChanges();
return PartialView(modal, model);
}
return PartialView(modal);
}
My Model:
public class Requirement
{
[Key]
public int RId { get; set; } //new column
public int Id { get; set; }
public string CreatedUser { get; set; }
}
And in partial, pass the key with a hidden field:
<input type="text" asp-for="RId" hidden/>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" />
Result:
I have a requirement where I am asked to capture a fee applicable for a certain course. But the problem is for few courses the same fee is applicable to all type of users and for some courses profession-wise fee is applicable. eg if the user is a teacher then he has fewer fees than if he is an industry professional.
There are predefined professions in database.
Here is my current logic with code.
Depending on the radio button, I am toggling the proper div and particular logic at the back end.
<input type="radio" name="professionWiseFees" value="true">
<input type="radio" name="professionWiseFees" value="false">
<div id="totalAmount" class="hidden">
<input class="feeAmountOnly" name="feeAmount" type="text" />
</div>
<div id="professionWiseAmount" class="hidden">
<c:forEach items="${applicationScope.professionList}" var = "profession" >
Course Fee For ${profession.profession}
<input type="hidden" value="${profession.id}" name="profession" type="text"/>
<input class="feeAmoun2t" name="profession" type="text" />
</c:forEach>
<div>
Now I am checking which type of fees is applicable and filling the hashmap accordingly. if prefessionWiseFees is not applicable then adding the same fees to all the profession.
Boolean isProfessionWiseFeesApplicable = Boolean.parseBoolean(reqParams.get("professionWiseFees")[0]);
Map<Integer,Double> feeProfessionMap = new HashMap<>();
List<Profession> professions = (List<Profession>) servletContext.getAttribute("professionList");
if(isProfessionWiseFeesApplicable) {
String[] propfessionWiseFees = reqParams.get("profession");
// [1(profession_id),1000(fees),2,2000,3,7000], hence i+2 i=profession_id, i+1=fees
for(int i=0; i < propfessionWiseFees.length-1 ;i=i+2){
feeProfessionMap.put(Integer.valueOf(propfessionWiseFees[i]),Double.parseDouble(propfessionWiseFees[i+1]));
}
}
else {
double feeAmount = Double.parseDouble(reqParams.get("feeAmount")[0]);
for(Profession profession: professions){
feeProfessionMap.put(Integer.valueOf(profession.getId()),feeAmount);
}
}
courseBean.setProfessionWiseFees(feeProfessionMap);
courseBean.setProfessionWiseFees(isProfessionWiseFeesApplicable);
Model class:
public class CourseBean {
// few fields
private Map<Integer, Double> professionWiseFees; // <profession_id ,fees>
// all setters and getters
}
So How can I solve this problem elegantly? requestParam.get is making the code cluttery
If you think that
use of requestParam.get reducing readability and maintainability of code.
You may use Spring web binding.
First define a FeeForm bean:
public class FeeForm {
boolean isProfessionWiseFees;
ProfessionAmount[] professionAmounts;
Double feeAmount;
}
Then define the ProfessionAmount bean:
public class ProfessionAmount {
int profession;
double amount;
}
Then change the controller method in this way:
#RequestMapping(value = { "/test.html" }, method = RequestMethod.POST)
public String testOverflow(Map<String, Object> model,
FeeForm form)
{
Map<Integer,Double> feeProfessionMap = new HashMap<>();
List<Profession> professions = (List<Profession>) servletContext.getAttribute("professionList");
if(form.isProfessionWiseFees()) {
for(ProfessionAmount f : form.getProfessionAmounts()){
feeProfessionMap.put(f.getProfession(),f.getAmount());
}
}
else {
for(Profession profession: professions){
feeProfessionMap.put(profession.getId(),form.getFeeAmount());
}
}
HTML:
<input type="radio" name="professionWiseFees" value="true"> true
<input type="radio" name="professionWiseFees" value="false"> false
<div id="totalAmount" class="hidden">
<input class="feeAmountOnly" name="feeAmount" type="text" />
</div>
<div id="professionWiseAmount" class="hidden">
<c:forEach items="${applicationScope.professionList}" var = "profession" varStatus="status" >
Course Fee For ${profession.profession}
<input type="hidden" value="${profession.id}" name="professionAmounts[${status.index}].profession" type="text"/>
<input class="feeAmoun2t" value='0' name="professionAmounts[${status.index}].fee" type="text" />
</c:forEach>
<div>
You should come to the jsp holding flag in the model bean which indicate if the fees is applicable or not based on it play on the design and disable the inapplicable options .
or
use AJAx. you can use radio button and use onchange of them to check by ajax. if it's inapplicable return validation error message.
I've got a partial View, loaded from an Action, so the parent view contains:
#Html.Action("TourSearch")
The TourSearch View uses a editor as such:
#Html.EditorFor(model => model.ImpersonatedAgentModel, "ImpersonatedAgentView")
where ImpersonatedAgentModel is as such:
[Serializable]
public class ImpersonatedAgentModel
{
[Required(ErrorMessage = "Please provide a User ref")]
public string AgentImpersonatedUserName { get; set; }
[Required(ErrorMessage="Please provide a ABTA/AgencyCode")]
public string AgentImpersonatedBranchCode { get; set; }
[Required(ErrorMessage = "Please provide a User ref")]
public int? AgentImpersonatedBranchID { get; set; }
}
My editor is pretty straight forward:
#model Travel2.WebUI.Models.ImpersonatedAgentModel
<ul id="agencyDetails">
<li>
<label for="AgentImpersonatedBranchCode">ABTA/Agency Code: *</label>
#Html.TextBoxFor(model => model.AgentImpersonatedBranchCode, new {ID="txtBranchCode" })
#Html.ValidationMessageFor(model => model.AgentImpersonatedBranchCode, "*")
<input id="txtBranchId", type="hidden" value="#Model.AgentImpersonatedBranchID" />
<input id="txtUserName", type="hidden" value="#Model.AgentImpersonatedUserName" />
<input id="hidCurrentController" type="hidden" value='#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue' />
</li>
<li>
<label for="AgentImpersonatedUserName">User ref: *</label>
<select id="ddlUser" disabled="disabled" ></select>
<input type="hidden" id="txtUserID" />
#Html.HiddenFor(model => model.AgentImpersonatedUserName, new {ID="AgentImpersonatedUserName" })
#Html.HiddenFor(model => model.AgentImpersonatedBranchID, new {ID="AgentImpersonatedBranchID"})
#Html.ValidationMessageFor(model => model.AgentImpersonatedUserName, "*")
</li>
</ul>
Now in Chrome, all works fine. But when I fill in the form in IE but not the data in the Editor, it passes validation incorrectly!
If I examine the markup, using IEs poor excuse for Firebug, I can see the validation attributes,
<input name="ImpersonatedAgentModel.AgentImpersonatedBranchCode" id="txtBranchCode" type="text" data-val="true" data-val-required="Please provide a ABTA/AgencyCode" jQuery172048066185567747205="94"/>
so why is IE ignoring them!! Stupid IE
Found the answer here:
http://www.tigraine.at/2011/08/26/jquery-validate-and-microsofts-unobtrusive-validation-dont-play-well-together/
We were referencing Jquery validation too and this seems to be causing this error
I am using the partial view with the ajax.beginform. In that partial view page, i have the following markup
EDIT
<%
using (Ajax.BeginForm("ManageDataSources", "DataSources", saveAjaxOptions))
{
%>....
<td>
<%: Html.Hidden("DataSource_Id", dataSource.Id, new { #class = "DataSource_Id" })%>
<%: Html.TextBox("DataSource_Name", dataSource.Name, new { #class = "DataSource_Name" })%>
</td>
<tr class="queryParameters" style="display: block">
<td colspan="2" align="center">
<input id="Text1" name="parametername" type="text" />
<input id="Text2" name="parametervalue" type="text" />
<input id="Text3" name="parametername" type="text" />
<input id="Text4" name="parametervalue" type="text" />
<input id="Text5" name="parametername" type="text" />
<input id="Text6" name="parametervalue" type="text" />
<input id="Text7" name="parametername" type="text" />
<input id="Text8" name="parametervalue" type="text" />
<input id="Text9" name="parametername" type="text" />
<input id="Text10" name="parametervalue" type="text" />
</td>
</tr>
and in the the controller, i have this model for the representation of the data
public class DataSourceViewModel
{
public string DataSource_Id { get; set; }
public string DataSource_Name { get; set; }
public List<SCParams> parameters { get; set; }
}
public class SCParams
{
public string parametername { get; set; }
public string parametervalue { get; set; }
}
EDIT
public ActionResult ManageDataSources(DataSourceViewModel dsvm)
{
return PartialView("ManageDataSources");
}
when i post the data these parametername and parameter values are not at all bound to the list of objects. How do i do this. i am using microsoft ajax and want to do this without using other plugings. Kindly suggest the right way.
EDIT
This is the data in the header taken from chrome
DataSource_Id:
DataSource_Name:Name
parametername:a
parametervalue:1
parametername:q
parametervalue:2
parametername:z
parametervalue:3
parametername:s
parametervalue:4
parametername:w
parametervalue:5
x:15
y:12
What I understand you have master detail structure and you want to receive it controller. if that is the case. then there are two possibilities either your detail portion has variable length detail portion or fixed length detail portion. You may follow the post here for variable length as well as fixed length. For Fixed length you may also follow here.
You will receive the model in following signature
public ActionResult ManageDataSources(DataSourceViewModel dsvm)
moreover you may also check formcollection parameter for actionresult
[HttpPost]
public ActionResult MyAction(FormCollection collection)