How could I pass a non default constructor object to Controller? - asp.net

Sorry if this is an obvious question, but I didn't had much luck so far.
I am having an input of a submit type in my View:
<input type="submit"
value="Remove Question"
class="btn btn-outline-danger"
formaction=#Url.Action("Survey_RemoveQuestion",
new Survey_Question_Wrapper() {
Survey = Model,
Question = Model.Questions[i] })/>
In my controller I have a handler, which looks like this:
public ActionResult Survey_RemoveQuestion(Survey_Question_Wrapper s)
{
s.Survey.Questions.Remove(s.Question);
return View("SurveyEdit", s.Survey);
}
The Survey_Question_Wrapper has 2 constructors: a default empty one and the one, who accepts 2 parameters and assigns them to fields.
The problem which I am struggle with, is that the Survey_RemoveQuestion method
is invoked with an object, build with the default constructor, so his fields are null's.
I believe there is something obvious I am missing. Many thanks in advance.

No, the problem is not relevant with Survey_Question_Wrapper constructors. You are trying to pass the complex types to controller via GET. You can't send the complex types directly like that.
You should serialize it and send it as string. (I used Json.Net, you can use another library)
<input type = "submit"
value="Remove Question"
class="btn btn-outline-danger"
formaction=#Url.Action("Survey_RemoveQuestion",
new
{
s = JsonConvert.SerializeObject(new Survey_Question_Wrapper
{
Survey = Model,
Question = Model.Questions[i]
}
)
})/>
And controller part looks like;
public ActionResult Survey_RemoveQuestion(string s)
{
//Deserialize it
var obj = JsonConvert.DeserializeObject<Survey_Question_Wrapper>(s);
obj.Survey.Questions.Remove(obj.Question);
return View("SurveyEdit", obj.Survey);
}
Also, if you want to send complex types to server, its proper way to perform it using POST instead of GET.

Related

Thymeleaf: th:value - if property exists

I want to create a hidden input field:
<input type="hidden" th:value="${map.version} name="version"/>
Problem:
version maybe a non existing attribute yet (I am not talking about null!).
Right now I am getting an Exception Property or field 'version' cannot be found on object
What I need:
If it does not exist, th:value statement maybe ignored or tag removed
CLARIFICATION:
map comes from Spring Controller in a handler-method:
#PostMapping("/new")
public String handleMapFormSubmit(
#ModelAttribute("map") #Valid AddMapCommand command, BindingResult result ) {
if ( result.hasErrors() ) {
return "map-form";
}
// do some stuff
return ".....";
}
Problem is that map (AddMapCommmand) in this handler-method does not contain the version attribute. In another handler-method (UpdateMapCommand) it does. The whole point is to reuse the map-form thymeleaf template in both scenarios which are almost similar.
You can try the instanceof operator to be used only for the object that contains the property:
<input type="hidden"
th:if="${map instanceof T(my.project.UpdateMapCommand)}"
th:value="${map.version} name="version">
For future reference, it is extremely confusing using a variable like map and not have the reader interpret it as a java.util.Map. You should change your map variable name to make it less confusing, or at least for the purpose of asking the question on StackOverflow.

ASP.NET Core: asp-* attributes use request payload over model?

