1

The Problem

  • I have a pre-defined [StringLength()] and [RegularExpression()] constraint on my Code-First Model.
  • The [StringLength()] requirement is 8-16 characters
  • The [RegularExpression()] constraint for the password is in a different format from the encrypted password.
    • The password is encrypted before being shoved into the database.
    • The encrypted password is 70 characters in length, which is higher than the 8-16 character limit defined in the Model.
  • I am required to use e.Encrypt(), meaning I cannot use the default ASP.NET hashing algorithm.

I've searched high and low, but have yet to find the correct answer.

My Code

I have a function that allows a user to register an account. It looks like this:

[HttpPost]
public ActionResult Register([Bind(Include="Username,Password,EmailAddress")] UserModel user)
{
    if (TryUpdateModel(user))
    {
        // Set password to a different format than Model's designated Regex
        user.Password = e.Encrypt(user.Password);

        context.Entry(user).State = EntityState.Added;
        context.SaveChanges();
        return RedirectToAction("Login", "Account");
    }
    return View();
}

In my UserModel.cs file, I have the following constraints:

    [Required]
    [DataType(DataType.Password)]
    [StringLength(16, MinimumLength = 8, ErrorMessage = "Must be between 8 and 16 characters.")]
    [RegularExpression("^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]", ErrorMessage = ErrorMessage_PasswordRegex)]
    public string Password { get; set; }

Again, the hashed password is in a completely different format. Therefore, I cannot update it because it throws a DbEntityValidationException. This [RegularExpression()] is for the format of the user's password. I need to be able to bypass or suspend the password's Regex constraint.

What I have done to remedy this

I've removed the [RegularExpression()] requirements, and upped the [StringLength()] to 70, which is the length of my password hash.

However, I don't want to allow users to input 70 characters of text. This seems like a cheap hack, and I feel that there should be a better way to do this. Any ideas?

oOo
  • 130
  • 7
  • 1
    may i ask, are you using the UserModel to bind? If so, may i sugest you have a view model for it? that way you can have the 70 character password on the model, but the required length on the view model for the controller – C-JARP Apr 10 '15 at 16:37
  • I'm not sure what you mean by using the UserModel to bind? I apologize; I've only recently picked up MVC. – oOo Apr 10 '15 at 16:44
  • 1
    Don't apologize, it's ok. Please read this post, it explains quite well what i mean, and it will probably shine light in your path :) http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc – C-JARP Apr 10 '15 at 16:50
  • Use a different class to accept user input (the view model), then map those values to your `UserModel` in your controller. Put your input validation rules on your view model -- now you can have different validation rules for user input than what the database requires (because you have programmatically transformed those values). – Jasen Apr 10 '15 at 16:51
  • Thank you. I've read that page, and am currently trying to fiddle with it, but the example provided is a little confusing to me. Is it possible to get a code sample of what you're talking about? – oOo Apr 10 '15 at 17:03

1 Answers1

0

Here's an example:

We've got different requirements for user input than what our database requires. We might need more user input which we will programmatically act upon.

The EF model

public class UserModel()
{
    [Key]
    public string Id { get; set; }

    public string Name { get; set; }

    [Required, StringLength(70)]
    public string Password { get; set; }
}

Now here's the class we use to capture user input

public class UserViewModel()
{
    [Required]
    public string Name { get; set; }

    [Required]
    [RegularExpression("^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]", ErrorMessage = ErrorMessage_PasswordRegex)]
    public string Password { get; set; }

    [Required]
    public string ConfirmPassword { get; set; }
}

Now we transform and map values.

The user won't know what to use for Id and the database doesn't have need for ConfirmPassword. We can also transform what the user originally entered as a password.

[HttpPost]
public ActionResult Register(UserViewModel model)
{
    if ((ModelState.IsValid) &&
        (model.Password == model.ConfirmPassword))
    {
        // map to EF model
        var user = new UserModel
        {
            Name = model.Name,
            Password = e.encrypt(model.Password)  // transform, encrypt, whatever
        };

        db.Users.Add(user);  // use EF model to persist
        ...

        return RedirectToAction("Login", "Account");
    }
    return(model);
}
Jasen
  • 14,030
  • 3
  • 51
  • 68
  • Wow, thank you so much! It seems that I was close. The only problem was I inverted the `[RegularExpression()]` order. I had the `UsersModel` class trying to validate using the aforementioned pattern, whereas the `UsersViewModel` class was trying to accept a string length of `70`. I've accepted your answer. If you found my question informative and fitting for this website, please throw me an upvote so I can begin upvoting helpful comments and answers. Thank you. – oOo Apr 10 '15 at 17:58