Edit user removes password - ASP.NET MVC - asp.net-mvc

I am trying to edit the data for one of my users. However, whenever I edit something, the passwords which are hidden, are also being changed and apparently set to null, which render the user unable to log in next time he wants to login. I know that I might have been able to solve the issue by using ViewModels, but im trying to do it without.
Model
public class User : IdentityUser
{
[Display(Name = "First name")]
public String FirstName { get; set; }
[Display(Name = "Last name")]
public string LastName { get; set; }
public string Email{ get; set; }
}
Please notice that the User-class extends from IdentityUser which holds password variables.
Edit in Controller
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "FirstName,LastName,Email,PhoneNumber")] User user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
View for Edit
<div class="form-horizontal">
<h4>User</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PhoneNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PhoneNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PhoneNumber, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
It is my understand that the Bind-parameter in the Edit-method either whitelist or blacklist the variables for editing. So for that reason i removed all of the values that shouldnt be edited by the user in Bind.

There are a couple of problems here. First, don't present these fields to the user in the first place. I can't imagine a reason why a user should be able to edit their "locked out" status, or their hashed password. Only include in the UI the fields which the user should actually be modifying. Hell, even this has horrible idea written all over it:
#Html.HiddenFor(model => model.Id)
You're not only allowing the user to edit everything about their user record, but you're also allowing them to specify another user record to edit. So any user in the system can completely edit any other user in the system.
Hopefully you see how this is a bad thing :)
Now, you can (and often must) include the identifier in a hidden field. The above problem is mainly bad because of what else you're doing:
db.Entry(user).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
You are completely and implicitly trusting whatever the user sends you to be a whole, correct, and complete record for the database. And replacing whatever existing record is there with whatever the user sends you. That's... not good.
This approach can work for some models, but certainly not sensitive ones like user security data.
Instead, fetch the existing record and only edit the necessary fields. Something like this:
var existingUser = db.Users.Single(u => u.Id == currentUserId);
existingUser.FirstName = user.FirstName;
existingUser.LastName = user.LastName;
// etc.
db.SaveChanges();
Notice that I used an otherwise undefined variable called currentUserId. Do not use model.Id, because again that's allowing the user to specify which other user they want to edit. Determing the current user ID by their current logged in session, not by what they send in the form. However you currently identify your users. (User.Identity?)
In short...
Only let the user see/edit what they're allowed to
Validate in the save action that the user is allowed to edit that data (never assume that they must be allowed to simply because they previously opened the page)
Only update the values meant to be updated in that operation, don't just wholesale replace an entire record of sensitive data

Related

Input field always sending null value

I have 3 input fields(User name, email & password) in my registration form. All of them are mandatory. But it looks like User Name field is always sending null value to controller and I'm getting unexpected validation message. Any help would be appreciated. P.S. rest 2 fields are just fine
HTML:
#using (Html.BeginForm("Create", "Registration", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Registration Form</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UserName, "", new { #class = "text-danger" })
</div>
</div>
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Email,Password")] UserRegistration userRegistration)
{
if (ModelState.IsValid)
{
db.UserSet.Add(userRegistration);
db.SaveChanges();
return View("Index");
}
return View(userRegistration);
}
I suppose that you need to change the Bind annotation to include a UserName and not a Name property
public ActionResult Create([Bind(Include = "UserName,Email,Password")] UserRegistration userRegistration)
When you use the Bind Attribute you define a list of property names for which the mvc engine should allow (include) the binding. The UserRegistration model has clearly a property named UserName and this property is not allowed in the binding list of names

ViewBag message is changed even when a form has errors