It seems that in ASP.NET Core, the value in asp-* attributes (e.g. asp-for) is taken from the request payload before the model. Example:
Post this value:
MyProperty="User entered value."
To this action:
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View();
}
OR this action
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View(m);
}
View renders this:
<input asp-for="MyProperty" />
The value in the form input is User entered value. and not Change it to this!.
First of all, I'm surprised that we don't need to pass the model to the view and it works. Secondly, I'm shocked that the request payload takes precedence over the model that's passed into the view. Anyone know what the rationale is for this design decision? Is there a way to override the user entered value when using asp-for attributes?
I believe this is the expected behavior/by design. Because when you submit the form, the form data will be stored to ModelState dictionary and when razor renders your form elements, it will use the values from the Model state dictionary. That is why you are seeing your form element values even when you are not passing an object of your view model to the View() method.
If you want to update the input values, you need to explcitly clear the Model state dictionary. You can use ModelState.Clear() method to do so.
[HttpPost]
public IActionResult Create(YourviewModel model)
{
ModelState.Clear();
model.YourProperty = "New Value";
return View(model);
}
The reason it uses Model state dictionary to render the form element values is to support use cases like, showing the previously submitted values in the form when there is a validation error occurs.
EDIT : I found a link to the official github repo of aspnet mvc where this is confirmed by Eilon (asp.net team member)
https://github.com/aspnet/Mvc/issues/4486#issuecomment-210603605
I can confirm your observation. What's really going to blow your mind is that this:
[HttpPost]
public IActionResult Foo (MyModel m)
{
m.MyProperty = "changed";
var result = new MyModel { MyProperty = "changed" };
return View(result);
}
...gives you the same result.
I think you should log a bug: https://github.com/aspnet/mvc/issues
Edit: I now remember this issue from previous encounters myself and concede that it isn't necessarily a bug, but rather an unintended consequence. The reasons for the result of executing this code is not obvious. There likely isn't a non-trivial way to surface a warning about this, but PRG is a good pattern to follow.

Auto Data Binding occurs when Binding two same-type instances to two Spring MVC forms

