Using TryUpdateModel to save an object on Edit Post with FormCollection - asp.net-mvc

I'm not sure I understand the best way of doing this.
If I have a model with a large number of fields, then do I have to explicitelly list every one of them in a whitelist under TryUpdateModel, or can I just pass the ForCollection.
The following code doesn't save my edits, is my only alternative to list all my fields one by one?
public ActionResult Edit(int id, FormCollection form)
{
var jobToUpdate = db.Jobs
.Include(x => x.JobNotes)
.Where(x => x.JobID == id)
.SingleOrDefault();
if (TryUpdateModel(jobToUpdate, form))
{
db.Entry(jobToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Details", new { id = model.Job.JobID });
}
return RedirectToAction("Details", new { id = model.Job.JobID })
}
Secondly, what is the best way to get a list of just the fields that have changed. If the only field that the user changes is the FirstName field, I'd like to record that in an audit log.
Thanks for your help!

If there are fields on your model that aren't in the form and you don't want users to change then you can use an exclude list. The choice to use an include or exclude list will depend which is largest. An include list is more secure as if you forget to include something it can't be changed. Not using an include, or exclude list will leave you vulnerable to model stuffing where users can post extra values to change details they shouldn't be able to.
public ActionResult Edit(int id, FormCollection form)
{
var jobToUpdate = db.Jobs
.Include(x => x.JobNotes)
.Where(x => x.JobID == id)
.SingleOrDefault();
if (TryUpdateModel(jobToUpdate, String.Empty, null, new [] {"SecretField"}, form))
{
db.SaveChanges();
return RedirectToAction("Details", new { id = model.Job.JobID });
}
// Model not saved - send them back to edit page for corrections
return View(jobToUpdate);
}
If the model is not saved you should not redirect. Show them the same page and make sure your edit view shows model errors.
The most likely reason your code is not saving the model is you're trying to insert a value that is not valid.

Related

return relational data list to partial view

I wanted to return a list to my partial view from relational matching data of products. I have attached picture of edmx file where you will get idea about their relationship status! Problem is i just dont know how can i write this query or i need any iteration process to do it. Main goal is: I want to get all Products that the current user has bookmarked. Any question welcome. Thanks in advance
[ChildActionOnly]
[Authorize]
public PartialViewResult _UserBookmark(string id)
{
using (mydb db = new mydb())
{
int userId = db.Users.Where(x => x.Email == id).FirstOrDefault().UserId;//here i am getting user primary key id
var ProductIds = db.Bookmarks.Where(x => x.UserId == userId).ToList();//here i am getting all Product primary keys under that user
var ListOfProducts = db.Products.Where(x=>x.ProductId == "i dont know how to do it") // here i wanted to return matched all products
return PartialView("_UserBookmark",ListOfProducts);
}
}
You can use a .Contains statement to return the Products where the ProductId is in your collection of ProductIds.
Change the method to
[ChildActionOnly]
[Authorize]
public PartialViewResult _UserBookmark(string id)
{
using (mydb db = new mydb())
{
int userId = db.Users.Where(x => x.Email == id).FirstOrDefault().UserId;
// Get a collection of the ProductId's
IEnumerable<int> ProductIds = db.Bookmarks
.Where(x => x.UserId == userId).Select(x => x.ProductId);
IEnumerable<Product> ListOfProducts = db.Products
.Where(x => ProductIds.Contains(x.ProductId))
return PartialView("_UserBookmark", ListOfProducts);
}
}
Note, if the results are for the current user, then consider just getting the current user in the method rather that passing their Email to the method. Note also that .FirstOrDefault().UserId would throw an exception is you passed an incorrect value to the method which resulted in User being null.

How to keep existing values when editing a record in mvc

I'm building my site based on the patterns used in the ASP.NET Music Store project but I'm having some trouble with editing user records.
My view uses a PasswordFor for the password field and there are some fields that I need my site to update without any possible user intervention (for auditing).
My problem is that because I'm not supplying a value for password or for the auditing fields, they're submitting with null values.
Here's my POST:
[HttpPost]
public ActionResult Edit(User user)
{
if (ModelState.IsValid)
{
try
{
// supply the Company and Role information by the PK values submitted on the view
user.Company = db.Companies.Single(x => x.CompanyId == user.CompanyId);
user.Role = db.Roles.Single(x => x.RoleId == user.RoleId);
// add the auditing information
user.ModifiedBy = String.Format("{0} {1}", Convert.ToString(Session["firstname"]), Convert.ToString(Session["lastname"]));
user.ModifiedOn = DateTime.Now;
// save the record
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return Json(new
{
Message = "Success",
IsOK = bool.TrueString
});
}
catch (Exception ex)
{
if (Request.IsAjaxRequest())
{
ThrowError(ex, "EDIT");
}
else
{
return RedirectToAction("Index");
}
}
}
return View(user);
}
So when I submit, because I don't want to change the password, I'm leaving it blank which means the model is getting a null value.
How can I ensure that the fields that I'm not changing aren't going to get updated?
At first you should provide some hidden field in your form which stores ID of an entity.
Then in your controller POST action you should somehow merge your original entity with the changes done in the view. So you should do the following steps:
Read a record from the database.
Update only fields which were updated in the view model.
Save changes.
For the second step you can use mapping tools, for instance - https://automapper.codeplex.com/
More or less it should look like that:
var updated = SomeClass.GetByKey(key);
Mapper.CreateMap<ViewModel, Model>()
.ForMember(dest => dest.someField, opt => opt.Ignore())
.ForMember(dest => dest.someField2, opt => opt.Ignore());
Mapper.Map(model, updated);
//Here save changes
(By saying view model I mean or view model or instance of your model which is bound with your view).

MVC Membership & Profile Strategy

Bear in mind, I have researched this and have found several articles, however, they are mostly old (like from 2008) so I am wanting more recent information pertaining to the latest version(s) of ASP.NET MVC.
I am using the Membership built-in thing to provide user registration, login and roles.
I want to use the profile functions, too.
I don't know how to create a profile and I can't find any articles on how to do this in MVC 3. I found one using MVC 2, maybe it's the same, but I want to use the latest available methods.
Can someone show me a step by step solution to creating a profile?
I am considering using my own membership classes + forms authentication. That way, creating profiles is as simple as assigning a foreign key...
What's the experts' opinion?
Please provide your answer in VB and not C# (I don't know why everyone writes me stuff in C#).
Thanks.
Edit: Here is my Register function:
'
' POST: /Account/Register
<HttpPost()> _
Public Function Register(ByVal model As RegisterModel) As ActionResult
If ModelState.IsValid Then
' Attempt to register the user
Dim createStatus As MembershipCreateStatus
Membership.CreateUser(model.UserName, model.Password, model.Email, Nothing, Nothing, True, Nothing, createStatus)
If createStatus = MembershipCreateStatus.Success Then
FormsAuthentication.SetAuthCookie(model.UserName, False)
Return RedirectToAction("Index", "Home")
Else
ModelState.AddModelError("", ErrorCodeToString(createStatus))
End If
End If
' If we got this far, something failed, redisplay form
Return View(model)
End Function
Personally, I would recommend against using the SQL Profile Provider (which is what you are using). Nothing has changed with profiles since MVC2 (or, for that matter, since it was introduced in webforms with .NET 2.0).
The reason is this: Profile data is stored as XML in the database, which makes it very difficult to use profile data outside of your app (meaning in a pure SQL query).
You're probably much better off creating profile fields in your database directly. This way you know which table / column the data is coming from, and can create views if you need to. Otherwise, you would have to parse a table column's XML to extract the profile data (which is what the ProfileCommon does in .NET).
Reply to comments
First of all, I was wrong about the property name. It is ProviderUserKey, not ProviderKey. However unless you want to store profile properties for anonymous users, you could just as easily use MembershipUser.UserName as your FK value, since it will also be unique.
[HttpPost]
public ActionResult Register(RegisterModel model)
{
MembershipCreateStatus createStatus;
var membershipUser = Membership.CreateUser(model.UserName, model.Password,
model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
var providerKeyObject = membershipUser.ProviderUserKey;
var providerKeyGuid = (Guid)membershipUser.ProviderUserKey;
// Use providerKeyGuid as a foreign key when inserting into a profile
// table. You don't need a real db-level FK relationship between
// your profile table and the aspnet_Users table. You can lookup this
// Guid at any time by just getting the ProviderUserKey property of the
// MembershipUser, casting it to a Guid, and executing your SQL.
// Example using EF / DbContext
using (var db = new MyDbContext())
{
var profile = new MyProfileEntity
{
UserId = providerKeyGuid, // assumes this property is a Guid
FirstName = model.FirstName,
LastName = model.LastName,
};
db.Set<MyProfileEntity>().Add(profile);
db.SaveChanges();
}
// you could get the profile back out like this
using (var db = new MyDbContext())
{
var profile = db.Set<MyProfileEntity>().SingleOrDefault(p =>
p.UserId == (Guid)membershipUser.ProviderUserKey);
}
FormsAuthentication.SetAuthCookie(membershipUser.UserName, false);
return RedirectToAction("Index", "Home");
}
return View(model);
}
Here is an example using the UserName instead of the ProviderUserKey. I would recommend this approach if you are not storing profile info for anonymous users:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
MembershipCreateStatus createStatus;
var membershipUser = Membership.CreateUser(model.UserName, model.Password,
model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
// Example using EF / DbContext
using (var db = new MyDbContext())
{
var profile = new MyProfileEntity
{
// assumes this property is a string, not a Guid
UserId = membershipUser.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
};
db.Set<MyProfileEntity>().Add(profile);
db.SaveChanges();
}
// you could get the profile back out like this, but only after the
// auth cookie is written (it populates User.Identity.Name)
using (var db = new MyDbContext())
{
var profile = db.Set<MyProfileEntity>().SingleOrDefault(p =>
p.UserId == User.Identity.Name);
}
FormsAuthentication.SetAuthCookie(membershipUser.UserName, false);
return RedirectToAction("Index", "Home");
}
return View(model);
}

