In MVC 4 How do I Add multiple Collections to a session? - c#

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");
}

Related

Foreach Loop in Razor Component causes await to fail

I'm new to Blazor and .NET 6, but I am working on a small app. I have LiteDB storing data on the backend, and my Razor component is fetching it. The problem is that, while 80% of the time it works as expected, the other 20% of the time the data gets populated as null in the Razor component before the database call has even processed. When debugging this, I see the code get to the database call in the backend, then it jumps back to the frontend Razor component which now has a null object, and then it jumps back to the backend where the database call has now been completed. The page populates from the null object though, so the data is not there. I get an error in the console, but it's just stating that the object was not set to an instance of an object at a foreach loop in my code. Here's my code:
#page "/origin"
#page "/origin/{id:int}"
#inject HttpClient http
#if (id == null)
{
<PageTitle>Origin Not Found</PageTitle>
<h3>Origin Not Found</h3>
}
else
{
<PageTitle>Origin: #origin.Name</PageTitle>
<h3>Origin: #origin.Name</h3>
<table class="table">
<tbody>
<tr>
<td>Description: </td>
<td>#origin.Description</td>
</tr>
<tr>
<td>Starting Health: </td>
<td>#origin.StartingHealth</td>
</tr>
<tr>
<td>Ground Movement Speed: </td>
<td>#origin.GroundMovementSpeed</td>
</tr>
#foreach(var stat in #essences)
{
<tr>
<td>Essence Increase Option: </td>
<td>#stat</td>
</tr>
}
<tr>
<td>Origin Benefit: </td>
<td>#origin.OriginBenefit</td>
</tr>
</tbody>
</table>
}
#code {
[Parameter]
public int id { get; set; }
private Origin origin = new();
private List<Essence> essences = new();
protected async override Task OnInitializedAsync()
{
var result = await http.GetFromJsonAsync<Origin>($"/api/origin/{id}");
if(result != null)
{
origin = result;
essences = result.EssenceIncreaseOptions;
}
}
}
Now if I remove the foreach loop (and the essence information I want there), then the code works 100% of the time. So there's something about that foreach loop that's causing it to fail 20% of the time, but I'm just not sure what. Any help would be appreciated. Thanks in advance.
In the posted code, the only point in the foreach(var stat ...) that could yield an NRE is <td>#stat</td>
So your Essence class probably has an override ToString() that causes the null de-reference.
What you can do is put a conditional check before the #foreach loop and when essences is null show some message saying loading etc, also, put StateHasChanged() after completion of fetching of data.
#if(essences.Count>0)
{
#foreach(var stat in #essences)
{
<tr>
<td>Essence Increase Option: </td>
<td>#stat</td>
</tr>
}
}
else {loading data}
and
protected async override Task OnInitializedAsync()
{
var result = await http.GetFromJsonAsync<Origin>($"/api/origin/{id}");
if(result != null)
{
origin = result;
essences = result.EssenceIncreaseOptions;
StateHasChanged();
}
}

Need help passing file from view to controller

I am allowing users to upload a file from the view which will eventually be passed to the model through controller actions. However, there is a particular situation that is giving me trouble.
I have the followong ViewModel that I'm passing to the view:
public class RegisterStudentViewModel
{
public Login loginViewModel;
public Person personViewModel;
public PersonResume personResViewModel;
}
When the form is submitted, I use some validation logic to make sure the fields entered were applicable, and if any of the validation fails, I send the ViewModel that I'm using back to the view, to make sure that the fields that had information entered correctly are stored. Before this though, I have also made sure that the file uploaded is binded to the model that it belongs to, like this:
if (res != null)
{
PersonResume resumeViewModel = createResume(res, personModel);
vm.personResViewModel = resumeViewModel;
}
SetUpView(vm, da);
result = View(vm);
}
so that when the validation fails but the resume wasn't one of the fields that failed and I want to display the uploaded resume, I can use the following code in my view:
#if (Model.personResViewModel != null && Model.personViewModel.id == 0)
{
<th class="editAddLabel">
#Html.LabelFor(model => model.personResViewModel.ResumeFileName, "Resume File Name")
</th>
<td>
#Html.ActionLink("View your resume", "linkDownload", "Student", new { id = Model.personViewModel.id, resume = Model.personResViewModel}, null)
</td>
}
to pass to the following controller method that will allow the link to be downloaded:
public FileResult linkDownload(int id, PersonResumeViewModel resume)
{
DataAccess da = new DataAccess();
PersonResume displayResume = new PersonResume();
if (id != 0)
{
displayResume = da.getPersonResumeByID(id);
}
else
{
displayResume.ResumeData = resume;
}
//some more code that downloads file
}
I know that in the view, the personResViewModel is not null, but when I look at the controller method, it is null and I can't do anything with it. Am I not passing my argument in correctly?