I have a simple contact form that is using a model for it's fields, everything seems to work but the ViewBag message gets changed regardless if there are validation errors or not, user validation prevents this but I also need the HttpPost action to set the message based on if the the form was filled correctly.
I tried using if(ModelState.IsValid) but it doesn't seem to work. I realize I can probably manually check each variable in the home to see if it's empty, but that won't really tell me if it's valid or the post was returned with errors, is there a build in method for this?
ContactFormModel.cs
namespace TestApplication.Models
{
public class ContactFormModel
{
[Required]
[Display(Name = "Name")]
public string name { get; set; }
[Required]
[Display(Name = "Phone")]
public string phone { get; set; }
[Required]
[Display(Name = "Message")]
public string message { get; set; }
}
}
HomeController.cs
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
ViewBag.Message = "Thank you. Your message has been sent.";
return View();
}
Contact.cshtml
#model TestApplication.Models.ContactFormModel
#{
ViewBag.Title = "Contact";
}
<h2>#ViewBag.Title.</h2>
<h3>#ViewBag.Message</h3>
<p>Use this area to provide additional information.</p>
#*#if (!IsPost)
{
#Html.EditorForModel(Model)
}*#
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.name, "", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.phone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.phone, "", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.phone, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.message, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.message, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.message, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
The pattern for doing what you want is to check ModelState.IsValid. If it's valid continue processing, if not return the view with the existing model contents to give the user a chance to correct their error(s).
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
if (ModelState.IsValid)
{
ViewBag.Message = "Thank you. Your message has been sent.";
return View();
}
else
{
return View(contactForm);
}
}
Having said that, you should consider using a PRG (post-redirect-get) pattern. By returning the same view in the HttpPost version of the method you open yourself up to repeated posting of the data. If the user hits Refresh in their browser it will repost the data they just posted (after popping up a dialog that most non-technical users will never understand). You must have a HttpGet version that delivers the view in the first place, you should redirect to that on success. You'll have to switch to using TempData instead of ViewBag because the ViewBag won't survive the redirect.
[HttpPost]
public ActionResult Contact(ContactFormModel contactForm)
{
if (ModelState.IsValid)
{
TempData.Message = "Thank you. Your message has been sent.";
// Assumes there is a Get version of the Contact action method
return RedirectToAction("Contact");
}
else
{
return View(contactForm);
}
}

ASP.NET MVC 5 Scaffolding not creating elements for enum property