Update of a row in asp.net MVC 3

I've got an Edit action like this:
[HttpPost]
public ActionResult Edit(UserModel user1)
{
if (ModelState.IsValid)
{
UserManager um = new UserManager();
String mail = User.Identity.Name;
long id = um.getUserIDByemail(mail);
user user = db.users.Single(u => u.user_id == id);
user.name = user1.name;
user.cellno = user1.cellno;
db.users.Attach(user);
db.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user1);
}
I've got user object exactly what I want to update. Two properties (name, cellno) of user comes from a view. When I run it I get an error:
The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
I think you can just remove the Attach and it should work.

MVC Updatemodel and Linq-To-Sql creates new entity instead of updating

I have a survey form with the following data hierarchy
survey -< surveyQuestions -< surveyQuestionOptions
I use updateModel to update the Survey entity in my save method which works fine for survey and surveyQuestions, however surveyQuestionOptions appear updated when I examine the updateSurvey variable in debugger but after I call SubmitChanges I get new surveyQuestionOption records instead of updates. My code is as follows
HTML
<%= Html.TextBox("Survey.Id", Model.Id)%>
<%= Html.TextBox("Survey.SurveyName", Model. SurveyName)%>
<%= Html.TextBox("Survey.SurveyQuestions[0].Id", Model.Id)%>
<%= Html.TextBox("Survey.SurveyQuestions[0].Question", Model. Question)%>
<%= Html.TextBox("Survey.SurveyQuestions[0].SurveyQuestionOptions[0].Id", Model.Id)%>
<%= Html.TextBox("Survey.SurveyQuestions[0].SurveyQuestionOptions[0].Option", Model. Option)%>
Controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(int? id, IList<ChannelForm> channelForms, FormCollection fc)
{
Survey updateSurvey = new Survey();
//if this is an existing Surveyretrieve that record from the database ready for updating
if (id != 0)
{
updateSurvey = surveynRepository.GetSingle(Convert.ToInt32(id));
}
try
{
// updateSurvey and all child elements
UpdateModel(updateSurvey, "Survey");
surveyRepository.Save();
return View();
}catch
{return View();}
}
Any help is appreciated
Survey updateSurvey;
if (id == 0)
{
updateSurvey = new Survey();
}
else
{
updateSurvey = surveyRepository.GetSingle(Convert.ToInt32(id));
}
try
{
..etc
Mmm.. i have not tried to Update directly a child element like that, specially with Complex models (various depth levels). I mostly use a ViewModel and then use that ViewModel to Update the Actual Model(the LinqtoSQL classes).
I would Update the child's this way:
Get The Current Saved Survey
currentSurvey = surveyRepository.GetSingle(Convert.ToInt32(id));
foreach (var option in updateSurveyViewModel.SurveyQuestions)
{
//check if exist
var current = currentSurvey.SingleOrDefault(a=> a.Id == option.Id);
if (current == null)
{
//Create a NewOne and attach it to the curentSurvey
}
else
{
//already exist, Update the option
current.Option = option.Option;
//...
}
}
I know this is not the depth level with the problem but is the same concept.
I've tried with LoadOptions in LINQ to SQL:
var loadOptions = new System.Data.Linq.DataLoadOptions();
loadOptions.LoadWith<Contact>(c => c.ContactEmails);
db.LoadOptions = loadOptions;
Contact old = db.Contacts.SingleOrDefault(c => c.ContactID == id);
UpdateModel(old, "Contact");
db.SubmitChanges();
Didn't help. All existing ContactEmail is deleted and inserted again.

Resources