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.
Related
I'm using ASP.NET MVC3, and I've got a view-model with several properties, some of which are for display to the user, and some of which are used as inputs from the user and may have default values. I'm using the same view-model for GET requests (the only parameter is an identifier for what to get) and for posts, which take the entire view-model as a parameter in the action. My controller populates the view-modelwith entities retrieved through a business logic layer that pulls entities from an NHibernate session.
Is it better to put hidden inputs in the view for all of the read-only fields so they will be present if the page is rendered after a post with an invalid view-model, or is it better to only use inputs that the user really supplies the data and reload the rest of the backend and merge it in?
Thanks!
edit:
And why?
edit:
The controller generally looks something like this:
public class MyController : BaseController /* BaseController provide BizLogic object */
{
[HttpGet]
public ActionResult EditSomething(Int32 id)
{
MyDomainObject = base.BizLogic.GetMyDomainObjectById(id);
MyViewModel model = new MyViewModel();
model.Id = id;
model.ReadOnly1 = MyDomainObject.Field1;
model.Readonly2 = MyDomainObject.Field2;
model.UserInput3 = MyDomainObject.Field3;
model.UserInput4 = MyDomainObject.Field4;
return View(model);
}
[HttpPost]
public ActionResult EditSomethingMyViewModel model)
{
PerformComplexValidationNotDoneByAttributes(model);
if (ModelState.Valid)
{
BizLogicSaveTransferObject transferObject =
new BizLogicSaveTransferObject();
transferObject.Id = model.Id;
transferObject.Field3 = model.UserInput3;
transferObject.Field4 = model.UserInput4;
base.BizLogic.SaveDomainObject(transferObject);
return RedirectToAction("EditSomething", new { id = model.Id });
}
else
{
#if reload_non_input_fields_from_db
MyDomainObject = base.BizLogic.GetMyDomainObjectById(model.Id);
model.ReadOnly1 = MyDomainObject.Field1;
model.Readonly2 = MyDomainObject.Field2;
#endif
return View(model);
}
}
}
The view looks something like this:
# Html.BeginForm();
${Html.ValidationSummary()}
<p>ID: ${Model.Id}</p><input type="hidden" name="${Html.NameFor(m => m.Id)}" value="${Model.Id" />
<p>Read Only One: ${Model.ReadOnly1}</p><!-- uncomment if not reload_non_input_fields_from_db <input type="hidden" name="${Html.NameFor(m => m.ReadOnly1)}" value="${Model.ReadOnly1}" />-->
<p>Read Only Two: ${Model.ReadOnly2}</p><!-- uncomment if not reload_non_input_fields_from_db <input type="hidden" name="${Html.NameFor(m => m.ReadOnly2)}" value="${Model.ReadOnly2}" />-->
<p>Input Three: ${Model.UserInput3}</p><input type="hidden" name="${Html.NameFor(m => m.UserInput3)}" value="${Model.UserInput3}" />
<p>Input Three: ${Model.UserInput4}</p><input type="hidden" name="${Html.NameFor(m => m.UserInput3)}" value="${Model.UserInput4}" />
# Html.EndForm();
There's no sense in putting any inputs into the page if they are read-only (except for the unique record ID field, of course). As you wrote, merge the fields that the user is allowed to modify.
You'll need to merge the fields either way; for read-only fields, those should never be over-written based on data you sent to the client, and assume will come back to you the same. Even if you make the inputs "hidden", they're not really hidden; they can easily be modified by anyone who knows how to use Firebug, for example.
I'm not familiar with the Spark view engine but it looks like your form only displays information and has hidden fields?
However, I believe that what your actually trying to achieve is to allow the user to edit UserInput3 and UserInput4. Therefore, I've rewrote your controller and view to what I think your trying to achieve and have included comments containing my answers. The view is written using the razor view engine so you will have to tranfer to Spark.
I hope this is what you wanted:
Controller:
//HttpGet has been removed as this is implied
public ViewResult EditSomething(Int32 id)
{
MyDomainObject = base.BizLogic.GetMyDomainObjectById(id);
MyViewModel model = new MyViewModel()
{
//this uses C# 3.0 object initializer syntax
model.Id = MyDomainObject.Id,
model.ReadOnly1 = MyDomainObject.Field1,
model.Readonly2 = MyDomainObject.Field2,
model.UserInput3 = MyDomainObject.Field3,
model.UserInput4 = MyDomainObject.Field4
};
return View(model);
}
//It is common (although not required) for the Post action to have the same name as the Get action
[HttpPost]
public ActionResult EditSomething (MyViewModel model)
{
//I recommend you take a look at FluentValidation to validate your models.
//Hopefully, you should no longer have the limitations of the data attributes and you will not require PerformComplexValidationNotDoneByAttributes()
//This will allow you to just simply check the model state.
if (ModelState.IsValid)
{
BizLogicSaveTransferObject transferObject = new BizLogicSaveTransferObject()
{
//You should write the new user inputs to the database.
transferObject.Id = model.Id,
transferObject.Field3 = model.UserInput3,
transferObject.Field4 = model.UserInput4
};
base.BizLogic.SaveDomainObject(transferObject);
//You were previously returning a redirect to the same action??? Do you need to pass the id?
return RedirectToAction("NextAction", new { id = model.Id });
}
//Note: The else is not required as a return was used in the previous if.
//IN ANSWER TO YOUR QUESTION, you should re-retrieve the domain object from the database to get the information to display again
//as these values will be null/default values in your model.
//There will be a performance cost in retrieving this information again but you could not rely on this data to be sent from the user.
//Nor, should it have been included in the request as you should keep the size of the request down as indicated here:
//http://developer.yahoo.com/performance/rules.html
MyDomainObject = base.BizLogic.GetMyDomainObjectById(model.Id);
model.ReadOnly1 = MyDomainObject.Field1;
model.Readonly2 = MyDomainObject.Field2;
return View(model);
}
View:
#model MyViewModel
<!--
The read only fields which are only used to display information have been included outside of the form.
As this information is only used for display purposes we do not want to send it back as:
1. It will increase the size of the request.
2. We can not rely on it as a mischievous user may have tampered with it.
-->
<p>Read Only One: #Html.TextBoxFor(m => m.ReadOnly1)</p>
<p>Read Only Two: #Html.TextBoxFor(m => m.ReadOnly2)</p>
#using (Html.BeginForm())
{
<!--
The id is still passed using a hidden field as you still need to identify the database entity in the post action.
I have not displayed the id as there seemed little point.
Again, a user could change this id so you would need to check that the id exists in the database.
-->
#Html.HiddenFor(m => m.Id)
<!--
The user inputs do not need to have their previous values passed via hidden fields.
This increases the size of the request and by the looks of it you are not using them in the post controller.
-->
<p>#Html.TextBoxFor(m => m.UserInput3)</p>
<p>#Html.TextBoxFor(m => m.UserInput4)</p>
<input type="submit" value="submit" />
}
I'm creating a small MVC application and am passing a User object from a controller to an ActionResult method in another controller. One of the attributes of the User object is a list of Property objects called Properties.
Here's the rub: when the User object is finally passed to the relevant View, it's list does not contain any properties.
Here's the setup:
User class:
public class User
{
public int Id {get;set;}
public List<Property> Properties {get;set;}
}
AccountController
public ActionResult LogOn(int userId, string cryptedHash)
{
//code to logOn (this works, promise)
User user = dbContext.getUser(userId);
//debugging shows the user contains the list of properties at this point
return RedirectToAction("UserHome", "Home", user);
}
HomeController
public ActionResult UserHome(User user)
{
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user); //debugging shows that user.Properties is now empty(!)
}
UserHome.cshtml View
#model emAPI.Model_Objects.User
#{
ViewBag.Title = "UserHome";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>UserHome</h2>
<div>
#Model.Forename, these are your properties:
<ul>
#foreach (var property in #Model.Properties)
{
<li>property.Name</li>
}
</ul>
</div>
The view loads without any problem - #Model.Forename is fine, but as far as HomeController is concerned user.Properties was empty when it received it, although I know it wasn't when AccountController sent it.
Any help or advice anyone has to offer would be gratefully received.
You cannot pass entire complex objects when redirecting. Only simple scalar arguments.
The standard way to achieve that is to authenticate the user by emitting a forms authentication cookie which will allow you to store the user id across all subsequent actions. Then if in a controller action you need user details such as forename or whatever you simply query your data store to retrieve the user from wherever it is stored using the id. Just take a look at the way the Account controller is implemented when you create a new ASP.NET MVC 3 application.
So:
public ActionResult LogOn(int userId, string cryptedHash)
{
//code to logOn (this works, promise)
User user = dbContext.getUser(userId);
//debugging shows the user contains the list of properties at this point
// if you have verified the credentials simply emit the forms
// authentication cookie and redirect:
FormsAuthentication.SetAuthCookie(userId.ToString(), false);
return RedirectToAction("UserHome", "Home");
}
and in the target action simply fetch the user id from the User.Identity.Name property:
[Authorize]
public ActionResult UserHome(User user)
{
string userId = User.Identity.Name;
User user = dbContext.getUser(int.Parse(userId));
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user);
}
Ah and please, don't use ViewBag. Use view models instead. If all that your view cares about is welcoming the user by displaying his forename simply build a view model containing the forename property and then pass this view model to the view. The view doesn't care about your User domain model and it shouldn't.
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. You should not think about passing a complex object in that to the next action method.
In this case, may be you can keep your user object in the Session variable and access it in the remaining places.
public ActionResult LogOn(int userId, string cryptedHash)
{
User user = dbContext.getUser(userId);
if(user!=null)
{
Session["LoggedInUser"]=user;
return RedirectToAction("UserHome", "Home");
}
}
public ActionResult UserHome()
{
var loggedInUser= Session["LoggedInUser"] as User;
if(loggedInUser!=null)
{
ViewBag.Messaage = "Hello, " + user.Forename + "!";
return View(user);
}
return("NotLoggedIn");
}
Please help with such a question and do not judge strictly because I'm a newbie in MVC:
I've got a model for storing names of users by ID in my DB
public class Names
{
public int NameId { get; set; }
public string Username { get; set; }
}
,
a conrtoller
[HttpPost]
public ActionResult EditforModel(Names Name)
{
if (ModelState.IsValid)
{
db.Entry(Name).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
adding and editing view
adding is working well, the question is about editing
I use
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend> legend </legend>
#Html.EditorForModel()
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
to edit my model.
when trying to go to this view I see an editor for both Id and Username, but if i fill Id - I've got error, because there is no Entry in DB with such Id.
Ok.Let's look for attributes to hide an editor.
[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not.
applaying it to my model I've got "0" id posting from my View.Try another attr.
[ReadOnly(true)] makes a field a readonly-field. But at the same time I've got "0" in posting Id.
Modifying a view I placed an editors for each field in model
#Html.HiddenFor(model => model.NameId)
#Html.EditorFor(model => model.Username)
but using it is dangerous because some user can post wrong Id throgh post-request.
I can't use [ScaffoldColumn(false)] with applying Id at [Httppost] action of the controller,by searching appropriate user-entry in DB, because the name was changed..
I can't believe #Html.HiddenFor is the only way out.But can't find one :(
As you mentioned "[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not", and [ReadOnly(true)] means that this property will be excluded by the default model binder when binding your model.
The problem is that the HTTP protocol is a stateless protocol, which means that when the user posts the edit form to the MVC Controller, this controller has no clue which object he was editing, unless you include some identifier to your object in the request received from the user, though including the real object Id isn't a good idea for the reason you mentioned (that someone could post another Id).
A possible solution might be sending a View Model with an encrypted Id to the View, and decrypting this Id in the controller.
A View Model for your object might look like this :
public class UserViewModel
{
[HiddenInput(DisplayValue = false)]
public string EncryptedId { get; set; }
public string Username { get; set; }
}
So your HttpGet action method will be
[HttpGet]
public ActionResult EditforModel()
{
// fetching the real object "user"
...
var userView = new UserViewModel
{
// passing the encrypted Id to the ViewModel object
EncryptedId = new SimpleAES().EncryptToString(user.NameId.ToString()),
Username = user.Username
};
// passing the ViewModel object to the View
return View(userView);
}
Don't forget to change the model for your View to be the ViewModel
#model UserViewModel
Now the HttpPost action method will be receiving a UserViewModel
[HttpPost]
public ActionResult EditforModel(UserViewModel Name)
{
if (ModelState.IsValid)
{
try
{
var strId = new SimpleAES().DecryptString(Name.EncryptedId);
var id = int.Parse(strId);
// select the real object using the decrypted Id
var user = ...Single(p => p.NameId == id);
// update the value from the ViewModel
user.Username = Name.Username;
db.Entry(user).State = EntityState.Modified;
}
catch (CryptographicException)
{
// handle the case where the encrypted key has been changed
return View("Error");
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
When the user tries to change the encrypted key, the decryption will fail throwing a CryptographicException where you can handle it in the catch block.
You can find the SimpleAES encryption class here (don't forget to fix the values of Key and Vector arrays):
Simple insecure two-way "obfuscation" for C#
PS:
This answer is based on the following answer by Henry Mori:
Asp.net MVC 3 Encrypt Hidden Values
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 have a project that use MVC3. in my project, i have a page that user can edit their account (UserComment, UserEmail, IsLocked, IsApproved). i already make the View for Edit Account. i have some trouble to make the Edit Controller.
here's my code:
[HttpPost]
public ActionResult Edit(string id, FormCollection collection)
{
id = id.Replace("+", " ");
var user = Membership.GetUser();
Guid UserGUID = new Guid(user.ProviderUserKey.ToString());
var SysUser = db.System_User.Single(u => u.User_UserId == UserGUID);
//this is for updating User Office in my System_user table
SysUser.User_Office = collection["SysUsers[0].UserOffice"];
//this is for updating User Account in aspnet_membership table
user.UserName = collection["SysUsers[0].UserName"];
Membership.UpdateUser(user);
user.Comment = collection["SysUsers[0].UserComment"];
Membership.UpdateUser(user);
user.Email = collection["SysUsers[0].UserEmail"];
Membership.UpdateUser(user);
return View();
}
when i run my controller, i get some error like :
user.UserName is read only, i cant update this one.
i get user.Comment value, but its not update.
i get error in my Edit View, it says "Object reference not set to an instance of an object."
#using (Html.BeginForm("edit/" + #Model.SysUsers[0].UserID, "cpanel/sysuser", FormMethod.Post))
can anyone help me ?
thanks,
You cannot update the UserName of a user using ASP.NET Membership provider. You need to write some custom data access logic if you wish to update a username.
I would suggest using the Membership Provider primarily for authentication and build all the other maintenance etc. methods into your User's business objects.