ViewModel Property Null in Post Action

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>
}

DropDownListFor SelectedValue and Disable using Session State

I have been introduced to Razor as applied with MVC 3 this morning, so please forgive me if my question seems terribly uninformed!
I am working with an app whose workflow involves allowing a user to select a value (warehouse) from a drop down list, and add a record (material) from that warehouse to another record (Materials Request). Once the first material has been added to the Materials Request, I need to permanently set the value of the drop down to the warehouse that was first selected, then disable the drop down control (or set to read only, perhaps). The existing code in the razor file uses the DropDownListFor() method, including a ViewBag collection of Warehouse records. I have seen discussions which suggest abandoning the ViewBag design, but honestly I don't have the desire to rewrite major portions of the code; at least it looks like a major rewrite from the perspective of my experience level. Here's the original code:
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
I believe I have been able to select a value based on a session object, though I'm still not sure how to disable the control. Here's my change:
#{
int SelectedWarehouseId = -1;
if (HttpContext.Current.Session["SelectedWarehouseId"] != null)
{
SelectedWarehouseId = Int32.Parse(HttpContext.Current.Session["SelectedWarehouseId"].ToString());
}
}
#Html.LabelPlusFor(m => m.WarehouseId, "*:")
#{
if (SelectedWarehouseId > -1)
{
#Html.DropDownListFor(m => m.WarehouseId, new SelectList((IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "WarehouseId", "WarehouseDescription", (int)SelectedWarehouseId))<br />
}
else
{
#Html.DropDownListFor(m => m.WarehouseId, (IEnumerable<SelectListItem>)ViewBag.WarehouseCodes, "")<br />
}
}
When the material is added to the Material Request, the WarehouseId is passed to the controller and I can access that value as "model.WarehouseId" in the controller class. However, I'm not sure how to get that value back to the View (apologies for the large code block here):
[HttpPost]
[TmsAuthorize]
public ActionResult Create(ItemRequestViewModel model)
{
string deleteKey = null;
//Removed code
else if (Request.Form["AddToRequest"] != null)
{
// If the user clicked the Add to Request button, we are only
// interested in validating the following fields. Therefore,
// we remove the other fields from the ModelState.
string[] keys = ModelState.Keys.ToArray();
foreach (string key in keys)
{
if (!_addToRequestFields.Contains(key))
ModelState.Remove(key);
}
// Validate the Item Number against the database - no sense
// doing this if the ModelState is already invalid.
if (ModelState.IsValid)
{
_codes.ValidateMaterial("ItemNumber", model.ItemNumber, model.WarehouseId);
Session["SelectedWarehouseId"] = model.WarehouseId;
}
if (ModelState.IsValid)
{
// Add the new Item Request to the list
model.Items.Add(new ItemViewModel() { ItemNumber = model.ItemNumber, Quantity = model.Quantity.Value, WarehouseId = model.WarehouseId });
ModelState.Clear();
model.ItemNumber = null;
model.Quantity = null;
model.WarehouseId = null;
}
}
//Removed code
return CreateInternal(model);
}
private ActionResult CreateInternal(ItemRequestViewModel model)
{
if (model != null)
{
if (!String.IsNullOrEmpty(model.SiteId))
{
ViewBag.BuildingCodes = _codes.GetBuildingCodes(model.SiteId, false);
if (!String.IsNullOrEmpty(model.BuildingId))
ViewBag.LocationCodes = _codes.GetLocationCodes(model.SiteId, model.BuildingId, false);
}
//Removed code
}
//Removed code
ViewBag.WarehouseCodes = _codes.GetWarehouseCodes(false);
return View("Create", model);
}
So my questions are, how do I disable the drop down list, and how can I pass a value for the selected WarehouseId back to the view? I've also considered adding the value to the ViewBag, but to be honest I don't know enough about the ViewBag to recognize any unintended consequences I may face by just randomly modifying it's contents.
Thanks for any help offered on this.
Without going into which approach is better...
Your dropdown should be rendered as an HTML select element, in order to disable this you'll need to add a disabled="disabled" attribute to it.
The DropDownListFor method has a htmlAttributes parameter, which you can use to achieve this:
new { disabled = "disabled" }
when your pass model to your view like
return View("Create", model);
if WareHouseID is set in model then
Html.DropDownListFor(x=>x.WareHouseID, ...)
will automatically set the selected value and u don't have to do that session processing for this. So far as disabling a field is required, stewart is right. you can disable drop down this way but then it won't be posted to the server when u submit the form. you can set it to readonly mode like
new{#readonly = "readOnly"}

Getting a single object from SQL Server to a view

I have a an object of type Picture that contains a List Comments see source code here http://pastebin.me/21148a93065bddb7302e160f5a0ac1fb
I then want to take an Picture.ID and show the image that has that unique ID from my database
So I made a ActionResult Comment in my GalleryController http://pastebin.me/21148a93065bddb7302e160f5a0acb02 with a HttpGet
I then have this view that should display the requested image and the comments in its list http://pastebin.me/21148a93065bddb7302e160f5a0acb02
But I am not sure if its the List that is not initialized or if I am just doing it wrong but I get an error
Server Error in '/' Application.
Object reference not set to an
instance of an object.
Description: An
unhandled exception occurred during
the execution of the current web
request. Please review the stack trace
for more information about the error
and where it originated in the code.
Exception Details:
System.NullReferenceException: Object
reference not set to an instance of an
object.
Source error: http://pastebin.me/21148a93065bddb7302e160f5a0ad965
Let's examine the Action method in question. First of all, I see this and this should almost never exist:
catch
{
}
So if an exception was thrown, you're just swallowing it up and will never know about it. This would result in returning a view with no model passed in, thus the model referenced in your strongly-typed view is null. This seems like a very good possibility of what's happening.
Also, why do you keep overwriting ViewBag.Picture inside your foreach loop? Is that really what you intended to do?
Hopefully this will get you on the right path and you'll quickly get to the underlying issue.
as I cant get it to only parse a single object (And I know there will always be max one result as it searches in the database on the primary key)
So my code is now
[HttpGet]
public ActionResult Comment(string ID)
{
int id = Convert.ToInt32(ID);
if (ID != null)
{
try
{
var model = from r in _db.Gallery
where r.ID == id
select r;
return View(model);
}
catch
{
}
}
return View();
}
And
#model IEnumerable<firstweb4.Models.Picture>
#{
ViewBag.Title = "Comment";
}
#if (Model != null)
{
foreach (var item in Model)
{
<img src="#item.Path" alt="#item.Title" width="250px" />
<table>
<tr>
<th>
Posted by
</th>
<th>
Comment
</th>
<th>
Posted on
</th>
</tr>
#foreach (var comment in item.PictureComments)
{
<tr>
<td>
#comment.Auther
</td>
<td>
#comment.Text
</td>
<td>
#comment.PostedTime
</td>
</tr>
}
</table>
}
}
So I itterate through a the singleitem collection I parse and it now works.

Categories