In the jsp file:
<sf:form ... action="queryUser" modelAttribute="user_a">
<sf:input path="name"/>
<input type="submit" id="submit1"/>
</sf:form>
<sf:form ...action="addUser" modelAttribute="user_b">
<sf:input path="name"/>
<input type="submit" id="submit2"/>
</sf:form>
In the Java file:
#Controller
#RequestMapping("/user")
#SessionAttributes("user_a")
public class UserController
{
...
RequestMapping("/addUser")
public void function(#ModelAttribute("user_a") User user_a,#ModelAttribute("user_b") User user_b,BindingResult bindingResult)
{
...
}
}
Here is the problem: when I click the submit2.
the request entity user_b will be passed to both user_a and user_b!!
Who knows how to distinguish them??
I think I figured out the problem.
When in the case like in the problem:
RequestMapping("/addUser")
public void function(#ModelAttribute("user_a") User user_a,#ModelAttribute("user_b") User user_b,BindingResult bindingResult)
{
...
}
The only possibility that you use two same-type objects in one controller's function and do not want to modify certain one at the same time is that one of them has already been filled previously, and you want to use it in this function. Or you agree to modify both of them (in this case, you would pass two different entities).
So, in the situation that I encountered, just do not list the object in the function's parameters. And you can access the ready-made model attribute by codes like:
RequestMapping("/addUser")
public void function(Model model,#ModelAttribute("user_b") User user_b,BindingResult bindingResult)
{
// if this is a session attribute, it will not be changed.
User user_a = model.get("user_a");
//more codes go here
}
In conclusion, spring MVC framework will bound one valid object of the request to all the same-type variables when there is only one valid object and there are multiple same-type objects in the function's parameter list.
One sentence principle:
Binding operation of a variable take place when and only when you list it in the function's parameter list.

handle form post with a array of items in spring MVC

I'm trying to send some data from the client side to the server, and have it processed into a file download.
I'm using a simple HTML form because I want to initialize a file download (and not AJAX).
one of the form fields is an array of items. (the other two are name and description strings).
I'm serializing this field to a string (JSON.stringify) before submitting the form.
on the server side I tried a million techniques (#ModelAttribute vs. #RequestBody, different jackson mapping bean configurations) to either convert this to a single type or to three separate types (String + String + List/Array).
the examples I found were only for AJAX...
can anyone supply a working example or a description of one?
=======
Update:
I've implemented a workaround by JSON.stringify-ing the collection and passing it in one of the inputs,
and on the server side I have:
#RequestMapping(method = RequestMethod.POST, value = "exportSectionsToExcel")
public HttpEntity<byte[]> createExcelWorkBook(#ModelAttribute ExportSectionsListForm exportSectionsListForm) {
Section[] sectionObjects = gson.fromJson(exportSectionsListForm.getSections(), Section[].class);
...
with ExportSectionsListForm object containing strings only:
public class ExportSectionsListForm {
private String name;
private String url;
private String rssUrl;
private String sections;
...
(omitting ctor, getters and setters)
additionally, I found this promising link:
http://viralpatel.net/blogs/spring-mvc-multi-row-submit-java-list/
but didn't try it - seems like I'll need to dynamically generate input elements for this to work, but it might actually be the right solution. has anyone tried this?
The #ModelAttribute tag will try to build the object based on form postings. Since you are serializing your form values to JSON, this wont work. #RequestBody simply gives you a String representing the request body. So, you could get the String representing the JSON being passed in, then demarshal the JSON using Jackson of FlexJSON (or whatever JSON library you use). I am not sure this is the best approach, though.
I would question why you need to serialize the form to JSON to begin with. Spring handles forms with Lists/Maps just fine. Simply submit the form using the #ModelAttribute, making your "array" and List, or whatever you are expecting, on the Controller. So, if I am interpreting your example correctly, my ModelAttribute would look like:
public class ExportSectionsFormBean {
private String name;
private String url;
private String rssUrl;
private List<String> sections;
/* getters/setters */
}
Then my Controller method would look like:
#RequestMapping(method = RequestMethod.POST, value = "exportSectionsToExcel")
public HttpEntity<byte[]> createExcelWorkBook(#ModelAttribute ExportSectionsFormBean exportSectionsFormBean ) {
/* Do whatever with your */
}
On the form side, using the Spring JSTL tags, simply make your "sections" fields look like:
<form:input path="sections[0]" />
<form:input path="sections[1]" />
Or, if you'd rather use HTML, then
<input type="text" name="sections[0]" id="sections0" />
<input type="text" name="sections[1]" id="sections1" />
Which is what gets generated by the above JSTL tags. As long as the values for "sections" is put in the HTTP request as 'section[#]=value', you are all set.
I have been working on the same issue. And if i have several inputs witht eh same name such as:
<input name="somename"/>
<input name="somename"/>
<input name="somename"/>
and i have a form mapped to my method like this:
#ModelAttribute("ReturnsAndExchangesForm") ReturnsAndExchangesForm returnsAndExchangesForm
and in that form i have getters and setters for a property named:
String[] somename , spring is passing those values into that array nicely!

How to read a complex view model on POST?

I have an user view model that has the following properties:
public User user;
public List<Language> Languages;
I send the above model to the view and use html helpers to build the form, so I end up with something like:
<form action="/Users/Edit/5" method="post"><input id="user_UserId" name="user.UserId" type="hidden" value="5" />
First Name
Last Name
Email
<br />
<input id="user_Email" name="user.Email" type="text" value="test#gmail.com" />
Language
-
en
en
Now, I try to read the POST in something that initially was something like :
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Edit( int UserId, FormCollection form ) {
and cannot get the user.UserId variable, user.FirstName variable etc.
Any idea what needs to be done to be able to read this kind of POST request. I'm kind of reluctant to modifying my ViewModel as it is very simple and easy to maintain as it is.
Thank you.
I had a similar problem some time ago. This article was helpful:
Editing a variable length list
Apparently the easy answer is to use Prefix, something like:
public ActionResult Edit([Bind(Prefix="user")] int UserId, FormCollection form ) {
}
However, I'm still getting the
The parameters dictionary contains a
null entry for parameter 'UserId' of
non-nullable type 'System.Int32' for
method 'System.Web.Mvc.ActionResult
Edit(Int32,
System.Web.Mvc.FormCollection)'
Any idea how to fix that?

Resources