This is probably a newbie question, as I'm quite new to ASP.NET MVC 5. When I tell Visual Studio to add a View based on my ViewModel class, it completely skips properties defined like public EnumName? PropertyName { get; set; } and does not create any #Html.EditorFor calls for it.
However, if I manually add the call #Html.EditorFor(model => model.PropertyName, new { htmlAttributes = new { #class = "form-control" } }) I get exactly what I expect -- a dropdown which is empty by default. Should scaffolding not do this by itself?
My understanding is that this is supposed to be supported in the current version of ASP.NET MVC. Am I wrong about that, or am I missing something? Help or advice is greatly appreciated.
Thanks in advance!
These are the ASP.NET products installed:
ASP.NET and Web Tools 12.4.51016.0
ASP.NET Web Frameworks and Tools 2012.2 4.1.21001.0
ASP.NET Web Frameworks and Tools 2013 5.2.21010.0
Edit for sample code:
Here is a small section of the view model. There are 170 different properties, almost all of them nullable-Enum type.
public partial class MedicalHistoryViewModel
{
public YesNo? Cancer { get; set; }
public MedicalHistoryDiagnosed? CancerDiagnosed { get; set; }
public YesNoUnsure? CancerIndustrialInOrigin { get; set; }
public YesNo? Diabetes { get; set; }
public MedicalHistoryDiagnosed? DiabetesDiagnosed { get; set; }
public YesNoUnsure? DiabetesIndustrialInOrigin { get; set; }
public YesNo? HeartDisease { get; set; }
//...
[Display(Name = #"Do you attribute the sleep disturbance to pain, anxiety and/or depression, or to other factors?")]
[DataType(DataType.MultilineText)]
public string SleepDisturbanceAttributedToComments { get; set; }
[Display(Name = #"Other (please specify)")]
[DataType(DataType.MultilineText)]
public string ParentsGrandparentsMedicalHistoryComments { get; set; }
}
Here is the complete output I get from Scaffolding. As you can see, it has completely ignored all enum properties.
#model QmeSurveyApp.ViewModels.MedicalHistoryViewModel
#{
ViewBag.Title = "EditMedicalHistory"; }
<h2>EditMedicalHistory</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>MedicalHistoryViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SleepDisturbanceAttributedToComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SleepDisturbanceAttributedToComments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SleepDisturbanceAttributedToComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SiblingsCousinsMedicalHistoryComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SiblingsCousinsMedicalHistoryComments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SiblingsCousinsMedicalHistoryComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ParentsGrandparentsMedicalHistoryComments, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ParentsGrandparentsMedicalHistoryComments, new { htmlAttributes
= new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ParentsGrandparentsMedicalHistoryComments, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div> }
<div>
#Html.ActionLink("Back to List", "Index") </div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval") }
But, if I add this block manually, I get exactly what I want: a drop-down which is empty by default, with my full pick list as the choices.
<div class="form-group">
#Html.LabelFor(model => model.Cancer, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Cancer, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Cancer, "", new { #class = "text-danger" })
</div>
</div>
You don't mention it, but I'm guessing you're not using Entity Framework.
I had a similar situation in an MVC project I was working in without EF. I had a POCO with a property that was an enum, and it was being completely skipped by the scaffolding engine. I even tried overriding the T4 templates with my own CodeTemplates and that's when I noticed the ModelMetadata.Properties collection didn't even contain my enum property.
I finally got it to work just by adding an empty Code First Entity Data model to the project. Doing that adds the Data context class textbox to the Add View scaffold item, and the resulting scaffolded view now includes my enum properties. This seems like a bug to me.

Html.EditFor passing in a new value MVC

Ive been looking around trying to figure this out but have had no luck.
What I want to do is pass the UserId of the person that is logged in, which I already have in UserID from my model. I have the code below and need to pass in UserID instead of last_update_by_IN.
<div class="form-group">
#Html.LabelFor(model => model.last_update_by_IN, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.last_update_by_IN)
#Html.ValidationMessageFor(model => model.last_update_by_IN)
</div>
</div>
I tried
<div class="form-group">
#Html.LabelFor(model => model.last_update_by_IN, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserID)
#Html.ValidationMessageFor(model => model.last_update_by_IN)
</div>
</div>
but that was displaying the correct UserID but passing 0 to the database. Let me know if I need to clarify things more. Thanks
here is the post in my controller, forgot to add this sorry
public ActionResult Edit([Bind(Include="column names here")] User_Accounts user_accounts)
{
if (ModelState.IsValid)
{
db.Entry(user_accounts).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.user_type_id_IN = new SelectList(db.User_Type, "userId", "userType", user_accounts.user_type_id_IN);
ViewBag.userId = new SelectList(db.Userinfo, "userId", "pinId", user_accounts.user_id_IN);
return View("Edit", user_accounts);
}

Does the name of parameter have to be model?

Hit a strange issue where my model is not binding and shows up on the controller as null.
I have a form doing a httppost. My breakpoint in the controller is hit and the parameter I expect to be my model is null.
Looking at some example code on another page that works, I copied and pasted it and the only difference was the name of the parameter was 'model' instead of message.
View
#model Site.Models.ContactMessage
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ContactMessage</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Message, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Message, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Message, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.To, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.To, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.To, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
public ActionResult Contact()
{
return View();
}
[HttpPost]
public ActionResult Contact(ContactMessage message)
{
var m = message;
return View();
}
and it worked. I thought I must have entirely missed something about naming convention. Found you can use Bind, from reading a heap of other posts, to change the prefix like;
public ActionResult Contact([Bind(Prefix = "model")] ContactMessage message)
but that didn't work, still null. Going to rename it to model so it works and I can move on but would like to know why it's not binding if not called model.
public ActionResult Contact(ContactMessage message)
Changed back to this as above but still returns a null.
Interestingly, if I open up another MVC app, that one has whatever parameter names I want and works fine. It's using an older version of MVC 5 (not updated it yet but I will do that and see if anything happens. I don't expect it will.)
Your problem is that you model contains a property named Message and you also have a parameter named message The DefaultModelBinder reads the form values which will include message = "someTextValue" and searches for model properties that have the name message. It finds the one in you model and sets it value (all OK so far) but then it finds another one (your parameter) and tries to set the value of a complex object string value (in effect ContactMessage message = "someTextValue";) which fails so the model becomes null

Resources