I have a model
public partial class User
{
public int user_id { get; set; }
public virtual string username { get; set; }
public string password { get; set; }
public string salt { get; set; }
public string email { get; set; }
public sbyte status { get; set; }
public System.DateTime creation_date { get; set; }
}
i wanto to give the user the possibility to change his mail from a page
so i create my controller
[Authorize]
[HttpGet]
public ActionResult Setup()
{
//retrieve the object user
return View(CustomDbFunctions.GetUserEntity(User, db));
}
then in my view
#Html.EditorFor(u => u.email)
but when i save my modification in the db using
[HttpPost]
public ActionResult Setup(User user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = System.Data.EntityState.Modified;
db.SaveChanges();
return Redirect("Index");
}
return View(user);
}
the system return an error, because the object user contains ONLY the modified mail. All others properties (i.e. User_id, password) are gone.
How can i send back to the db the entire object properly modified?
To Update something with entity framework you have to first get the entity from DB:
if (ModelState.IsValid)
{
var existingUser = db.Users.FirstOrDefault(u => u.user_id == user.user_id);
existingUser.email = user.email;
db.SaveChanges();
return Redirect("Index");
}
For this to work you also need to have in your view, inside the form, something like
#Html.HiddenFor(u => u.user_id)
However, I suppose you have the Id of the user somewhere in the Session. You should use it from there and not from the hidden field, because a hacker could change the value in the hidden tag and edit another user's email.
If you use model binding of mvc, then you need to use the other properties as hidden field, that way while posting back the whole object will be returned instead of only email.
Related
Here is a simplified version of my model:
public class User {
public int UserID { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
I have a controller that I'd like to return a User as well as some summary information about their recipes. The scaffolded controller code looks like this:
var user = await _context.Users.SingleOrDefaultAsync(m => m.UserID == id);
It works fine. Now I try to add the Recipes, and it breaks:
var user = await _context.Users.Include(u => u.Recipes).SingleOrDefaultAsync(m => m.UserID == id);
My web browser starts to render the JSON, and it flickers and I get a message in the browser saying the connection has been reset.
My Theory - I believe that the parent (User) renders, which exposes the child (Recipe) which contains a reference to the parent (User), which contains a collection of the child (Recipe) and so on which is causing an infinite loop. Here's why I think this is happening:
The Visual Studio debugger allows me to navigate the properties in that way infinitely.
If I comment out the Recipe.User property, it works fine.
What I've tried
I tried to just include the data from Recipe that I need using Entity Framework projection (I'm attempting to not include Recipe.User). I tried to only include Recipe.RecipeName... but when I try to use projection to create an anonymous type like this:
var user = await _context.Users.Include(u => u.Recipes.Select(r => new { r.RecipeName })).SingleOrDefaultAsync(m => m.UserID == id);
I receive this error:
InvalidOperationException: The property expression 'u => {from Recipe r in u.Recipes select new <>f__AnonymousType1`1(RecipeName = [r].RecipeName)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
What is the solution? Can I project with different syntax? Am I going about this all wrong?
Consider using POCOs for serialization rather than doubly-linked entity classes:
public class UserPOCO {
public int UserID { get; set; }
public string FirstName { get; set; }
public ICollection<RecipePOCO> Recipes { get; set; }
}
public class RecipePOCO {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
}
Copy the entity contents to the corresponding POCO and then return those POCO objects as the JSON result. The removal of the User property via usage of the RecipePOCO class will remove the circular reference.
I can propose you 3 options.
U sing [JsonIgnore] on property, but it will work on every use of Recipe class, so when you would like to just return Recipe class you won't have User in it.
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
[JsonIgnore]
public virtual User User { get; set; }
}
You can this solution to stop reference loop in all jsons https://stackoverflow.com/a/42522643/3355459
Last option is to create class (ViewModel) that will only have properties that you want send to the browser, and map your result to it. It is propably best from security reason.
I have some simple code I am using to update a profile picture that is saved in my database as a byte[]. For some odd reason it runs through my code with no errors at all but the database is not updated with the new picture that the user has chosen.
Client client = db.Clients.FirstOrDefault(c => c.Email == User.Identity.Name);
if (client != null)
{
client.ProfileImage = bytes;
db.Clients.Attach(client);
var entry = db.Entry(client);
entry.Property(e => e.ProfileImage).IsModified = true;
db.SaveChanges();
}
Here is the code for the Client entity class:
public partial class Client
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Client()
{
Customers = new HashSet<Customer>();
}
public int ID { get; set; }
public int? StripeID { get; set; }
[Required]
[StringLength(200)]
public string Name { get; set; }
[Required]
[StringLength(150)]
public string Email { get; set; }
public Guid UserID { get; set; }
public bool IsActive { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public byte[] ProfileImage { get; set; }
public virtual StripeInfo StripeInfo { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Customer> Customers { get; set; }
}
Also after debugging the code I can see that the profile image is being set to the correct byte[] and the client.ProfileImage then contains the correct byte[], but as soon as db.SaveChanges() is called, client.ProfileImage is immediately set back to what it originally was. No errors occur or anything so I really can't figure out why this would be happening.
7/4/2017
Here are some additional things I have tried after looking on the internet some more:
1) Create a new database context instance to make the update.
2) Dispose of the original database context and create a new one to make the update.
Neither of the above attempts have worked. The ProfileImage property is still just set back to the original value when db.SaveChanges() is called.
The issue is that the ProfileImage property of your entity has an attribute applied to it:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
Which, from the documentation, is telling Entity Framework:
Computed: The database generates a value when a row is inserted or updated.
You just need to remove that attribute completely.
Can you try with below code ? :
Client client = db.Clients.FirstOrDefault(c => c.Email == User.Identity.Name);
if (client != null)
{
client.ProfileImage = bytes;
var entry = db.Entry(client);
entry.Property(e => e.ProfileImage).IsModified = true;
db.SaveChanges();
}
If client has been already loaded from context then no need to attach it !
I have a put method in my MVC4 Web API like this:
public HttpResponseMessage PutUser(int id, User user)
{
if(!Modelstate.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Modelstate invalid");
}
// etc...
}
My User model looks like this:
public Class User
{
public int ID { get; set; }
public string Password {
set
{
PasswordHash = HashPassword(value);
}
}
[Required]
public string PasswordHash { get; set; }
}
Now when I call my put method my Modelstate is invalid because my PasswordHash is not set. I don't want to update the password, so naturally, I didn't set it. How do I get my Modelstate to be valid and to not update the password hash field? I tried excluding it with Bind(Exclude) and I tried including it the same way.
I ended up using a ViewModel and AutoMapper.
ViewModel:
public Class UserViewModel
{
public int ID { get; set; }
}
Put method:
public HttpResponseMessage PutUser(UserViewModel user)
{
Mapper.CreateMap<UserViewModel, User>();
User user = db.Get(user.ID);
Mapper.Map(user, oldUser);
if(!Modelstate.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Modelstate invalid");
}
// etc...
}
You can install AutoMapper using NuGet.
I find myself repeating the following pattern when a user is required to link two entities together and it feels wrong.
I have two entities, Site and User say, a user msut have a site and a site can have many users. On the add user form the available Sites are displayed as a DropDown List.
Example Code:
public class Site()
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
}
public class User()
{
public int Id { get; set; }
public string Name { get; set; }
public int SiteId { get; set; }
public Site Site { get; set; }
}
public class UserViewModel()
{
public User User { get; set; }
public List<Site> AvailableSites { get; set; }
}
Add User View
#model SomeNameSpace.UserViewModel
#using (Html.BeginForm())
{
<div>
<div>#Html.LabelFor(m=>m.User.Site)</div>
<div>#Html.DropDownListFor(m=>m.User.Site.Id,
new SelectList(Model.AvailiableSites,"Id","Name"))
</div>
</div>
<--- the rest of the view --- >
Controller
public ActionResult Add()
{
UserViewModel u = new UserViewModel();
u.AvailableSites = context.Sites.ToList();
return View(u);
}
[HttpPost]
public ActionResult Add(UserViewModel model)
{
//THIS FEELS WRONG
Site s = context.Sites.Where(s=>s.Id == model.User.Site,Id).FirstOrDefault();
model.User.Site = s;
context.Users.Add(model.User);
context.SaveChanges();
}
It seems odd to be looking up the site in the controller, I expected the dropdown to link the two entities together - it displays the site name correctly - but it only sets the UserViewModel.User.Site.Id value.
Am I missing something, or is this the preferred/correct way?
You don't need to grab the whole entity in order to link it. As long as the FK has been properly set up, all you would need to do is make sure that the FK's ID property has been set (which it looks like you have, considering that you're using model.User.SiteId to grab the entity).
EF doesn't need to know the full entity in order to link them. It just needs to know its ID. Also, sometimes it can be bad to assign the full entity. Depending on how you do it you could create duplicates.
I have two models defined as follows:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
public virtual Employee Contact{ set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int EmployeeID{ get; set; }
public string Name{ get; set; }
public virtual Division Division{set;get;}
}
Entity framework sets a field in division table called employee_employeeid, how can I create dropdown for employees for the contact attribute in the division table.
Here is what I have tried but nothing is being sent to the database.
ViewBag.contact = new SelectList(db.Employees,"EmployeeID","Name");
In the view I have:
#Html.DropDownList("contact",String.Empty)
Is there a naming convention I have to use?
Edit
POST action:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.employeeid = new SelectList(
db.Employees, "EmployeeID", "EmployeeFirstName", division.employee);
return View(division);
}
I would recommend actually using ViewData instead of the ViewBag. In your controller have the following:
var employees = db.Employees.Select(e => new DropDownItem{ Text = e.Name, Value = e.EmployeeID });
ViewData["Employees"] = employees;
Then, in your view, have the following to display it:
#Html.DropDownList("Contact", ((IEnumerable<DropDownItem>)(ViewData["Employees"])))
I would suggest that you expose a foreign key property in your model. It makes the binding to the dropdown list and the later update much easier. Your Division model would look like this:
public class Division
{
public int DivisionID { get; set; }
[Required]
public string DivisionName { get; set; }
[ForeignKey("Contact")]
public int ContactId { set;get; }
public virtual Employee Contact { set;get; }
public virtual ICollection<Employee> Employees { get; set; }
}
Then in the Edit GET action you fill the ViewBag as you did:
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
You have a strongly typed view with the Division as model:
#model MyNamespace.Division
In this view you can bind the dropdown list to the ContactId property:
#Html.DropDownListFor(model => model.ContactId, ViewBag.Contacts)
Your POST action can be similar to your current version:
[HttpPost]
public ActionResult Edit(Division division)
{
if (ModelState.IsValid)
{
db.Entry(division).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Contacts = new SelectList(
db.Employees, "EmployeeID", "Name", division.ContactId);
return View(division);
}
Note, that it is in many cases the better practice (especially for security reasons) to use a special ViewModel instead of a database entity for your views. You could then incorporate the Contacts collection into your ViewModel (instead of using the ViewBag). To update the entity in the database you would load it and write the changed properties from the ViewModel to the entity and then save it.