Can't get selected option in controller - spring

I have a form to list up buckets in select options, it uploads file to bucket after select bucket and file, but I can't get selected bucket in my controller, The folder alwsy return empty string, though mutipartFile is no problem, I really want to know why!
I googled for all this week but no result what I need!
I am very new in thymeleaf even in spring framework:(
Pls help me to solve this simple problem to you:)
part of html file as below:
<form role="form" enctype="multipart/form-data" action="#" th:object="${folder}" th:action="#{'/drill/skin/upload'}" method="POST">
<div class="form-group">
<label class="form-control-static">Select Bucket</label>
<select class="form-control" th:field="${folder}">
<option th:each="bucket : ${buckets}" th:value="${bucket.name}" th:text="${bucket.name}">bucket</option>
</select>
</div>
<label class="form-control-static" for="inputSuccess">Select Upload File</label>
<div class="form-group">
<input type="file" class="form-control" name="uploadFile"/>
</div>
<div class="form-group">
<button class="btn btn-primary center-block" type="submit">Upload</button>
</div>
</form>
controller as below:
#RequestMapping(value="/", method=RequestMethod.GET)
public String provideUploadInfo(Model model) {
List<Bucket> buckets = s3Service.listBuckets();
model.addAttribute("buckets", buckets);
model.addAttribute("folder", "com.smartstudy");
return "index";
}
#RequestMapping(value="/upload", method=RequestMethod.POST)
public String handleFileUpload(
#ModelAttribute("folder") String folder,
#RequestParam("uploadFile") MultipartFile uploadFile, Model model) {
log.info("Bucket: " + folder + ", uploadFile: " + uploadFile.getOriginalFilename());
if (!uploadFile.isEmpty() && !folder.isEmpty()) {
return s3Service.upload(uploadFile, folder);
}
return "index";
}

There are couple of issues in your code.
Ideally, the whole form should be encapsulated in one form-backing object. In your case, create a Java object that wraps the folder and the file together.
class BucketFileForm{
private MultipartFile uploadFile;
private String folder;
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
public MultipartFile getUploadFile() {
return uploadFile;
}
public void setUploadFile(MultipartFile uploadFile) {
this.uploadFile = uploadFile;
}
}
Make this object available in the model so that you can access it in the view
#RequestMapping(value="/", method=RequestMethod.GET)
public String provideUploadInfo(Model model) {
List<Bucket> buckets = s3Service.listBuckets();
model.addAttribute("buckets", buckets);
//replace this
//model.addAttribute("folder", "com.smartstudy");
//with
BucketFileForm bucketFileForm = new BucketFileForm();
bucketFileForm.setFolder("com.smartstudy");
model.addAttribute("bucketFileForm", bucketFileForm);
return "index";
}
Now, use this form-backing object in the form
<form role="form" enctype="multipart/form-data" action="#" th:object="${bucketFileForm}" th:action="#{'/drill/skin/upload'}" method="POST">
<div class="form-group">
<label class="form-control-static">Select Bucket</label>
<select class="form-control" th:field="*{folder}">
<option th:each="bucket : ${buckets}" th:value="${bucket.name}" th:text="${bucket.name}">bucket</option>
</select>
</div>
<label class="form-control-static" for="inputSuccess">Select Upload File</label>
<div class="form-group">
<input type="file" th:field="*{uploadFile}" class="form-control" name="uploadFile"/>
</div>
<div class="form-group">
<button class="btn btn-primary center-block" type="submit">Upload</button>
</div>
</form>
Then modify your POST endpoint to accomodate these changes.
#RequestMapping(value="/upload", method=RequestMethod.POST)
public String handleFileUpload(#ModelAttribute("bucketFileForm") final BucketFileForm form, final BindingResult bindingResult, Model model) {
log.info("Bucket: " + form.getFolder() + ", uploadFile: " + form.getUploadFile().getOriginalFilename());
if (!form.getUploadFile().isEmpty() && !form.getFolder().isEmpty()) {
return s3Service.upload(uploadFile, folder);
}
return "index";
}

