I have an administration panel where i can add users.
In insert form i have USERNAME, PASSWORD (no confirm-password), and some ordinary fields like name, surname,....
When i insert a new user, i want that hibernate will validate it:
username not null
unique username in db (with an unique index on table)
password not null (for now, no length controls)
until here, everything is working fine.
As in future i will store encrypted passwords,
in Edit form, i have normal editable fields like username, name,
but i didn't put password. instead i put new password field just in case i want to change it (not mandatory).
my edit form:
<!--
- username
-->
<div class="form-group">
<label class="col-sm-4 control-label">Username*:</label>
<div class="col-sm-8" ><input class="form-control" type="text" data-th-field="*{username}" placeholder="Username" th:errorclass="'has-error-input'"></input></div>
</div>
<!--
- password
- fingo password
-->
<div class="form-group">
<label class="col-sm-4 control-label">Password:</label>
<div class="col-sm-8" >******</div>
</div>
<!--
- new password
- (if i want to change it)
-->
<div class="form-group">
<label class="col-sm-4 control-label">New Password:</label>
<div class="col-sm-8" ><input class="form-control" type="password" id="password_new" name="password_new" placeholder="New Password"></input></div>
</div>
As you can see the password_new field is a normal tag, without using spring notation. I will retrieve this as parameter in controller and i will check if new password was written or no.
Now the problem: when i submit the form, i get validation error, as password is null.
I'm asking you this:
am i on a good way to do this?
is it possible skip a validation (password) in hibernate on update,
but set as mandatory on insert? and then update all fields except
password (you will see that i commented the line
userToUpdate.setPassword(user.getPassword());)?
could be better remove new password field from edit form, and change
password in a page where i update only the password?
is it a stupid / unsafe idea to set an hidden field called 'password' in edit form containing the encrypted password, so it will not give me validation errors? thinking for a second, i think it's really not a good idea, as someone can see encrypted password just looking the source code of the page. having a encrypted password "in clear" can be dangerous / unsafe?
User.java (model)
#NotNull(message = "PASSWORD cannot be null")
#NotEmpty(message = "PASSWORD cannot be null")
#Size(max = 50, message = "Max 50 char")
#Column(name = "password", length = 50)
private String password;
UserDao.java
#Override
public void updateUser(User user) throws UserNotFoundException {
User userToUpdate = getUser(user.getId());
userToUpdate.setUsername(user.getUsername());
//userToUpdate.setPassword(user.getPassword());
userToUpdate.setNome(user.getNome());
userToUpdate.setCognome(user.getCognome());
userToUpdate.setEmail(user.getEmail());
userToUpdate.setEnabled(user.getEnabled());
userToUpdate.setRole(user.getRole());
getCurrentSession().update(userToUpdate);
}
UserController.java
#RequestMapping(value = "/edit", method = RequestMethod.POST)
public String editingUser(#Valid #ModelAttribute User user,
BindingResult result, RedirectAttributes redirectAttrs,
#RequestParam(value = "action", required = true) String action,
#RequestParam(value = "password_new") String password_new
){
logger.info("IN: User/edit-POST: " + action);
List<Role> user_roles = RoleService.getRoles();
if (action.equals("abort"))
{
/**
* message
*/
String message = "Edit not saved";
redirectAttrs.addFlashAttribute("message", message);
redirectAttrs.addFlashAttribute("message_class", "alert-warning");
redirectAttrs.addFlashAttribute("user_roles", user_roles);
}
else if (result.hasErrors())
{
logger.info("User-edit error: " + result.toString());
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
redirectAttrs.addFlashAttribute("user", user);
/**
* as in my list page i have also the insert form,
* i need to populate the select
*/
redirectAttrs.addFlashAttribute("user_roles", user_roles);
return "redirect:/users/edit?id=" + user.getId();
}
else if (action.equals("save"))
{
try
{
logger.info("User/edit-POST: " + user.toString());
/**
* i see if i changed the password
*/
logger.info("new password: " + password_new);
if (!password_new.equals(""))
{
user.setPassword(password_new);
}
else
{
//nothing, or what?
}
/**
* now i can update the user in DB
*/
UserService.updateUser(user);
/**
* message
*/
String message = "User edited with success";
redirectAttrs.addFlashAttribute("message", message);
redirectAttrs.addFlashAttribute("message_class", "alert-success");
/**
* as in my list page i have also the insert form,
* i need to populate the select
*/
redirectAttrs.addFlashAttribute("user_roles", user_roles);
}
catch (UserNotFoundException e)
{
logger.info("User/edit-POST: id [" + user.getId() + "] not found");
String message = "User id [" + user.getId() + "] not found!";
redirectAttrs.addFlashAttribute("message", message);
redirectAttrs.addFlashAttribute("message_class", "alert-danger");
}
}
return "redirect:/users/list";
}
thank you very much
PS.
As it's my first application in spring / hibernate / java word, for now i'm storing plain passwords, in future i will encrypt them.
Sure!
Only if you remove the #Valid annotation. What you could do is to remove the annotation, then set all properties in the controller and before calling the DAO function validate the entity manually:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
It's up to you.
Yes, don't do that :)
•username not null
You can use #NotNull in model.
•unique username in db (with an unique index on table)
instead of doing at the time of inserting you can make an AJAX call when username field on form get out of focus to check entered user is exit or not.
While updating you want to update fields without updating password (if its blank)
First of all I would like to tell you that NotEmpty and NotNull annotation from domain model. I don't use the #NotNull and #NotEmpty validations in practice you can refer this article
You can use hibernates dynamic-update to update only those properties which is changed. Go Through this
You can use password field on the edit form and as per your idea you can validate if user enters something in password field then update is required other wise first fetch the object from DB set to updating object then call for hibernate to update the object.
Better Way
To change your models As you said you want to take users FirstName,LastName,other personal details that should be in Person model and all the login account related part in LoginAccount model
So your model becomes like :
Person
PersonId
FirstName
LastName
etc.
LoginAccount
LoginAccountId
UserName
Password
FK_PersonID
And you make viewmodel for new registration form and change password and edit profile functionalities to update details.
Related
Basically my question is very simple, I have a registration form with a constraint on field "login".
Table(uniqueConstraints=#UniqueConstraint(columnNames = { "login" }))
So, when I add a login which is already exists, an exception shows up: Duplicate entry 'MyLogin' for key 'UKew1hvam8uwaknuaellwhqchhb'.
I'm asking if there's any way in thymeleaf to just show a message saying that name already exists.
I would check if user already exists in table with the same username(I think your "login" is same as username for the user) before making any persistence transaction for new user, if username already exists in the table simply do not go forward and execute new user registration logic and let form controller return view with the Model object with the duplicate username attribute returned by service method.(you can use exceptions in service methods for better logic)
#RequestMapping("/registerNewUser")
public String showModel(#ModelAttribute UserDataTransferObject userDTO, Model model){
String existedUsername = service.createUser(userDTO);
if(existedUsername != null){
model.addAttribute("existedUsername",existedUsername);
}
return "registrationstatus";
}
createUser method checks if repository contains entry with the same username and if so returns that username as a string (Simple implementation).
in thymeleaf:
<div th:if="${existedUsername != null}" class="alert alert-danger notification" role="alert">
<p th:text="${existedUsername}"></p><p> already exists</p>
</div>
i use RemoteAttribute in my modelview for Check Instantly If Username Exists .
[Remote("ValidUsername","UsersManagement",ErrorMessage ="this usernaem is duplicate")]
public string Username { get; set; }
This idea is useful when inserting a new record, but prevents the update from being edited. because the usernaem is exists. What is the solution to that proposal?
Option 1 - Use Additional fields:
You can use the AdditionalFields argument to your remote validation attribute in your model and combine that with a hidden field in your view .
In your model class :
[Remote("ValidUsername", "Home", ErrorMessage = "this usernaem is duplicate", AdditionalFields = "PageType")]
public string Username { get; set; }
In your edit/create page , add hidden field inside the same form as the field your are validating :
<label asp-for="Username">Username</label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
<input type="hidden" name="PageType" value="Edit" />
Then on server side validation , you could get the Additional value(edit/create) and validate base on that , if it is edit ,just skip the validation :
[AcceptVerbs("Get", "Post")]
public IActionResult ValidUsername(string Username, string PageType)
{
if ("Edit".Equals(PageType))
{
return Json(true);
}
if (Username.Equals("21"))
{
return Json(false);
}
return Json(true);
}
Option 2 - Use different view model
You can also use different view model in create and edit pages.
I have a spring form for a complex object that contains a List of email address objects within a One-To-One relationship object that contains a One-To-Many to email addresses. The use-case is as follows: user wants to add new email addresses to the existing ones in the form. This is done via a pop up window and a jQuery that appends a new option element inside the select statement as such:
<option value="0">new.email#address.com</option>
All the prepopulated options in the loaded form of course already have an ID set, such as:
<option value="1">existing.email#email.com</option>
In my controller, the submitted form contains the ID field (1 for the prepopulated object and 0 for the 'new' email address), but the actual value in the form is null - i.e. first#email.com is not submitted with the form and bound to the emailAddress field in the EmailAddress object:
This code:
List<EmailAddress> emailAddress = batchJob.getDestinationExtras().getMailsInternal();
for (EmailAddress e : emailAddress) {
logger.info(e.getEmailAddressId() + " "+e.getEmailAddress());
}
Results into output:
1 null
0 null
This is not a problem for the existing objects because with the ID they can simply be retrieved from the database using hibernate either in the controller or already during the parse stage (I am using a formatter implementation for the EmailAddress object). But for the 'new' objects created dynamically by the user I have a problem because they do not yet exist in the database and thus the actual email address value can not be retrieved in this way.
What could be a solution to this problem?
The JSP tag is as follows:
<form:select multiple="true" path="destinationExtras.mailsInternal" id="internalMailsList">
<form:options items="${internalMailsList}" itemValue="emailAddressId" itemLabel="emailAddress" />
</form:select>
The relevant parts of the objects involved below - omitting other fields and ID fields where not relevant:
#Entity
#Table(name="batchjob")
public class BatchJob {
(...) id + other fields
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="destinationextrasid")
private DestinationExtras destinationExtras;
(...) getters and setters
}
#Entity
#Table(name="destinationextras")
public class DestinationExtras {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="emailaddressid")
private List<EmailAddress> mailsInternal;
}
#Entity
#Table(name="emailaddresses")
public class EmailAddress {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_EMAILADDRESSID")
#SequenceGenerator(name="SEQ_EMAILADDRESSID",sequenceName="SEQ_EMAILADDRESSID",allocationSize=1)
private int emailAddressId;
#Column(name="email_address")
private String emailAddress;
}
When it goes wrong, think about the low-level HTTP protocol ... JQuery is very clever, so is Spring, but they can only communicate through HTTP ! And when submitting a form containing a <select> field, the only thing that is sent is the value of the selected option(s).
So if you want Spring to use the text of the new email, you could try to put it in the value of the <option> field, like :
<option value="new.email#address.com">new.email#address.com</option>
but I think you will have to change all the select to systematically use the text as value and change it also in your controller, to avoid mixing integer ids and texts. Your options would then be :
<form:options items="${internalMailsList}" itemValue="emailAddress" itemLabel="emailAddress" />
You can also just add a hidden field with the help of jQuery, with the email text as value and use it if you find a value of 0, like that :
<input type="hidden" name="newmail" value="new.email#address.com"/>
<select ...>
<option value="0">new.email#address.com</option>
Personnally, I would prefere that second way, but it really depends on you controller structure.
I have an EF Code First model which I'm editing via an MVC page, with a particular field that always returns false with the [Required] data annotation. If I hard set a value right before validating, it still fails.
It's for a User object, of which I can configure if I'm using a username or email address as the 'username' property.
The model:
public class User {
[DisplayName("User Id")]
public int Id { get; set; }
[Required(ErrorMessage="Username is required")]
public string Username { get; set; }
[UIHint("EmailAddress")]
[Required(ErrorMessage = "Email is required")]
[EmailAddress]
public string Email { get; set; }
}
In my view, I'm only drawing the Username editor if it's required:
#if (#ViewBag.LoginMethod == "username") {
#Html.LabelFor(m => m.Username)
#Html.TextBoxFor(m => m.Username, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Username)
}
#Html.LabelFor(m => m.Email)
#Html.TextBoxFor(m => m.Email, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Email)
My controller:
[HttpPost]
public ActionResult Create(UserModel viewModel) {
ViewBag.LoginMethod = this.loginMethod.ToString();
var user = new User();
if (this.loginMethod == LoginMethods.Username)
user.Username = viewModel.User.Username;
else
user.Username = viewModel.User.Email;
user.Email = viewModel.User.Email;
user.FirstName = viewModel.User.FirstName;
user.LastName = viewModel.User.LastName;
user.Username = "TEST";
if (TryValidateModel(user) == false) {
this.FlashError("Validation Errors!");
return View(viewModel);
}
throw new Exception("here");
}
As you can see, I'm setting the User.Username property, based on the login method. For the sake of testing, I'm setting it to "TEST", right before validation.
The Username Required validation returns false, and I end up back in my view. I never get to the exception.
I have managed to make it work correctly, by rendering the Username editor on the page no matter what. As I have client side validation enabled, I can't submit the form without entering a value, and it works - even though the Username value is still "TEST" once validated.
I'm beginning to think TryValidateModel isn't the right function. Using ModelState.IsValid yields the same result - an incorrect Required fail.
First, I think you can use EditFor now, to avoid creating the trio of controls every time (label, textbox, validator).
Another thing that strikes me as weird is that your Model seems to be of type User (m.Email), but your action takes a UserModel (which contains a User). I am not sure why your code still works. Normally you could take directly a User as a parameter (so you won't have to copy the values by hand).
It's normal if ModelState.IsValid doesn't work. If your model is of type User, it will try to validate all POSTED properties, regardless of whether they are on the view. On the other hand, TryValidateModel SHOULD have worked in your scenario. It seems there's an added security feature there, which considers empty the properties for which there was no Edit control.
A workaround would be to create your custom model binder for the User object yourself (it's not that hard, you inherit IModelBinder, you override BindModel and call the base method. Afterwards, if the UserName is empty, then you add "TEST" or the correct value. Of course, you cannot leave it empty). Search about custom model binders in Asp.net MVC.
I hope that helps!
I'm new to Entity Framework, and I have to update a record in my database. I used the "Edit" example generated by the MVC3 framework and tried to customize to my needs.
I have a password field and before submit it to update I need to encrypt it with MD5. All process is running ok, except for the db.SaveChanges(); it saves the data posted by the form. Doesn't matter if I try to change the password, the framework just ignore that and save the data as it was posted in the form.
My .cshtml file:
<div class="editor-label">
#Html.Label("password", "Senha")
</div>
<div class="editor-field">
#Html.Password("password")
</div>
My method:
[HttpPost]
public ActionResult Editar(FormCollection form)
{
var newPassword = form["password"];
var email = Session["email"].ToString();
UserSet user = db.UserSet.SingleOrDefault(m => m.Email == email);
if (ModelState.IsValid)
{
//Changing password
user.Password = Crypto.CalculateMD5Hash(newPassword);//this line is ignored
TryUpdateModel(user);
db.SaveChanges();
return Redirect("~/Home/Mural");
}
return View(user);
}
What am I missing?
Your line
TryUpdateModel(user);
Will overwrite anything you've done on your model prior.
Change the order to
TryUpdateModel(user);
user.Password = Crypto.CalculateMD5Hash(newPassword);//this line is ignored
And it'll probably work.