I am using AJAX controller to provide the object from the server side. User enters the input, and AJAX controller provides the object for that input request. I have a complex validation logic so I have written it in Java, and put it in the AJAX controller. It looks like this:
#RequestMapping(value = "api/user/{user}", produces = "application/json", method = RequestMethod.GET)
public #ResponseBody UserData execute(#PathVariable(value = "user") String user) {
if(validator.isValid(user)) {
return service.getUserData(user);
} else {
throw new ResourceNotFoundException(INVALID_USER_FORMAT);
}
In Javascript, I call the AJAX controller like this:
// obtain user from text input
var user = $('#user_input').val();
$.getJSON("/api/user/"+user, function (data) {
// ...
});
Now, I want to display on the UI an error message where the entered user in '#user_input' is invalid. And I want to use the validation logic from my controller, and not to have duplicated validation logic also in Javascript.
What should I return from my AJAX controller when the validation doesn't succeed, and how to make use of that in Javascript, so that I can display something like "User format invalid"? What is the best practice?
Related
I have a form with some input fields and when you click on Save, I do a check if a field already exist in the database. I have a service method for this.
For example in the database the field with value "Test10" already exist and if the user use "Test10" in the input field and clicks on save I want to show this message :
private async Task<bool> CheckIfCodeAlreadyExist(string code)
{
return await _service.CheckCodeExist(code);
}
I tried with session vaiables, with an extra bool parameter but not effective enough.
My JavaScript knowlegde is not that much, but would it possible to check this with a "onClick" event in the form?
If the result is true you stay on the page with the message.
Don't mind the Model.Code is red.
Take a look at Microsoft's documentation for model validation, specifically remote validation. The use case and example for this is almost identical to yours:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.2#remote-attribute-1
The [Remote] attribute implements client-side validation that requires
calling a method on the server to determine whether field input is
valid. For example, the app may need to verify whether a user name is
already in use.
In your case, you may use a model with a Code property like this one:
[Remote(action: "CheckCode", controller: "Home")]
public string Code { get; set; }
On the server under the corresponding controller, you can provide the endpoint needed to validate the code:
[AcceptVerbs("Get", "Post")]
public IActionResult CheckCode(string code)
{
if (!yourService.VerifyCode(code))
{
return Json($"Code {code} is already in use.");
}
return Json(true);
}
You don't need to do it with onClick if you have liberty to use Remote validation .net MVC offers.
Decorate you model property with something like:
[Remote("Action", "Controller", ErrorMessage = "Invalid Code")]
Or pass additional fields as well:
[Remote("Action", "Controller", AdditionalFields = "Id", ErrorMessage = "Invalid Code")]
with javascript and jquery, you do ajax request.
Bind click event to button:
$('#button').on('click', function(){
$.ajax({
url: '/Controller/Action',
method: 'get',
data: {
code: $('#Code').val()
}
}).done(function (result) {
if (result) {
// code valid
$('form').submit();
} else {
// show error
}
}).fail(function () {
// ajax request failed
});
});
Controller/Action to return bool:
public async Task<bool> CheckIfCodeAlreadyExist(string code)
{
return await _service.CheckCodeExist(code);
}
I am using AJAX controller. Here is the workflow:
User enters the data in the textbox, hits search
If the entered data doesn't correspond to the expected (valid) data format (ie some exact regex) - Validation error message should pop up
If the entered data is correct, the controller is called. Controller calls the external service based on (validated) data from the user (textbox), and returns the corresponding JSON, and AJAX returns in back to client-side Javascript
Controller.java
#RequestMapping(value = "api/user/{user}", produces = "application/json", method = RequestMethod.GET)
public #ResponseBody UserData execute(#PathVariable(value = "user") String user) {
if(validator.isValid(user)) {
return service.getUserData(user);
} else {
throw new ResourceNotFoundException(INVALID_USER_FORMAT);
}
Javascript
// obtain user from text input
var user = $('#user_input').val();
$.getJSON("/api/user/"+user, function (data) {
// ...
});
For now, I have the validation done in my controller, and NOT in Javascript. For example, I could remove it from controller, and just have it in Javascript, like this:
// obtain user from text input
var user = $('#user_input').val();
if(validate(asin)) {
$.getJSON("/api/user/"+user, function (data) {
// ...
});
}
The question
What is better, and what is more a good pattern? Having validation in Controller like such, or in Javascript?
It is good to have validation on both side but client side validation is must, client side validation will save lot server hit by restricting data with invalid format.
It is always preferred to validate both the side.
Server side validation required for security reason. It is not necessary that user always send request through client UI. some of malicious user can use curl request [ex. postman] and any script for [ex JavaScript, angular etc] for request manipulation.
So always assume that client can send anything you should validate it on server side as well.
I am writing a single page Spring MVC application.
Requirements:
I want it to change the state of the page according to the URL that is entered.
I want it to change the state of the page on a click of a button.
Example use cases:
If I enter a URL "my.site.com/", I want only my site skeleton to be loaded, with no data for the User. (User is an object in my model).
If I enter a URL "my.site.com/users/John", I want the data for "John" to be displayed (the page can be reloaded).
If I enter string "John" in a textbox, and hit button Go!, I want only the part of the page displaying user data to be refreshed and not the whole page reloaded.
Design Question:
I understand that for 1) and 2) I would need to return a new ModelAndView object, and for 3) I could use AJAX. This probably implies I would need three controller methods.
What I don't know is how to avoid conflicting URLs between the MVC and AJAX controller methods, and how to actually then call my AJAX controller method from Javascript, and not the ModelAndView controller method.
Code Example:
What I would need is something like this, except that this, of course, causes conflicting URLs.
/*
* Default view.
*/
#RequestMapping(value = "/users")
public ModelAndView displayDefault() {
return new ModelAndView("userdisplay_default.jsp");
}
/*
* View for a specific user.
*/
#RequestMapping(value = "/users/{username}")
public ModelAndView displaySpecific(#PathVariable(value = "username") String username) {
User user = new User(username);
return new ModelAndView("userdisplay_specific.jsp", "Specific User", user);
}
/*
* AJAX controller method.
*/
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.GET)
public #ResponseBody User getTime(#PathVariable(value = "username") String username) {
return new User(username);
}
In Javascript I would then fetch the POJO like this:
// obtain user
var user = $('#user_input').val(); // this is a text input
$.getJSON("/users/"+user, function() {
//...
});
NOTE: My way of trying to achieve that could be wrong // insufficient // not optimal, so please feel free to also suggest some other way on how to do that.
Could you please give me an explanation along with a code example how what I need should be accomplished?
You can make different methods for your controllers.
For example:
#RequestMapping(value = "/users") and #RequestMapping(value = "/users/{username}") - there are GET methods. But for AJAX make controller with POST:
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.POST)
And JS will be smth like this:
// Send the request
$.post("/users/"+user, data, function(response) {
// Do something with the request
});
Another advice (if it's possible in your situation) - rename url for your rest. E.g. add word api into the url.
I have a .JSP file that has three forms on it whose fields are dynamically shown/hidden based on user interaction with Javascript/Jquery elements.The forms are spring forms sending their action to a URL that matches a controller.
The issue is that when I submit a form, and it does not validate, the URL the form submitted to stays in the URL. Then any action I take that is URL dependent is basically corrupted because the URL has the form action appended to it.
For example, if my .JSP's normal URL is /admin/, and my spring form is:
<form:form id="form" method="POST" action="${pageContext.request.contextPath}/admin/createUser" modelAttribute="User">
If validation fails my URL will now be /admin/createUser. If I am then taking some action using Javascript/Jquery the URL is no longer a valid way to navigate. I could just work around the URL but it just seems...un-ideal.
I have tried using redirects like: "redirect:/admin/", but spring validation will not work with this because you are basically just reloading the page.
Are there any best-practice or "elegant" solutions to this, or something really simple that I'm overlooking?
The redirection should probably be done on the client side anyways with a param of returnToURL, and as for error handling in Spring, you can register method as error handler to return proper error responses:
class YourController {
/**
* see #link{org.springframework.web.bind.annotation.ExceptionHandler}
*/
#ExceptionHandler(Exception.class)
public #ResponseBody String handleException(Exception e, HttpServletResponse rsp) {
// set the response status
return "{\"error\" : ...}"'
}
}
If you get error you can return error message to a custom error page to display the error message. or in your case you can return the admin page again..
here is some example code .
#RequestMapping(value = "/createUser" ,method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute User user) {
if(user == null){
Map<String, String> model = new HashMap<String, String>();
model.put("errorCode", "00");
model.put("errorText", "Error Message Here");
return new ModelAndView("error_page", model);
}
return new ModelAndView("Welcome_page");
}
This is my server-side code that adds data to db:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,UsersLanguage,OtherLanguage,Notes,Difficulty")] Word word)
{
if (ModelState.IsValid)
{
word.LastReviewed = DateTime.Now;
word.NextReview = DateTime.Now;
word.OwnerName = User.Identity.Name;
word.ReviewInterval = 0;
if (String.IsNullOrEmpty(word.OwnerName))
return View(word);
db.Words.Add(word);
db.SaveChanges();
return RedirectToAction("Create", new { addingSuccess = true });
}
return View(word);
}
I want to add some data to database using javascript. So I wrote this:
$.ajax({
type: "POST",
url: "/Words/Create",
data: { "UsersLanguage": questionToAdd, "OtherLanguage": answerToAdd }
}).success(function (data) {
console.log("Added");
$(this).parent().fadeOut(1000);
}).fail(function (data) {
console.error("cannot add word");
});
The problem is: this javascript code actually doesn't work. I'm getting error: "The required anti-forgery form field [...] is not present".
I think the reason is I have to send to server exactly the same object as server's "Word". OK, I could make variable in javascript that will look like "Word" class, but what if I once wanted to change "Word" class on my server? I would have to jump through all my scripts that send "Word" to server and change these js objects.
How to avoid that? How could I send some data without knowledge about how Word class is built? How can I send this "Word" to server without all data and make server to create missing variables?
You need to pass the AntiForgeryToken, you can do this creating a form with the tolken and pass it in you data:
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
#Html.AntiForgeryToken()
}
<script type="text/javascript">
$.ajax({
type: "POST",
url: "/Words/Create",
data: { "UsersLanguage": questionToAdd,
"OtherLanguage": answerToAdd,
"__RequestVerificationToken": $('input[name="__RequestVerificationToken"]'.val()}
}).success(function (data) {
console.log("Added");
$(this).parent().fadeOut(1000);
}).fail(function (data) {
console.error("cannot add word");
});
</script>
I think I found the problem, I had to send something like AntiForgeryToken and I've done this basically the same way like in best answer here:
include antiforgerytoken in ajax post ASP.NET MVC
I don't know if it's the best way, but actually it works ^^
You can not send an object without knowing how it is built.
Why would you change class?.
Generally, in any form creating object WORD in the MVC, should be built by
#Html.EditorFor (word => word.Id)
so how do you change the word class, you will also have to change all the forms. As for the problem: You can expand the object word, but if you not send new properties, will always null.
As for the script: if your form is created using EditorFor (), input element in HTML always have id attribute equal named property. You can write a nice generic js function that will retrieve all the fields from the form and on the basis of selectors build parameter "data" and send to the Controllers.