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.
Related
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.
I'm just getting started with MVC5 (from WebForms), and dropdownlist bindings are giving me some fits.
I'd like to get this working using a GET request back to the page, with a selected value parameter. I'm hopeful that I can specify the route arguments in the form itself, so I'd like to reference the DDL's SelectedValue.
<p>
#using (Html.BeginForm("Index", "Profile", FormMethod.Get, new { id = WHATDOIPUTHERE} )) {
#Html.AntiForgeryToken()
#Html.DropDownList("ApplicationID", new SelectList(ViewBag.ApplicationList, "ApplicationID", "ApplicationName", ViewBag.SelectedApplicationId), new {onchange = "this.form.submit();"})
}
</p>
I can make it work with a POST form, but that requires a second controller method so I end up with
public ActionResult Index(long? id) {
ConfigManager config = new ConfigManager();
//handle application. default to the first application returned if none is supplied.
ViewBag.ApplicationList = config.GetApplications().ToList();
if (id != null) {
ViewBag.SelectedApplicationId = (long)id;
}
else {
ViewBag.SelectedApplicationId = ViewBag.ApplicationList[0].ApplicationID; //just a safe default, if no param provided.
}
//handle profile list.
List<ProfileViewModel> ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId) select new ProfileViewModel(p)).ToList();
return View(ps);
}
//POST: Profile
//read the form post result, and recall Index, passing in the ID.
[HttpPost]
public ActionResult index(FormCollection collection) {
return RedirectToAction("Index", "Profile", new {id = collection["ApplicationId"]});
}
It would be really nice to get rid of the POST method, since this View only ever lists child entities.
What do you think?
You can update your GET action method parameter name to be same as your dropdown name.
I also made some small changes to avoid possible null reference exceptions.
public ActionResult Index(long? ApplicationID) {
var config = new ConfigManager();
var applicationList = config.GetApplications().ToList();
ViewBag.ApplicationList = applicationList ;
if (ApplicationID!= null) {
ViewBag.SelectedApplicationId = ApplicationID.Value;
}
else
{
if(applicationList.Any())
{
ViewBag.SelectedApplicationId = applicationList[0].ApplicationID;
}
}
var ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId)
select new ProfileViewModel(p)).ToList();
return View(ps);
}
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.
I have an issue with caching and have tried every solution I can find!
I have a simple create screen where one row is inserted in a table (although I also get the same issue when editing existing rows).
When the row is created, the user is returned to the previous screen, which still shows the old data. (same issue with edit)
Refreshing the page makes no difference. Difference browsers have the same problem. The data is successfully added in the database. Only by restating the application does it refresh the data on screen.
Things I have tried:
1:
DataContext.Refresh(RefreshMode.OverwriteCurrentValues)
2:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
3:
ModelState.Clear()
None of which made any difference. I've not had this problem before with edits or creates, so I must be missing something. Any help much appreciated!
The following is the relevant parts of the controller:
ISISDataContext db = new ISISDataContext(StudentISIS.Properties.Settings.Default.ISISConn.ToString());
public ActionResult Index()
{
var student = (ISIS2Models.Student)Session["CurrentUser"];
return View(student);
}
public ActionResult STGCreate(int id)
{
var enrolment = db.Enrolments.Single(e => e.EnrolmentID == id);
return View(enrolment);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult STGCreate([Bind(Exclude = "Id")] StudentGrade STGToCreate, FormCollection collection)
{
var STG = new StudentGrade();
STG.Grade = collection["StudentTG"];
STG.EnrolmentID = int.Parse(collection["Enrolment"]);
STG.DateChanged = DateTime.Now;
db.StudentGrades.InsertOnSubmit(STG);
db.SubmitChanges();
return RedirectToAction("Index");
}
Edit:
Here is the code from the index view which loops through enrolments to show the grade:
<%foreach (var en in Model.Enrolments) {%>
//Some table stuff
<td>
<%try
{ %>
<%= Html.ActionLink(en.StudentGrades.Grade,"STGEdit",new {controller = "Progress", id = en.StudentGrades.StudentGradeID})%>
<%}
catch (NullReferenceException) {%><%= Html.ActionLink("Set","STGCreate",new {controller = "Progress", id = en.EnrolmentID})%><% } %>
</td>
//Some more table stuff
<%}%
Where do the rows come from? is it your ISIS2Models.Student class? I can only assume it is because you have so little code in your Index method.
If it is, and you are storing that in the session, then you are not updating that value, so when you retrieve it from within Index it will still have the same old values.
What you need to do is get the updated model from the database each time you make a call to Index. Some method like this:
public ActionResult Index()
{
var currentUser = (ISIS2Models.Student)Session["CurrentUser"];
var student = GetStudentById(currentUser.ID);//this will get the up-to-date student record from the DB
return View(student);
}
I hope I am able to put this question together well.
In a partial view I have a link to a create action:
public ActionResult CreateProject()
{
return View("EditProject", new Project());
}
Now this loads another view which allows editing of the blank model passed to it. But when form is submitted it is supposed to post to:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditProject(Project record)
{
if (ModelState.IsValid)
{
projectRepo.saveProject(record);
return View("Close");
}
else
{
return View("EditProject");
}
}
This method works for many of the tables and edit actions work just as well for the same view. But only for the create action (with the blank model) the form keeps calling to the create action, as I traced with the debugger.
One of my team mates has solved this problem so:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult EditProject(int id)
{
Project project = null;
if (id == 0)
{
project = new Project();
}
else
{
project = (from p in projectRepo.Projects
where p.ProjectID == id
select p).First();
}
return View(project);
}
And in the partial instead of having <%= Html.ActionLink("Create New", "CreateProject")%> there'd be <%= Html.ActionLink("Create New", "CreateProject", new { id = 0 })%>.
Now I was hoping to find out why the previous method would not go through, since it does for other tables in other views. Thanks.
By default your form will post to same URL it was rendered at. Since you called create action it will post back to create action, and not edit, 'cos views do not matter (-:
Explicitly use
<%= using( Html.BeginForm("Action","Controller) ){ %>