Related

Passing form data from View Component to Controller in .NET Core MVC

I have a Component View and I try to update data from a form in it, I call the controller but in the controller I receive null :
public class CustomerPropertyViewComponent: ViewComponent
{
private MyDBContext context;
public CustomerPropertyViewComponent(MyDBContext _contex)
{
context = _contex;
}
public async Task<IViewComponentResult> InvokeAsync(int id)
{
CustomerPropertyModelView c = new CustomerPropertyModelView();
TblCustomerProperty t = new TblCustomerProperty(context);
c = t.getAllInfo(id);
if (c.model == null)
{
c.model = new TblCustomerProperty();
c.model.CustomerId = id;
}
return View(c);
}
}
and in the view I have
#model SunSystemDotNetCoreVersion.Models.helpers.CustomerPropertyModelView
<form asp-action="Update" asp-controller="CustomerProperties"
data-ajax="true"
data-ajax-method="POST"
method="post">
<div class="row w-100">
<div class="col-6">
<div class="row align-items-center h-100">
<div class="col-5 text-right">
Number of Bedrooms
</div>
<div class="col-7 p-1 p-1">
#Html.TextBoxFor(model => model.model.Bedrooms, new { #class = "form-control", Type = "number" })
</div>
<div class="col-5 text-right">
Current Heating System
</div>
<div class="col-7 p-1">
<select asp-for="model.HeatingSystemTypeId" class="form-control"
asp-items="#(new SelectList(Model.HeatingsList,"HeatingSystemTypeId","Title"))">
<option value="0">-Plaese Select-</option>
</select>
</div>
.....
<div class="col-12">
<button type="submit" >Save</button>
</div>
</div>
</form>
I have reduced most of the view code but it contains all data that the model needs. and this is my controller:
public class CustomerPropertiesController : Controller
{
private readonly MyDBContext_context;
public CustomerPropertiesController(MyDBContextcontext)
{
_context = context;
}
public IActionResult Update(TblCustomerProperty modelView) {
//here modelView is null
return View();
}
it should be work I don't know why it keeps sending null to my controller.
You could F12 to check the html elements in browser,and you will find the name of these elements are like:model.Bedrooms.Because your main model is CustomerPropertyModelView but your input belongs to TblCustomerProperty which named model in CustomerPropertyModelView.If your backend code recieve CustomerPropertyModelView as parameter,it will not be null.But if you recieve TblCustomerProperty as parameter,you need specify the suffix.
Change like below:
public IActionResult Update([Bind(Prefix ="model")]TblCustomerProperty modelView)
{
return View();
}

Forms inside foreach loop MVC

I have situation where MVC controller has sent list of records.
Those records are displayed in view in order that every single record is a form, so it can be posted record by record.
#model IList<FMS.Application.Models.TransferLineUpdateResponse>
#{
ViewData["Title"] = "Transfer Lines Page";
}
<div class="row">
<div class="col-12">
#foreach (var item in Model)
{
<form method="post" class="form-inline">
<div class="form-group">
<input type="hidden" asp-for="#item.Id" readonly class="form-control" />
<input type="hidden" asp-for="#item.TransferHeaderId" readonly class="form-control" />
</div>
<div class="form-group">
<label asp-for="#item.Item"></label>
<input asp-for="#item.Item" readonly class="form-control" />
</div>
<div class="form-group">
<label asp-for="#item.ToLocationId"></label>
<select class="form-control" asp-for="#item.ToLocationId" asp-items="ViewBag.ToLocations"></select>
</div>
<div class="form-group">
<input type="submit" asp-route-status="Accepted" class="btn btn-success" /> |
<input type="submit" asp-route-status="Rejected" class="btn btn-danger" />
</div>
</form>
}
</div>
</div>
When one of two buttons is clicked and form submission is called, as parameter in controller all my records like Id, ToLocationId are null or empty guids in case if property has type guid. Why it is not taken data which has been actually changed in view ?
Regards
P.S.here is the code of controller
[HttpGet("Transfer/Details/{id}")]
public async Task<IActionResult> Details(Guid id)
{
var lines = await _transferLineService.GetTransferLines(id);
var locations = await _locationService.GetLocations();
var customLocations = locations
.Where(w=>w.Code != "Tranzit")
.Select(s => new
{
Id = s.Id,
Description = $"{s.Code} - {s.Description}"
});
ViewBag.ToLocations = new SelectList(customLocations, "Id", "Description");
return View(lines);
}
[HttpPost("Transfer/Details/{transferHeaderId}")]
public async Task<IActionResult> AcceptDetails(Guid transferHeaderId, TransferLineUpdateResponse update)
{
var result = await _transferLineService.UpdateTransferLine(update);
await _transferHeaderService.UpdateTransferHeaderStatus(update.TransferHeaderId);
return RedirectToAction("Details",new { transferHeaderId = update.TransferHeaderId });
}
Why it is not taken data which has been actually changed in view ?
Model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.In your code,the asp-for tag helper will generate the name like:item.propertyName.It could not match with backend model.You could use [Bind(Prefix ="item")] to specify the prefix.
Change like below:
[HttpPost("Transfer/Details/{transferHeaderId}")]
public async Task<IActionResult> AcceptDetails(Guid transferHeaderId,[Bind(Prefix ="item")] TransferLineUpdateResponse update)
{
//do your stuff...
//_context.Update(update);
//await _context.SaveChangesAsync();
//your get method used [HttpGet("Transfer/Details/{id}")]
//so,it should be id not transferHeaderId
return RedirectToAction("Details", new { id = update.TransferHeaderId });//also change here...
}
Result:

