The problem I'm having right now it's that ASP.NET MVC validates the request object, user in this case
public ActionResult Edit(User user)
What I want to do is that if the user's password is left blank, do not update the password, just use the old password, but if it's set, update it.
The problem is that the framework complains that user does not has a password, even if I update the user object, it complains
public ActionResult Edit(User user)
{
user.Password = "Something";
// more code...
}
Apparently it does the validation on the request object, is there a way I can skip the validation in this case, or at least delay it until I finished modifying the user object?
This is the full method code
[HttpPost]
public ActionResult Edit(User user)
{
if (string.IsNullOrEmpty(user.Password))
{
var oldUser = db.Users.Single(u => u.Id == user.Id);
user.Password = oldUser.Password;
}
try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View(user);
}
}
The model state will still be invalid even after you set the password to something. After you do that try clear the model state using ModelState.Clear(); Or modify the ModelState accordingly i.e clear the error state only for password property ModelState.Remove("Password");
[HttpPost]
public ActionResult Edit(User user)
{
if (string.IsNullOrEmpty(user.Password))
{
var oldUser = db.Users.Single(u => u.Id == user.Id);
user.Password = oldUser.Password;
ModelState.Clear(); // or change the state accordingly (ModelState.Remove("Password");)
}
.......
}
From your posts it looks like you are ware of the [Required] attribute on the password field. I suggest you force javascript validation and let the user know that they cannot update the password to be blank. Or if you are updating User information in a form that doesn't include password create a new viewmodel or temp model that does not have the password field and the model is specific to that form. Post to this model and bind to this model. Then update the actual user model by using the data from the temp user model. This will be better practice to follow.
The problem here isn't related to the server, it's the client side. I assume you have a Required attribute on the Password property so when MVC generates the view for the model it automatically generates client-side validation AKA Unobtrusive validation.
You could write your own custom attribute to handle your particular scenario, however, that would mean you would also need to write your own client-side validation. The simple fix here is to remove the Required attribute and handle the validation on the server side.
What I want to do is that if the user's password is left blank, do not update the password, just use the old password, but if it's set, update it.
If that is the case then your logic is actually wrong anyway. You shouldn't have the Required attribute on the Password as your rules dictate that it can be empty.
Related
So in my .net application I have three controllers
Home
Kitchen
Institution
When the user logs in, I get what controller the user can go. I have defined some roles and using those I get which user needs to go where.
So for example if a user Bob is a cook. When Bob logs in, he is taken to the kitchen dashboard. But if Bob types in the URL ../Home/Dashboard there is nothing stopping him for going in there. What should be done in order to restrict Bob accessing any other url?
Also, when the user logs in should I store the information about his role in session?
What is the best practice for this purpose?
You can handle all your requirements in an HttpPost Login Action. See comments for further instructios.
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model, string returnUrl)
{
// check first for field validations
if (!ModelState.IsValid)
return View(model);
// validate user agains database
var user = FindUser(model.UserName, model.Password);
if (user == null)
{
ModelState.AddModelError("", "Invalid username or password.");
return View(model)
}
// user is valid, sign in assuming forms authentication, however it's
// best practice to abstract following statement by use of some kind of authentication
// manager (refer to OWIN framework for a better approach).
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
// when returnUrl param is provided
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
// following 'if' - condition dependent on your domain models.
if (user.IsCook)
return RedirectToRoute(/*kitchen route*/);
else
return RedirectToRoute(/*dashboard*/);
}
I have a CompanyCode that is readonly and has to be displayed to the user. I have no say in preventing this from being the primary key. How can I prevent the user from updating this field at all on the server-side?
I've tried the following:
company.Property(s => s.CompanyCode).IsModified = false;
However, this throws an error that the key field can't be modified. Is there an easy way to prevent the user from changing the key field or telling Entity Framework not to attempt to update this field?
You should always, always, always, update the entity pulled fresh from the database with the posted values, rather than saving the posted version directly to the database. Therefore, all you need to do is pass the id in the URL:
public ActionResult Edit(int id, CompanyViewModel model)
{
var company = db.Companies.Find(id);
if (company == null)
{
return new HttpNotFoundResult();
}
if (ModelState.IsValid)
{
// map `model` properties to `company` properties
db.Entry(company).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
If I'm understanding you correctly, and you must show this CompanyCode on the UI, just display the company code using #Html.DisplayFor(model => model.CompanyCode) on your View, instead of EditorFor which renders a text box in which case, if you render a textbox, this value would be able to be updated. Does this make sense
EDIT
If you need this to post back to the server, make it #Html.EditorFor(model => model.CompanyCode) and simply disable this control via JavaScript.
I have an ASP.NET MVC 4 project which I have successfully connected to a MySQL database. I have done this by adding a ADO.NET/EntityFramework class which created a Model.edmx object.
Within the database, I have created a table called user which holds what you should expect in a User table such as Email, UserName, Password, FirstName. etc etc.
I have created some dummy records and added the following code to the Login method within the AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var database = new Database();
user user = database.SelectByUserName(model.UserName).FirstOrDefault<user>();
var hash = Utilities.HashPassword(model.Password, user.Salt);
if (hash == user.Password && WebSecurity.Login(user.UserName, user.Password))
{
//Correct Login Details!
RedirectToAction("About", "Home");
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
For some reason, the WebSecurity.Login method returns false and the user isn't redirected to the Home page.
Why is it returning false? What am I missing and how would the WebSecurity.Login even know what credentials are required i.e. How does it even know that it should look inside the user table which I created?
WebSecurity doesn't default to looking at your database, it will actually make it's own tables using the DefaultConnection that is defined in Web.Config. To work around this you need to add a new connection string Web.Config and then during app initialization force WebSecurity to look at that connection.
The easiest way to accomplish this, assuming you have a MySQL specific connection string in your Web.Config named "AccountConnection" is by adding the following to your Application_Start()
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
And then you'll need the following fields and function:
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
// Overload is: Web.Config Connection string by name, user table name, user id column name, user name column name, auto create missing tables
WebSecurity.InitializeDatabaseConnection("AccountConnection", "UserProfile", "UserId", "Email", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The Membership database could not be initialized.", ex);
}
}
}
Whether you can make WebSecurity work with MySQL I have no idea, though I believe I've read some place that it is supported.
Note: the UserContext should have been auto generated when you installed WebSecurity into your solution. If not it's a CodeFirst model that you can easily add.
There are one of two reasons your code will not work. Understand that WebSecurity and SimpleMembershipProvider (assuming you are using it) uses PBKDF2 algorithm to populate the password field when you call WebSecurity.CreateUserAndAccount or WebSecurity.CreateAccount.
So Either:
You did not use one of these two methods to create the user, in which case WebSecurity.Login will almost always fail (99.99%).
or
You did use one of the methods above and the code in Utilities.HashPassword() (which seems redundant since the Create Account methods listed above hash passwords anyway...) does not hash the password Exactly the same way WebSecurity does so hash == user.Password will always fail.
Maybe that's not exactly the solution i need, but this is what i want to do:
i have a company registration form, and each company needs an administrative user. an administrative user may manage multiple companies, so in the company registration form, you can choose an existing user from a dropdown.
the company view model looks something like this:
public class CompanyViewModel {
[Required]
public string Name { get; set; }
// other properties...
public UserViewModel Administrator { get; set; }
public IEnumerable<UserViewModel> AvailableUsers { get; set; }
}
and the user view model looks like this:
public class UserViewModel {
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
// other properties...
}
in the company registration view:
<div><input type="radiobutton" name="chooseuser" id="existing"/>Choose an Existing User:</div.
<div>#Html.DropDownListFor(m => m.Administrator.Id, Model.AvailableUsers.Select(u => new SelectListItem { Text = string.Format("{0} - {1} {2}", u.UserName, u.FirstName, u.LastName), Value = u.Id.ToString() }), "<Choose existing user>", new { id = "existingusers" })
</div>
<div><input type="radiobutton" name="chooseuser" id="createnew"/>Create a new User:</div>
<div><label>Username:</label> #Html.EditorFor(m => m.Administrator.UserName)</div>
Through javascript, based on radio button selection, the dropdown list is disabled and the new user form shown, or the new user form is hidden and the dropdown list is enabled.
The problem is in the controller Save action after you press save, ModelState.IsValid is false if an existing user is chosen and no data is filled in on the form. If the user chooses to enter a new user, validation succeeds.
What is the best way to handle this?
One option is to load all data for all users into data structures in javascript, and when the value changes on the existing user dropdown, the hidden "create new" form fields can be populated. But this seems lame since passwords would be sitting the html in plain text. i can get fancier and use ajax for a "create new" form and populate a user id on the original form once the new user is saved, but i'd like to keep it all in one form if possible.
Seems liked i'd ideally be able to load the existing user data from the db and populate the model state in the controller Save action, but writing this code manually (even using reflection) seems sloppy. It would be nice if there was a built in method to do this.
Any ideas?
Thanks.
That's a typical scenario which perfectly illustrates the limitations of declarative validation (a.k.a Data Annotations). In order to handle it you could write a custom validation attribute which will be applied to the CompanyViewModel instead of individual properties and will allow you to perform the validation logic based on which radio button the user choose (btw you will need a property on your view model which will represent the radio button selection). The problem with model validators is that you might have some hard time handling the error highlighting.
That's one of the reasons why I use FluentValidation.NET instead of Data Annotations. This allows me to have the validation logic away from the model and done in an imperative way. It also allows me to have conditional validators which apply based on the values of some properties on the view model (in this case this would be the radio button selection).
You may want to consider a custom Modelbinder.
Here's some sample code from my site - this is part of a checkout page for a shopping cart - the user can enter an address but for US StateCd is sent and for non US StateOrProvince is sent. So we look at the country and remove any model errors for the other property that doesn't apply.
I think this is very similar to what you're describing (that you have two scenarios that need different rules but you want to use the same model).
The important code here is bindingContext.ModelState.Remove(...)which removes the model state and allows IsValid to return true.
public class AddressModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
// get the address to validate
var address = (Address)bindingContext.Model;
// remove statecd for non-us
if (address.IsUSA)
{
address.StateOrProvince = string.IsNullOrEmpty(address.StateCd) ? null : CountryCache.GetStateName(address.StateCd);
bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateOrProvince");
}
else
{
address.StateCd = null;
bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateCd");
}
// update country
address.Country = CountryCache.GetCountry(address.CountryCode, true).Name;
// validate US zipcode
if (address.CountryCode == "US")
{
if (new Regex(#"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).Match(address.ZipOrPostal ?? "").Success == false)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".ZipOrPostal", "The value " + address.ZipOrPostal + " is not a valid zipcode");
}
}
// all other modelbinding attributes such as [Required] will be processed as normal
}
}
Note: You need to register this modelbinder in global.asax. The modelbinding component is smart enough to let you create differnt model binders for any part of your model if it contains different objects.
ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();
Hope this helps. I think this applies to your situation.
Rails has a very convenient uniqueness validation.
ASP.NET MVC doesn't.
I need to make sure that the e-mail address a user has entered hasn't been registered by anyone yet.
I can see only one way of doing this kind of validation: create a new data context object in the UniqueAttribute class.
But I'm afraid that wasting memory on a new data context object just for one validation is dangerous.
Am I wrong? Is there a better way to do that?
Update
This is what I got so far
public class UniqueEmailAttribute : ValidationAttribute {
public override bool IsValid(object value) {
DataContext db = new DataContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.Email == (string)value);
return userWithTheSameEmail == null;
}
}
// Usage
[UniqueEmail(ErrorMessage="This e-mail is already registered")]
public string Email { get; set; }
There are two problems.
It would be good to have just one UniqueAttribute class, not separate classes for e-mails, usernames etc. How can I do that?
Creating a new data context every time you need to validate a single attribute.
SOLUTION
So in the end I created a unique constraint on the table and now I just have to intercept SqlException in Users repository. Works great and is probably more efficient than searching for the same node in the whole table. Thanks!
Mvc 3 Relaease candidate has new New Validation Attributes as a remotevalidation -where you can register a method for validation on clientside(jquery).
see below example-
RemoteAttribute
The new RemoteAttribute validation attribute takes advantage of the jQuery Validation plug-in's remote validator, which enables client-side validation to call a method on the server that performs the actual validation logic.
In the following example, the UserName property has the RemoteAttribute applied. When editing this property in an Edit view, client validation will call an action named UserNameAvailable on the UsersController class in order to validate this field.
public class User {
[Remote("UserNameAvailable", "Users")]
public string UserName { get; set; }
}
The following example shows the corresponding controller.
public class UsersController {
public bool UserNameAvailable(string username) {
return !MyRepository.UserNameExists(username);
}
}
Mvc 3
UPDATE
public bool UserNameAvailable(string Propertyname)
{
if (Request.QueryString[0]= "UserName")
{
//validate username
}
elseif (Request.QueryString[0]= "Email")
{
//Validate Email
}
}
ASP.Net does have a feature that can automatically check the uniqueness of a user's email address when a user registers. It is the ASP.Net Membership service and you can use it to do what you want even if you don't use all of the features of it.
If you are not using the full Membership feature in your MVC application, then all you need to do is use
Membership.FindUsersByEmail(emailYouAreLookingFor);
If any values come back, you know that the address is not unique. If you ARE using the Membership service to create users, then the Membership service will check AUTOMATICALLY and return a code to you if the user's email address is not unique.
The Membership service sits in the System.Web.Security area so you would need a
using System.Web.Security;
reference in your controller.
Here is an example
MembershipCreateStatus createStatus = MembershipService.CreateUser(UserName, Password, Email);
if (createStatus == MembershipCreateStatus.DuplicateEmail)
{
//do something here
}
else
{
//do something here
}
I hope this helps!
The right way to make a generic remote unique validator in MVC can be found in this MVC forum. by counsellorben. It's based on my MVC unique remote validator article http://msdn.microsoft.com/en-us/library/gg508808(VS.98).aspx
A foolproof way of doing this is to create a validation attribute that would query the database for the email address. It would certainly add latency.
An alternative would be to create a unique constraint on the table and intercept SqlException.