MVC - Display multiple Controller results in one view - asp.net-mvc-4

I have a controller in my MVC sire that has a couple of methods along the lines of the following:
public ActionResult _GetServiceStatus()
{
...
}
public ActionResult _GetEventLogErrors()
{
...
}
Each of these methods references a different class type that I have stored in my Model, Service and Event respectively. These can be seen below:
public class Service
{
public string Name { get; set; }
public string Status { get; set; }
}
public class Event
{
public string Source { get; set; }
public string EntryType { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
}
What I want to do is to show the results of these methods on a single view. I already have this working for the Services check, with the results displaying correctly, but I cannot find how to add in the Event results.
What I have currently in my view is below:
#{
ViewBag.Title = "Monitoring";
}
#model IEnumerable<SystemMonitoringTool.Models.Monitoring.Service>
<div style="width:25%">
<span>
Services
</span>
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>
</div>
Can someone help me find a way to display these side by side on the same view? Will be happy to provide more info if needed.

Two options:
Use child actions. Instead of directly going to one of these individual controller actions, instead add an action like:
public ActionResult Monitoring()
{
return View();
}
You'll notice this action doesn't do much. It's just rendering a view. Then you'll need to move your HTML for services/events into partial views. For example, _GetServiceStatus.cshtml and _GetEventLogErrors.cshtml, where the model for each will be a collection of your Service or Event types, respectively. Finally, in your Monitoring.cshtml view (based on the action name above), you'll add:
#Html.Action("_GetServiceStatus")
#Html.Action("_GetEventLogErrors")
This view doesn't need a model, because it's not directly working with anything.
Use a view model that encapsulates your two collections:
public class MonitoringViewModel
{
public List<Service> Services { get; set; }
public List<Event> Events { get; set; }
}
Then you'll still need a unifying action. But here, you'll populate both lists. Basically, you'll just be moving your two existing actions into one:
public ActionResult Monitoring()
{
var model = new MonitoringViewModel
{
Services = /* code to retrieve services */,
Events = /* code to retrieve events */
}
return View(model);
}
Then, you can iterate through each list independently to build your HTML:
Monitoring.cshtml (again, based on the action name)
#model Namespace.To.MonitoringViewModel
...
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model.Services) { // notice the change here
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>

Related

Razor page: complex modeling binding not updating model

So I'm trying to create a little tool where a user can select a set of days. I however need quite a complex model for extra data.
public class DayRange{
...
List<DaySelected> Days
...
}
public class DaySelected{
public bool Selected{ get; set;}
public string DayName {get; set;}
public DaySelected(string Day){
Selected = false;
DayName = day;
}
}
My Razorpage looks like this:
#Model DayRange
...
<form asp-action="RegisterSelection" asp-controller="DayRegister">
<table>
#foreach (var Day in Model.Days)
{
<tr>
<td>
<input asp-for=#Day.Selected />
</td>
</tr>
}
</table>
<button type="submit">Confirm</button>
</form>
My method Registerselection looks like this:
[HttpPost]
public IActionResult RegisterSelection(DayRange dr){
...
}
However, whenever I change any of textboxes, all of the selected bool values remain the same. Can anybody help me on my way? Thanks in advance!
Here is a demo to pass data to action correctly:
Model:
public class DayRange
{
public List<DaySelected> Days { get; set; }
}
public class DaySelected
{
public bool Selected { get; set; }
public string DayName { get; set; }
public DaySelected()
{
}
public DaySelected(string Day)
{
Selected = false;
DayName = Day;
}
}
View:
#Model DayRange
<form asp-action="RegisterSelection" asp-controller="DayRegister">
<table>
#{ var i = 0;}
#foreach (var Day in Model.Days)
{
<tr>
<td>
<input asp-for=#Day.Selected name="Days[#i].Selected" />
#Day.DayName
<input asp-for=#Day.DayName name="Days[#i].DayName" hidden />
</td>
</tr>
i ++;
}
</table>
<button type="submit">Confirm</button>
</form>
result:

Does .net core handle related look up differently than standard asp.net mvc for child records?

In one of my views I have a series of child tables, where the child table has a related lookup- ie not a grandchild table. The related lookup is a description for a code. In standard asp.net mvc, I can point to that related table and display the description in that child record. In .net core it comes up as blank. If I just display the code in the child records it works.
Am I missing something?
Code for model, controller and view below:
Traditional asp.net mvc that works:
Model
Model
namespace ServeMeHR.Models
{
using System;
using System.Collections.Generic;
public partial class TeamAssignmentHistory
{
public int Id { get; set; }
public string AssignedBy { get; set; }
public System.DateTime DateAssigned { get; set; }
public int ServiceRequest { get; set; }
public int Team { get; set; }
public virtual ServiceRequest ServiceRequest1 { get; set; }
public virtual Team Team1 { get; set; }
}
}
Relevant Controller portion
public async Task<ActionResult> Details(int? id)
{
ViewBag.FileUp = db.ApplicConfs.Select(s => s.FileSystemUpload).FirstOrDefault();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ServiceRequest serviceRequest = await db.ServiceRequests.FindAsync(id);
if (serviceRequest == null)
{
return HttpNotFound();
}
return View(serviceRequest);
}
Relevant view portion
<div id="tabs-2">
<table class="table">
<tr>
<th>Assigned BY</th>
<th>Date Assigned</th>
<th>Team</th>
</tr>
#foreach (var item in Model.TeamAssignmentHistories)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AssignedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateAssigned)
</td>
<td>
#Html.DisplayFor(modelItem => item.Team1.TeamDescription)
</td>
</tr>
}
</table>
</div>
For .Net Core -This doesnt work
Model
using System;
using System.Collections.Generic;
namespace ServeMeHRCore21.Models
{
public partial class TeamAssignmentHistories
{
public int Id { get; set; }
public string AssignedBy { get; set; }
public DateTime DateAssigned { get; set; }
public int ServiceRequest { get; set; }
public int Team { get; set; }
public ServiceRequests ServiceRequestNavigation { get; set; }
public Teams TeamNavigation { get; set; }
}
}
relevant controller code
Relevant part of controller
// GET: ServiceRequests/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var serviceRequests = await _context.ServiceRequests
.Include(s => s.MemberNavigation)
.Include(s => s.PriorityNavigation)
.Include(s => s.RequestTypeNavigation)
.Include(s => s.RequestTypeStepNavigation)
.Include(s => s.StatusNavigation)
.Include(s => s.TeamNavigation)
.Include(s => s.FileDetails)
.Include(s => s.ServiceRequestNotes)
.Include(s => s.StepHistories)
.Include(s => s.TeamAssignmentHistories)
.Include(s => s.IndividualAssignmentHistories)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.Id == id);
if (serviceRequests == null)
{
return NotFound();
}
return View(serviceRequests);
}
Relevant part of the view
<div id="tabs-2">
<table class="table">
<tr>
<th>Assigned BY</th>
<th>Date Assigned</th>
<th>Team</th>
</tr>
#foreach (var item in Model.TeamAssignmentHistories)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AssignedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateAssigned)
</td>
<td>
#Html.DisplayFor(modelItem => item.TeamNavigation.TeamDescription)
</td>
</tr>
}
</table>
</div>
The above view shows blank for the team description. The code for each of these is almost identical...
Depending on what version of EF Core you're running, there's various solutions. In EF 6 (what you're using in ASP.NET MVC), reference/collection properties with a virtual keyword participate in lazy-loading. EF dynamically subclasses your entity class, overriding the reference/collection properties (which is why virtual is necessary) and adds logic to query the database when the getter is hit. Then, when you access this property in your application, if the relationship is not available, EF transparently queries for it and fills it in, giving the semblance that it's always set and ready to go.
Before EF Core 2.1, lazy-loading was not an option at all. To have these properties have values, you need to either eagerly or explicitly load them:
Eager loading
var foo = await _context.Foos.Include(x => x.Bar).SingleOrDefaultAsync(x => x.Id == fooId);
Explicit loading
var foo = await _context.Foos.SingleOrDefaultAsync(x => x.Id == fooId);
await _context.Entry(foo).Reference(x => x.Bar).LoadAsync();
// use `Collection` rather than `Reference` for a collection type
EF Core 2.1 now supports lazy-loading, but you still need the virtual keyword, which you currently don't have on your entity class reference/collection properties. Also, you need to add in the appropriate services in Startup.cs:
services.AddDbContext<MyContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
However, I'd advise you to avoid going that route, especially since you don't seem to be aware that any of the stuff I mentioned was actually happening under the hood back in EF 6. It's very easy with lazy-loading to create all sorts of issues like N+1 queries that can cause major problems for you application. It's almost always better to eagerly load any related entities, so you can combine queries. Just because EF Core now supports lazy-loading, doesn't mean you need to use it.

why checkbox wont check on page load?

i have the following database table for the Compounds table (chemical compounds/elements in the periodic table) there are typos in table data so ignore them
the data is :
the controller :
public class CheckboxController : Controller
{
//
// GET: /Checkbox/
testdbEntities db = new testdbEntities();
[HttpGet]
public ActionResult Index()
{
var comps = db.Compounds.Select(c => new CompoundModel { Id=c.Id, CompoundName=c.Name, IsSelected=c.IsSelected}).ToList();
CheckboxVM cvm = new CheckboxVM { checkboxData=comps};
return View(cvm);
}
[HttpPost]
public string Index(IEnumerable<CheckboxVM> collection)
{
return "";
}
}
Model class CompoundModel is:
public class CompoundModel
{
public int Id { get; set; }
public string Code { get; set; }
public string CompoundName { get; set; }
public bool IsSelected { get; set; }
}
and the ViewModel CheckBoxVM:
public class CheckboxVM
{
public string Id { get; set; }
public string CompoundNmae { get; set; }
public bool IsSelected { get; set; }
public IEnumerable<CompoundModel> checkboxData { get; set; }
}
When the page loads it should display check boxes with names and if db table has checked on them (IsSelected=1) then they should be checked.In the post back i need to receive the id, of the user checked checkboxes. At the moment my code does meet the first requirement to check the checked checkboxes based on IsSelected on page load. Is there a way to fix this?
If you need a video with debugging please ask i will be happy to post : )
THE VIEW: (UPDATE)
#model recitejs1.Models.CheckboxVM
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
foreach (var item in Model.checkboxData)
{
#Html.CheckBoxFor(x=>x.IsSelected, (item.IsSelected)?new{#checked="check"}:null)#item.CompoundName
#Html.HiddenFor(x=>x.Id, item.Id)
#Html.HiddenFor(x=>x.CompoundNmae, item.CompoundName)
}
<br><br>
<input type="submit" name="name" value="Send" />
}
You cannot use a foreach loop to generate form controls. It generates duplicate name attributes (that have no relationship to your model) and duplicate id attributes (invalid html).
Create a custom `EditorTemplate for your model
In /Views/Shared/EditorTemplates/CompoundModel.cshtml
#model recitejs1.Models.CompoundModel
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.CompoundName)
#Html.CheckBoxFor(m => m.IsSelected)
#Html.LabelFor(m => m.CompoundName)
Then in the main view
#model recitejs1.Models.CheckboxVM
....
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.checkboxData)
<input type="submit" name="name" value="Send" />
}
The EditorFor() method will generate the correct html for each item in your collection
Note: You should inspect the html before and after you make this change to better understand how model binding works.
Note also that your POST method parameter needs to be
public string Index(CheckboxVM model)
since that's what the view is based on. However the only property of CheckboxVM that you use in the view is IEnumerable<CompoundModel> checkboxData in which case your view should be
#model IEnumerable<CompoundModel>
...
#Html.EditorFor(m => m)
and keep the POST method as it is (but change the GET method)

Access Model List Used in Another Model to view

I have the following code. I want to use the objects of List in my view where I have used DTOQuestions
//My Model Question
public class Question
{
public int Question_Id { get; set; }
public string Question_Title { get; set; }
public List<Reply> Reply_List { get; set; }
}
//My 2nd Model Answer
public class Reply
{
public int Reply_Msg { get; set; }
public int Attachment_Id { get; set; }
}
//View
#foreach(var item in Model)
{
foreach(var replylbl in item.Forum_Reply_List)
{
<th>#Html.DisplayNameFor(replymodel => replylbl.Reply_Msg)</th>
<th>#Html.DisplayNameFor(replymodel => replylbl.Attachment_Id)</th>
}
}
#foreach (var item in Model) {
<tr>
<td>
#foreach (var reply in item.Forum_Reply_List){#Html.DisplayFor(modelReply => reply.Reply_Msg)}
</td>
<td>
#foreach (var reply in item.Forum_Reply_List){#Html.DisplayFor(modelReply => reply.Attachment_Id)}
</td>
I have these two models and following view. Now I want to access Answer (reply) model's objects in my view which has model type Question
But in view I am unable to display headers for those columns..

Base Models and Derived Models in MVC

I'm putting together an MVC application where I have created a base Model which then has four derived Models, all of which inherit from the base Model:
public abstract class BaseFund
{
public string Name { get; set; }
public int AccountId { get; set; }
public abstract decimal Value { get; }
public virtual InvestmentAccount Account { get; set; }
}
one of the derived Models:
public class ShareFund : BaseFund
{
public string ISIN { get; set; }
public ShareFundType FundType { get; set; }
public IncomeStatus IncomeStatus { get; set; }
public decimal TotalShares {
get
{
ICollection<ShareTransaction> tt = this.Transactions;
var outgoings = Transactions.Count > 0 ? Transactions.Where(t => t.TransactionType.IsOutgoing.Equals(true)).Sum(a => a.Units) : 0;
var incomings = Transactions.Count > 0 ? Transactions.Where(t => t.TransactionType.IsOutgoing.Equals(false)).Sum(a => a.Units) : 0;
return incomings - outgoings;
}
}
public override decimal Value
{
get
{
return this.TotalShares * (this.SharePrice / 100);
}
}
public decimal SharePrice { get; set; }
public ICollection<ShareTransaction> Transactions { get; set; }
}
And there are three other derived Models that are similar. All of the Models are POCO's used by Entity Framework.
EDIT : The view is standard MVC scaffolding stuff at this stage:
<table>
<tr>
<th>
Name
</th>
<th>
Account
</th>
<th>
Value
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Account.AccountNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.Value)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
What I want to do is create a View that shows the fields from the Base Fund (Name, AccountId and Value). The problem is that the logic that works out the Value is different for every derived Model - in the case of ShareFund it uses TotalShares, so the BaseFund used in the View must be cast to be of type ShareFund. The other derived Models don't necessarily have TotalShares as a property.
With that in mind:
Is using inheritance with Models in this way the way to go? If so, how do I get the fields specific to the derived models in the View?
If using inheritance is not recommended for this type of scenario, what should I use in its place?
Thanks
There turned out to be a simple answer to this. One of the Transaction properties further down the chain wasn't being populated from the database by EF. This meant TransactionType was null which led to an null reference error in TotalShares. I misread this as meaning there was a problem with the property as it belonged to the derived Model and not the base Model.
Thanks Lazarus, your comment led me to the problem.