Custom Model Validation in ASP.Net core v3.1 MVC ajax form not seems to be working

I'm working on an ASP.Net core 3.1 MVC project in which I have to create a custom Validator, and I want it to be working for client as well as server side (e.g. Required Attribute).
I developed a simple custom validator like below just for POC -
public class ImportantAttribute : ValidationAttribute, IClientModelValidator
{
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
AttributeUtils.MergeAttribute(context.Attributes, "data-val", "true");
AttributeUtils.MergeAttribute(context.Attributes, "data-val-important", FormatErrorMessage(context.ModelMetadata.GetDisplayName()));
}
public class AttributeUtils
{
public static bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
string val = value.ToString();
if (val.Contains("hello"))
{
return ValidationResult.Success;
}
}
return new ValidationResult("Value not valid");
}
}
and used this attribute on a property and created a View using the same model.
Them modified the form tag to become an ajax form like -
<form asp-action="Index" role="form" data-ajax="true" data-ajax-method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" value="SGSM" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Then I added below java script -
$(document).ready(() => {
console.log('I\'m ready bro');
$.validator.addMethod("important",
function (value, element, params) {
console.log('1', value);
return value.contains('hello');
}, "Not OK");
$.validator.unobtrusive.adapters.add("important",
['important'],
function (options) {
console.log('2', options);
options.rules["important"] = options.important;
options.messages["important"] = options.message;
});
});
When I run this by providing any value to the text box and submitting the form it don't show any error message on the page, but if I put break point in the Action Method the ModelState shows correct info.
If I make the form as regular form (i.e. non-ajax form) everything works as expected.
I have searched a lot but could not find any thing related.
Based on your code and requirement, I made some some modifications on custom client-side validation code, which works well for me, you can refer it.
<div class="row">
<div class="col-md-4">
<form asp-action="Index" method="post" role="form" data-ajax="true" data-ajax-method="post" data-ajax-complete="completed">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.js"></script>
<script>
$(document).ready(() => {
console.log('I\'m ready bro');
});
completed = () => {
alert('Request completed!');
};
$.validator.addMethod("important",
function (value, element, params) {
console.log('1', value);
return value.includes('hello');
}, "Not OK");
$.validator.unobtrusive.adapters.add("important",
['important'],
function (options) {
console.log('2', options);
var element = $(options.form).find('input#Name')[0];
options.rules["important"] = [element, ''];
options.messages["important"] = options.message;
});
</script>
}
Test Result

