I read tutorial "Creating an Entity Framework Data Model for an ASP.NET MVC Application" from http://www.asp.net/ and wanted to use multiple checkbox list from part 6 - http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
In this tutorial, it is only possible to access these checkbox options (to assign courses to each instrucor) from edit page of instructor. I want to render these checkboxes on the create page, but couldn't adjust codes I've found in this tutorial.
These are parts of the codes that are used to display and use checkboxes for new inputs to database.
HttpGet Edit method
public ActionResult Edit(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.InstructorID == id)
.Single();
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = db.Courses;
var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewBag.Courses = viewModel;
}
HttpPost Edit method
[HttpPost]
public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses)
{
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.InstructorID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
db.Entry(instructorToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in db.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
}
}
And the view
<div class="editor-field">
<table>
<tr>
#{
int cnt = 0;
List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses) {
if (cnt++ % 3 == 0) {
#: </tr> <tr>
}
#: <td>
<input type="checkbox"
name="selectedCourses"
value="#course.CourseID"
#(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
#course.CourseID #: #course.Title
#:</td>
}
#: </tr>
}
</table>
</div>
So, I want to insert multiple choices (select choices) to database, but would like to display this list of checkboxes immediately on the create page, not the edit page like it was done in the tutorial. If you have another solution that is not related to this tutorial, I would also be grateful. I know that I can't use these methods because they're methods for Edit, so they're takin "id" as parameter, but I hope that I can adjust them to be used in create page of my entity.
Consider using a partial view to isolate the control you're trying to render on both pages, you just need to make sure your model lines up with what the model for your view is.
http://mvc4beginner.com/Tutorial/MVC-Partial-Views.html
Related
I am trying to populate a partial view using the following in my view:
#{Html.RenderAction("AppointmentsView", "Appointment", new { id = Model.PatientId });}
My action result is as follows:
public ActionResult AppointmentsView(int id)
{
using (var context = new WaysToWellnessDB())
{
IEnumerable<AppointmentDiary> appointments = context.AppointmentDiaries.Where(a => a.PatientId == id).ToList();
var accountUsers = context.AccountUsers.Select(rr => new SelectListItem { Value = rr.AccountUserId.ToString(), Text = rr.FirstName + " " + rr.LastName }).ToList();
ViewBag.AccountUsers = accountUsers;
var location = context.Locations.Select(rr => new SelectListItem { Value = rr.LocationId.ToString(), Text = rr.LocationDesc }).ToList();
ViewBag.Location = location;
return PartialView("/Views/Patient/Appointment/_ViewAppointments.cshtml", appointments);
}
}
My partial view is as follows:
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(x => item.DateTimeScheduled)</td>
<td>#Html.DisplayFor(x => item.AppointmentLength)</td>
<td>#Html.DisplayFor(x => item.AccountUser.FirstName) #Html.DisplayFor(x => item.AccountUser.LastName)</td>
<td>#Html.DisplayFor(x => item.Location.LocationDesc)</td>
<td>#Html.DropDownListFor(x => item.AttendedStatusId, (IEnumerable<SelectListItem>)ViewBag.AppointmentStatus, null, htmlAttributes: new { #class = "form-control", #id = "appointmentStatusId", onchange = "alert(this.options[this.selectedIndex].value);" })</td>
</tr>
}
This is falling down, saying the following:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I tried using .Inlcude in my linq query, but this hasn't worked.. any thoughts on why this isn't working?
The answer as posted by Alexander Derck was that I needed to include System.Data.Entity in my using statements at the top, then the following worked.
public ActionResult AppointmentsView(int id)
{
using (var context = new WaysToWellnessDB())
{
IEnumerable<AppointmentDiary> appointments = context.AppointmentDiaries.Include(p => p.AccountUser).Include(p => p.AttendedStatus).Include(p => p.Location).Where(a => a.PatientId == id).ToList();
return PartialView("/Views/Patient/Appointment/_ViewAppointments.cshtml", appointments);
}
}
You have implemented "using" block, which disposes the object once done executing the "using" block. So when you are trying to access the virtual properties "item.AccountUser.FirstName", as the context object has already disposed, it is throwing "The ObjectContext instance has been disposed" error.
As posted by Alexander Derck, using include will resolve this issue because you are including all the required virtual properties in advance when the context object is still alive.
hope this explains.
Using MS VS 2012, C#.net, Nhibernate.
Im inserting a new row.
[HttpPost]
public ActionResult AddQuestion(ModuleAddQuestionModel model)
{
if (!_moServices.Authoriser.Authorise(AcademyPermissions.AccessDeveloperArea))
return new HttpUnauthorizedResult();
if (model == null)
return HttpNotFound();
int modelId = 0;
var module = _moduleService.GetModule(model.ModuleId);
// if null
if(model != null)
{
modelId = model.ModuleId;
}
var addQuestion = new Question()
{
Text = model.Question,
QuestionType = model.QuestionType,
Published = model.Published,
Country = module.Country,
Module = module
};
_moduleService.SaveQuestion(addQuestion);
return RedirectToAction(
"Edit",
"Module",
new { Id = modelId }
);
}
redirect calls
public ActionResult Edit(int Id) {
if (!_moServices.Authoriser.Authorise(AcademyPermissions.AccessDeveloperArea))
return new HttpUnauthorizedResult();
var module = _moduleService.GetModule(Id);
if(module == null)
return HttpNotFound();
var model = new ModuleEditModel {
Id = module.Id,
Name = module.Name,
Questions = module.Questions
.Where(x => !x.Deleted)
.Select(x => new ModuleEditModel.ModuleQuestionModel {
Id = x.Id,
Question = x.Text
}).ToList(),
Videos = module.Videos.Select(x => new ModuleEditModel.ModuleVideoModel {
Id = x.Id,
Title = x.Name
}).ToList()
};
return View(model);
}
the view displays the results, it should display a list that has the new row, but doesn't, even after f5. I suspect it was caching, but we don't have any caching involved for this whole procedure. Next step was to look into Nhibernate and caching.
the has the 2 relevent methods
public Module GetModule(int id) {
_moduleRepository.Flush();
return _moduleRepository.Get(id);
}
public void SaveQuestion(Question question) {
if (question.Id > 0)
UpdateQuestion(question);
else
InsertQuestion(question);
}
public void InsertQuestion(Question question) {
_questionRepository.Create(question);
_questionRepository.Flush();
_signals.Trigger(ModuleSignalChanged);
}
the .flush command was recently added by me to see if that would help clear the results, but didn't change anything.
looked into Isession.Clear(); but as i understand in our cms this lives outside the scope.
Im wondering what the issue is here, was i on the right tract? is this a NHibernate caching issue? Can anyone offer a solution?
If you require any additional information let me know.
ADDITIONAL INFO:
Yes the insert does populate the Database (MS SQL server 2008)
It appears after a few minutes, if one refreshes the page, the new record is displayed.
Heres the answer
var addQuestion = new Question()
{
Text = model.Question,
QuestionType = model.QuestionType,
Published = model.Published,
Country = module.Country,
Module = module
};
module.Questions.Add(addQuestion);
_moduleService.UpdateModule(module);
Instead of trying to insert a question, i updated the module instead that question is a child element of. This i assume cascades due to the relationship which forces nhibernate to clear cache and check the DB.
I cant confirm this, but this appears to at least solve this problem, if anyone can enlighten me as to why the previous method hasn't worked, ill swap my confirmed answer
This is now fixed. A combination of Ish's suggestion below plus adding calls to #HiddenFor in the view resolved the problem.
I have an ASP.NET MVC 5 web application where users can mark a defect as resolved. I want to display a list of potentially related defects, with check-boxes that users can tick to indicate that yes, this is the same defect, and should also be marked as resolved.
So I have a View Model with a property that is a collection, each member of which contains a defect object property and Boolean IsSameDefect property. This all works fine in the GET action method and in the view. I can display the related defects and tick the boxes.
The problem arises in the POST action when I want to update the data. At this point the property (the collection of potentially related defects) is null. I'm having a hard time trying to figure out how to pass this data back to the controller?
Code as requested ...
// GET: /DefectResolution/Create
public ActionResult Create(int ciid)
{
int companyId = User.CompanyID();
DefectResolutionCreateViewModel drcvm = new DefectResolutionCreateViewModel(ciid, companyId);
return View(drcvm);
}
// POST: /DefectResolution/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(DefectResolutionCreateViewModel drcvm)
{
DefectResolutions currentResolution = drcvm.DefectResolution;
currentResolution.CreatedOn = System.DateTime.Now;
currentResolution.UserID = User.UserID();
if (ModelState.IsValid)
{
unitOfWork.DefectResolutionRepository.Insert(currentResolution);
if (currentResolution.ResolutionStatusID == 2)
{
//code breaks here as drcvm.RelatedUnresolvedDefects is null
foreach (var relatedDefect in drcvm.RelatedUnresolvedDefects)
{
if (relatedDefect.IsSameDefect)
{
DefectResolutions relatedResolution = new DefectResolutions();
relatedResolution.ChecklistID = relatedDefect.RelatedChecklist.ChecklistID;
relatedResolution.CreatedOn = System.DateTime.Now;
relatedResolution.ResolutionNote = currentResolution.ResolutionNote;
relatedResolution.ResolutionStatusID = currentResolution.ResolutionStatusID;
relatedResolution.UserID = User.UserID();
}
}
}
unitOfWork.Save();
return RedirectToAction("Index", new { ciid = currentResolution.ChecklistID });
}
return View(drcvm);
}
In the view ...
#model Blah.ViewModels.DefectResolution.DefectResolutionCreateViewModel
#{
ViewBag.Title = "Create Defect Resolution";
var relatedDefects = Model.RelatedUnresolvedDefects;
}
... and later in the view ...
#for (int i = 0; i < relatedDefects.Count(); i++ )
{
<tr>
<td>
#Html.EditorFor(x => relatedDefects[i].IsSameDefect)
</td>
</tr>
}
I followed Ish's suggestion below, and modified the code to refer to Model.RelatedUnresolvedDefects directly instead of using a variable as I had been doing. This does get me a bit further. The view model's RelatedUnresolvedDefects property is no longer null. But only RelatedUnresolvedDefects.IsSameDefect has a value. RelatedUnresolvedDefects.RelatedChecklist is null. Here's the controller code again showing where it now breaks ...
// POST: /DefectResolution/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(DefectResolutionCreateViewModel drcvm)
{
DefectResolutions currentResolution = drcvm.DefectResolution;
currentResolution.CreatedOn = System.DateTime.Now;
currentResolution.UserID = User.UserID();
if (ModelState.IsValid)
{
unitOfWork.DefectResolutionRepository.Insert(currentResolution);
if (currentResolution.ResolutionStatusID == 2)
{
//prior to change, code used to break here
foreach (var relatedDefect in drcvm.RelatedUnresolvedDefects)
{
if (relatedDefect.IsSameDefect)
{
DefectResolutions relatedResolution = new DefectResolutions();
//code now breaks here because relatedDefect.RelatedChecklist is null
relatedResolution.ChecklistID = relatedDefect.RelatedChecklist.ChecklistID;
relatedResolution.CreatedOn = System.DateTime.Now;
relatedResolution.ResolutionNote = currentResolution.ResolutionNote;
relatedResolution.ResolutionStatusID = currentResolution.ResolutionStatusID;
relatedResolution.UserID = User.UserID();
}
}
}
unitOfWork.Save();
return RedirectToAction("Index", new { ciid = currentResolution.ChecklistID });
}
return View(drcvm);
}
Without knowing your code.I suggest you to use for loop instead of foreach while rendering the defects in View (.cshtml).
Editing Answer based on your code.
Following statement in the view creating problem
var relatedDefects = Model.RelatedUnresolvedDefects;
You should directly iterate over the Model.RelatedUnresolvedDefects property in the loop.
#for (int i = 0; i < Model.RelatedUnresolvedDefects.Count(); i++ )
{
<tr>
<td>
#Html.EditorFor(x => Model.RelatedUnresolvedDefects[i].IsSameDefect)
</td>
</tr>
}
I'm using MVC 4 with Razor Syntax to create a collection based on a class that was created using scaffolding (Database first based development) and I can add the first collection to the Session and return it to the Index view and display it on the page.
When I attempt to add a second collection to the Session Variable it gives me a error.
Unable to cast object of type
'System.Collections.Generic.List`1[EagleEye.Models.tblTask]' to type
'EagleEye.Models.tblTask'.
What am I doing wrong - how do I add 2 collections to the session?!
Index.cshtml (My Index view using Razor syntax)
#model List<myApp.Models.tblTask>
<table>
#{
foreach (var tblTask in Model)
{
<tr>
<td>
TaskName: #tblTask.Name
</td>
<td>
Desc: #tblTask.Description
</td>
<td>
Schedule: #tblTask.Freq #tblTask.FreqUnit
</td>
<td>
Reocurring?: #tblTask.ReocurringTask.ToString()
</td>
</tr>
}
}
</table>
Here's the "ActionResult" portion of the code from my HomeController.cs:
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
TaskCollection.Add((tblTask)Session["TaskCollection"]);
}
TaskCollection.Add(newTask);
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}
public ActionResult Index()
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
TaskCollection = (List<tblTask>)Session["TaskCollection"];
}
return View(TaskCollection);
}
When I add the first entry it works fine and shows up on my index view. When I try to add the second collection of tasks, it tells me:
Unable to cast object of type
'System.Collections.Generic.List`1[EagleEye.Models.tblTask]' to type
'EagleEye.Models.tblTask'.
I've been fighting this for a few days now and have been developing for a while, but am just beginning to learn the power of asking questions when I'm stumped (instead of just continuing to beat my head against the wall until something caves in (often my head), so if my question is not well formed, please let me know.
Thanks!
Dan
Because, inside your if condition, you are casting the Session["TaskCollection"](which is a collection of tblTask to a single instance of tblTask.
This should work.
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
//Check whether the collection exist in session, If yes read it
// & cast it to the tblTask collection & set it to the TaskCollection variable
if (Session["TaskCollection"] != null)
{
TaskCollection= (List<tblTask>) Session["TaskCollection"];
}
if(newTask!=null)
TaskCollection.Add(newTask);
//Set the updated collection back to the session
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}
I finally see the light -- Note the change in the HomeController.cs "TaskCollection = (List)Session["TaskCollection"]; "
[HttpPost]
public ActionResult CreateTask(tblTask newTask)
{
var TaskCollection = new List<tblTask>();
if (Session["TaskCollection"] != null)
{
//Here is the line that changed -- the following line works~
TaskCollection = (List<tblTask>)Session["TaskCollection"];
}
TaskCollection.Add(newTask);
Session["TaskCollection"] = TaskCollection;
return RedirectToAction("Index");
}
I have controller that get list of objects from my database and return this list:
public ActionResult Index()
{
var list = db.MyObjects.Where(x => x.family == "Web").ToArray();
list = list.Distinct(new MyObjectByProtocolComparer())
.OrderBy(x => x.fileName)
.ToArray();
ViewBag.Files = list;
return View();
}
Index.cshtml:
This return list is inserted into DropDownList:
#using (Html.BeginForm())
{
<div>
#Html.DropDownList(
"File", new SelectList(ViewBag.Files,
"protocol_site", "protocol_site"),
"Select webmail site",
new { style = "vertical-align:middle;" }
)
<button type="submit">Select</button>
</div>
}
After choose one item from and hit the button my other controller method is get the DropDownList item name and make new database query:
[HttpPost]
public ActionResult Index(string File)
{
var list = db.MyObjects.Where(x => x.protocol == File).ToArray();
ViewBag.Files = list;
return View();
}
What happen now is that the new query is inserted again into my DropDownList but i want this list to send to another page and from this page i wand to show this over ListView.
how can i do that ?
If you want to send it to another view, you'd better redirect to a new action instead of displaying your form again !
[HttpPost]
public ActionResult Index(string File)
{
// Some validation logic
return RedirectToAction("ShowList", new { protocol = File });
}
public ActionResult ShowList(string protocol)
{
var list = db.MyObjects.Where(x => x.protocol == protocol).ToArray();
ViewBag.Files = list;
return View();
}
The view called ShowList will display your items as a listView if it is what you defined.
EDIT :
As you want to display your list in the same view, you have to give a different name to your list, because ViewBag.Files is already used by your drop down list.
Also, as you want to update your list based on your current selection in the drop down list, i suggest you to use Ajax helpers. Here's a good link about this : http://geekswithblogs.net/blachniet/archive/2011/08/03/updating-partial-views-with-unobtrusive-ajax-in-mvc-3.aspx
Basically, your view (index.cshtml) should use an Ajax form, and prepare a empty div containing your list. The action called by your form would return a partial view, inserted in that div, without refreshing the whole view.