Spring #ModelAttributes auto binding

Hi i am tring to use #ModelAttributes for auto binding from view to controller.
this jsp page should send all elements which is Personal Schedule VO.
<form name="insertFrm" action="myScheduleInsert" method="POST">
<ul>
<input type="hidden" class="form-control" name="perschd_num" >
<li>
<label class='control-label'>title</label>
<input type="text" class="form-control" name="perschd_title" >
</li>
<li>
<input type="hidden" class="form-control" name="perschd_writer" >
<li>
<label class='control-label'>start date</label>
<input type="date" id="fullYear" class="form-control" name="perschd_start_date" value="">
</li>
<li>
<label class='control-label'>end date</label>
<input type="date" class="form-control" name="perschd_end_date" >
</li>
<li>
<label class='control-label'>content</label>
<input type="text" class="form-control" name="perschd_cont" >
</li>
</ul>
</div>
<!-- button-->
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input type="submit" class="btn btn-primary">
</form>
and this is my controller for insert feature by using #ModelAttributes PerschdVO perschd and when i try print all out, all is null.
//insertSchedule
#RequestMapping("/myScheduleInsert")
public String myScheduleInsert(#ModelAttribute PerschdVO perschdVO ){
String view="redirect:/professor/mypage/mySchedule";
*System.out.println(perschdVO);*
try {
proService.insertPerschd(perschdVO);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return view;
}
and this is PerschdVO
public class PerschdVO {
private int perschd_num;
private String perschd_title;
private String perschd_cont;
private Date perschd_date;
private String perschd_start_date;
private String perschd_end_date;
private String perschd_writer; //id
public String getPerschd_writer() {
return perschd_writer;
}
public void setPerschd_writer(String perschd_writer) {
this.perschd_writer = perschd_writer;
}
public int getPerschd_num() {
return perschd_num;
}
public void setPerschd_num(int perschd_num) {
this.perschd_num = perschd_num;
}
public String getPerschd_title() {
return perschd_title;
}
public void setPerschd_title(String perschd_title) {
this.perschd_title = perschd_title;
}
public String getPerschd_cont() {
return perschd_cont;
}
public void setPerschd_cont(String perschd_cont) {
this.perschd_cont = perschd_cont;
}
public Date getPerschd_date() {
return perschd_date;
}
public void setPerschd_date(Date perschd_date) {
this.perschd_date = perschd_date;
}
public String getPerschd_start_date() {
return perschd_start_date;
}
public void setPerschd_start_date(String perschd_start_date) {
this.perschd_start_date = perschd_start_date;
}
public String getPerschd_end_date() {
return perschd_end_date;
}
public void setPerschd_end_date(String perschd_end_date) {
this.perschd_end_date = perschd_end_date;
}
why this elements from jsp does not match with this? Eventhough i matched all name in jsp file to PerschdVO's get() names.
and if i fix like this
public String myScheduleInsert(
Principal who,
#RequestParam("perschd_start_date")String perschd_start_date,
#RequestParam("perschd_end_date")String perschd_end_date,
#RequestParam("perschd_title")String perschd_title,
#RequestParam("perschd_cont")String perschd_cont
){
PerschdVO perschdVO = new PerschdVO();
...}
than the data from jsp could be sent to #Controller, but still can't get informations about using #ModelAttributes. cos if i use that always 400 bad request happened.

Spring Boot multiple controllers with same mapping

My problem is very similar with this one: Spring MVC Multiple Controllers with same #RequestMapping
I'm building simple Human Resources web application with Spring Boot. I have a list of jobs and individual url for each job:
localhost:8080/jobs/1
This page contains job posting details and a form which unauthenticated users -applicants, in this case- can use to apply this job. Authenticated users -HR Manager-, can see only posting details, not the form. I have trouble with validating form inputs.
What I tried first:
#Controller
public class ApplicationController {
private final AppService appService;
#Autowired
public ApplicationController(AppService appService) {
this.appService = appService;
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.POST)
public String handleApplyForm(#PathVariable Long id, #Valid #ModelAttribute("form") ApplyForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "job_detail"; //HTML page which contains job details and the application form
}
appService.apply(form, id);
return "redirect:/jobs";
}
#RequestMapping(value = "/applications/{id}", method = RequestMethod.GET)
public ModelAndView getApplicationPage(#PathVariable Long id) {
if (null == appService.getAppById(id)) {
throw new NoSuchElementException(String.format("Application=%s not found", id));
} else {
return new ModelAndView("application_detail", "app", appService.getAppById(id));
}
}
}
As you guess this didn't work because I couldn't get the models. So I put handleApplyForm() to JobController and changed a little bit:
#Controller
public class JobController {
private final JobService jobService;
private final AppService appService;
#Autowired
public JobController(JobService jobService, AppService appService) {
this.jobService = jobService;
this.appService = appService;
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.POST)
public ModelAndView handleApplyForm(#PathVariable Long id, #Valid #ModelAttribute("form") ApplyForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return getJobPage(id);
}
appService.apply(form, id);
return new ModelAndView("redirect:/jobs");
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.GET)
public ModelAndView getJobPage(#PathVariable Long id) {
Map<String, Object> model = new HashMap<String, Object>();
if (null == jobService.getJobById(id)) {
throw new NoSuchElementException(String.format("Job=%s not found", id));
} else {
model.put("job", jobService.getJobById(id));
model.put("form", new ApplyForm());
}
return new ModelAndView("job_detail", model);
}
}
With this way, validations works but I still can't get the same effect here as it refreshes the page so that all valid inputs disappear and error messages don't appear.
By the way, job_detail.html is like this:
<h1>Job Details</h1>
<p th:inline="text"><strong>Title:</strong> [[${job.title}]]</p>
<p th:inline="text"><strong>Description:</strong> [[${job.description}]]</p>
<p th:inline="text"><strong>Number of people to hire:</strong> [[${job.numPeopleToHire}]]</p>
<p th:inline="text"><strong>Last application date:</strong> [[${job.lastDate}]]</p>
<div sec:authorize="isAuthenticated()">
<form th:action="#{/jobs/} + ${job.id}" method="post">
<input type="submit" value="Delete this posting" name="delete" />
</form>
</div>
<div sec:authorize="isAnonymous()">
<h1>Application Form</h1>
<form action="#" th:action="#{/jobs/} + ${job.id}" method="post">
<div>
<label>First name</label>
<input type="text" name="firstName" th:value="${form.firstName}" />
<td th:if="${#fields.hasErrors('form.firstName')}" th:errors="${form.firstName}"></td>
</div>
<!-- and other input fields -->
<input type="submit" value="Submit" name="apply" /> <input type="reset" value="Reset" />
</form>
</div>
Check thymeleaf documentation here
Values for th:field attributes must be selection expressions (*{...}),
Also ApplyForm is exposed then you can catch it in the form.
Then your form should looks like this:
<form action="#" th:action="#{/jobs/} + ${job.id}" th:object="${applyForm}" method="post">
<div>
<label>First name</label>
<input type="text" name="firstName" th:value="*{firstName}" />
<td th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"></td>
</div>
<!-- and other input fields -->
<input type="submit" value="Submit" name="apply" /> <input type="reset" value="Reset" />
</form>

Resources