How would I sort and display data in a tabular way with entities gathered from a Linq request? - asp.net-mvc-3

How would I display data from the following database entity in a format that looks like a table:
public class Attendance
{
public int AttendanceID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public int AttendanceDay { get; set; }
public bool Present { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
I would want to find all rows in the Attendance db entry that had CourseID == x; So I would use something like:
AttendanceData = Attendance.Where(s => s.CourseID == x); // I think
Then I would need to be able to sort this information in my view to display it in a way that makes sense. I would want to have a data on screen with all of the present/not present values sorted in a table with StudentIDs listed on the left and AttendanceDays listed accross the top.
How would I sort and display this information?
UPDATE:
Using the following code (along with Mvc WebGrid) - I can get a grid of some sort to appear in my view.
Controller:
IEnumerable<Attendance> model = db.Attendance.Where(s => s.CourseID == 4);
return View(model);
View:
#model IEnumerable<MyProject.Models.Attendance>
<div>
#{
var grid = new WebGrid(Model, defaultSort: "Name");
}
#grid.GetHtml()
</div>
However, the grid is not organized in a manner that is useful for my needs.
I want the top of my displayed table to read:
Day 1 | Day 2 | Day 3 | etc until the max value of "Attendance Day" (which is dictated at the creation of a Course that a student signs up for).
I want the left side of the displayed table to read:
Student ID 1
Student ID 5
Student ID 6
Student ID etc . . until all of the students within the data set have been displayed.
I think I need to use something along the lines of this in my controller:
var model = from s in db.Attendance
where s.CourseID == 4
group s.AttendanceDay by s.StudentID into t
select new
{
StudentID = t.Key,
Days = t.OrderBy(x => x)
};
return View(model);
But I need an IEnumerable<> returned to my view using Mvc WebGrid -- I am getting somewhere, just still a little lost along the way. Can I get a nudge in the right direction?

For a fairly typical set of requirements, it sounds like this would be a good candidate for the ASP.NET WebGrid. It's flexible, allows for paging, sorting, formatting, etc. I've used it for a few projects and works just like any of the other HTML helpers that you're probably used to in ASP.NET MVC.
Here's a good starting place.

Related

Foreach ViewBag data gives 'object' does not contain a definition for 'var'

I have a controller that is building a query from Linq to Sql to pass into the ViewBag.products object. The problem is, I cannot loop using a foreach on it as I expected I could.
Here's the code in the controller building the query, with the .ToList() function applied.
var products = from bundles in db.Bundle
join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
join product in db.Products on bProducts.productID equals product.productID
join images in db.Images on product.productID equals images.productID
where bundles.bundleInactiveDate > DateTime.Now
select new {
product.productName,
product.productExcerpt,
images.imageID,
images.imageURL
};
ViewBag.products = products.ToList();
Since I am using a different model on the Index.cshtml for other items needed, I thought a simple Html.Partial could be used to include the viewbag loop. I have tried it with the same result with and without using the partial and simply by using the foreach in the index.cshtml. A snippet that includes the partial is below:
<div id="bundle_products">
<!--build out individual product icons/descriptions here--->
#Html.Partial("_homeBundle")
</div>
In my _homeBundle.cshtml file I have the following:
#foreach (var item in ViewBag.products)
{
#item
}
I am getting the ViewBag data, but I am getting the entire list as output as such:
{ productName = Awesomenes Game, productExcerpt = <b>Awesome game dude!</b>, imageID = 13, imageURL = HotWallpapers.me - 008.jpg }{ productName = RPG Strategy Game, productExcerpt = <i>Test product excerpt</i>, imageID = 14, imageURL = HotWallpapers.me - 014.jpg }
What I thought I could do was:
#foreach(var item in ViewBag.Products)
{
#item.productName
}
As you can see, in the output, productName = Awesomenes Game. However, I get the error 'object' does not contain a definition for 'productName' when I attempt this.
How can I output each "field" so to say individually in my loop so I can apply the proper HTML tags and styling necessary for my page?
Do I need to make a whole new ViewModel to do this, and then create a display template as referenced here: 'object' does not contain a definition for 'X'
Or can I do what I am attempting here?
*****UPDATE*****
In my Controller I now have the following:
var bundle = db.Bundle.Where(a => a.bundleInactiveDate > DateTime.Now);
var products = from bundles in db.Bundle
join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
join product in db.Products on bProducts.productID equals product.productID
join images in db.Images on product.productID equals images.productID
where bundles.bundleInactiveDate > DateTime.Now
select new {
product.productName,
product.productExcerpt,
images.imageID,
images.imageURL
};
var bundleContainer = new FullBundleModel();
bundleContainer.bundleItems = bundle;
return View(bundleContainer);
I have a model, FullBundleModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace JustBundleIt.Models
{
public class FullBundleModel
{
public IQueryable<Bundles> bundleItems { get; set; }
public IQueryable<Images> imageItems { get; set; }
}
}
and my View now has
#model IEnumerable<JustBundleIt.Models.FullBundleModel>
#foreach (var item in Model)
{
<div class="hp_bundle">
<h3>#Html.Display(item.bundleName)</h3>
</div>
}
If I remove IEnumerable from the model reference, the foreach errors out that there is no public definition for an enumerator.
In the #Html.Display(item.bundleName) it errors out that the model has no definition for bundleName. If I attempt
#foreach(var item in Model.bundleItems)
I get an error that bundleItems is not defined in the model.
So what don't I have wired up correctly to use the combined model?
Do I need to make a whole new ViewModel to do this, and then create a
display template as referenced here...
Darin's answer that you linked states the important concept: Anonymous types are not intended for use across assembly boundaries and unavailable to Razor. I would add that it's rarely a good idea to expose anonymous objects outside of their immediate context.
Creating a view model specifically for view consumption is almost always the correct approach. View models can be reused across views if you are presenting similar data.
It's not necessary to create a display template, but it can be useful if you want to reuse the display logic. HTML helpers can also fill a similar function of providing reusable display logic.
Having said all of that, it's not impossible to pass an anonymous type to a view, or to read an anonymous type's members. A RouteValueDictionary can take an anonymous type (even across assemblies) and read its properties. Reflection makes it possible to read the members regardless of visibility. While this has its uses, passing data to a View is not one of them.
More reading:
Can I pass an anonymous type to my ASP.NET MVC view?
Dynamic Anonymous type in Razor causes RuntimeBinderException
Why not create a new model that contains all of the data that you need?
Example One:
public class ContainerModel
{
public IQueryable<T> modelOne;
public IQueryable<T> modelTwo;
}
This will allow you to access either of your queries in Razor:
#model SomeNamespace.ContainerModel
#foreach (var item in Model.modelOne)
{
//Do stuff
}
I personally avoid using ViewBag at all and store everything I need in such models because it's NOT dynamic and forces everything to be strongly typed. I also believe that this gives you a clearly defined structure/intent.
And just for clarity's sake:
public ViewResult Index()
{
var queryOne = from p in db.tableOne
select p;
var queryTwo = from p in db.tableTwo
select p;
var containerModel = new ContainerModel();
containerModel.modelOne = queryOne;
containerModel.modelTwo = queryTwo;
return View(containerModel);
}
Example Two:
public class ContainerModel
{
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
public Nullable<DateTime> startDate { get; set; }
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
public Nullable<DateTime> endDate { get; set; }
public SelectList dropdown { get; set; }
public IQueryable<T> modelOne { get; set; }
public IQueryable<T> modelTwo { get; set; }
}
In this case you've stored 3 other items in the model with your 2 queries. You can use the Html helpers to create a Drop Down List in Razor:
#Html.DropDownList("dropdown", Model.dropdown)
And you can use the DisplayFor helper to display your dates as defined in your model with Data Annotations:
#Html.DisplayFor(a => a.startDate)
This is advantageous IMO because it allows you to define all of the data that you want to make use of in your View AND how you plan to format that data in a single place. Your Controller contains all of the business logic, your Model contains all of the data/formatting, and your View is only concerned with the content of your page.

multiple models to partial view

I am teaching myself asp .net mvc3.
I have a partial view which uses various models and lookup type db. I want to keep it strongly typed and use it in multiple places but I am not sure how to implement it.
The example below should explain it better. This question might get a bit long and I appreciate your patience.
The partial view basically gives a small description of the property. A snippet of the ‘_PropertyExcerptPartial’ is below:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>#Html.DisplayFor(model => model.Property.NoOfBedrooms) bedroom #Html.DisplayFor(model => model.FurnitureType.FurnitureTypeDescription) flat to Rent </h3>
…
</div>
I want to keep this partial view strongly typed and use it in multiple places. The model that it is strongly typed to is as follows:
public class PropertyExcerptViewModel
{
public Property Property { get; set; }
public FurnitureType FurnitureType { get; set; }
}
The two 2 database that this model looks up is as follows:
public class Property
{
public int PropertyId {get; set; }
...
public int NoOfBedrooms {get; set;}
public int FurnishedType { get; set; }
...
}
public class FurnishedType
{
public int FurnishedTypeId { get; set; }
public string FurnishedTypeDescription { get; set; }
}
The furnished type database is basically just a lookup table with the following data:
1 - Furnished
2 - Not Furnished
3 - Part Furnished
4 - Negotiable
I have many such lookups in that I only store an int value in the property database which can be used to look up the description. These databases are not linked to property database and the value of furniture type is read via a function GetFurnitureType(id). I pass stored int value of Property.FurnitureType as the id.
However, I encounter a problem when I try to use this partial view as I am not sure how to pass these multiple models from a view to partial view.
Say I am trying to create an ‘added property’ page. This page basically list the properties added by the logged in user. To facilitate this, I have created another function called GetAddedProperties(userId) that return the properties added by a particular user. In the ‘added property’ view, I can call a foreach function to loop through all the properties returned by GetAddedProperties and display the _PropertyExcerptPartial. Something like this:
<div>
#foreach (var item in //Not sure what to pass here)
{
#Html.Partial("_PropertyExcerptPartial",item)
}
</div>
However, I can’t use the partial view to display information as it will display the int value of furniture type stored in the property database and I am not sure how to get the corresponding FurnitureTypeDescription and pass it to the partial view from the ‘added property’ page.
Any help would be appreciated. Thanks a lot!
You should start by designing a real view model, not some class that you suffix its name with ViewModel and stuff your domain models inside. That's not a view model.
So think of what information you need to work with in your view and design your real view model:
public class PropertyExcerptViewModel
{
public int NoOfBedrooms { get; set; }
public string FurnishedTypeDescription { get; set; }
}
and then adapt your partial to work with this view model:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>
#Html.DisplayFor(model => model.NoOfBedrooms)
bedroom
#Html.DisplayFor(model => model.FurnitureTypeDescription)
flat to Rent
</h3>
...
</div>
OK, now that we have a real view model let's see how we could populate it. So basically the main view could be strongly typed to a collection of those view models:
#model IEnumerable<Test.ViewModels.PropertyExcerptViewModel>
<div>
#foreach (var item in Model)
{
#Html.Partial("_PropertyExcerptPartial", item)
}
</div>
and the last bit of course is the main controller that will do all the db querying and building of the view model that will be passed to the main view:
[Authorize]
public ActionResult SomeAction()
{
// get the currently logged in username
string user = User.Identity.Name;
// query the database in order to fetch the corresponding domain entities
IEnumerable<Property> properties = GetAddedProperties(user);
// Now let's build the view model:
IEnumerable<PropertyExcerptViewModel> vm = properties.Select(x => new PropertyExcerptViewModel
{
NoOfBedrooms = x.NoOfBedrooms,
FurnishedTypeDescription = GetFurnitureType(x.FurnishedType).FurnishedTypeDescription
});
// and finally pass the view model to the view
return View(vm);
}
Be careful with the lazy nature of EF if that is the ORM that you are using in order not to fall into the SELECT N + 1 trap.

Binding DropdownList and maintain after postback

I am using MVC3. I'm binding the dropdown with the Data coming from a service. But after the page posts back and a filter applies to list, the dropdown shows the filter record value in the grid because I always bind the list coming from the service.
However, I want the dropdown to always show all the Records in the database.
I don't understand your question that clearly. But it seems that it is a dropdown that you have on your view? I also have no idea what you are trying to bind so I created my own, but have a look at my code and modify it to fit in with your scenario.
In your view:
#model YourProject.ViewModels.YourViewModel
On the view there is a list of banks in a dropdown list.
Your banks dropdown:
<td><b>Bank:</b></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
Your view model that will be returned to the view:
public class YourViewModel
{
// Partial class
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
Your create action method:
public ActionResult Create()
{
YourViewModel viewModel = new YourViewModel
{
// Get all the banks from the database
Banks = bankService.FindAll().Where(x => x.IsActive)
}
// Return the view model to the view
// Always use a view model for your data
return View(viewModel);
}
[HttpPost]
public ActionResult Create(YourViewModel viewModel)
{
if (!ModelState.IsValid)
{
// If there is an error, rebind the dropdown.
// The item that was selected will still be there.
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
return View(viewModel);
}
// If you browse the values of viewModel you will see that BankId will have the
// value (unique identifier of bank) already set. Now that you have this value
// you can do with it whatever you like.
}
Your bank class:
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
This is simple as it gets. I hope this helps :)
PS: Please remember with future posts, always give as much detail as possible so that we can help you better. Also don't forget to display code samples so that we can see what you have already done. The more details that we can have the better.
When you post the model back to the create[Http Post] action is it not possible to have the list of dropdown list values for the banks binded back to the model. I see that if the model is invalid, you call the code
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
to get a list of all the banks again which I assume you need to hit the database again.
Thanks

View with more than 3 table data in MVC3

I am building an intranet web site using MVC3. I have a view in which I want to display 3 tables. All 3 tables data are being fetched from one database only. I have 3 different stored procedures for 3 tables data. I am thinking of using 3 different partial views in index.cshtml. And can use 3 stored procedure to fetch data.
Can someone help me how to set controller index method so that it return view for all 3 partial views? I can use viewbag. But I read articles online and it says that its not a good approach to use viewbag. Currently Index method in controller returns view for only one partial view by using 1 stored procedure.
Thanks in advance.
Create a custom view model.
Model:
public class MyViewModel
{
public DataSet Result1 { get; set; }
public DataSet Result2 { get; set; }
public DataSet Result3 { get; set; }
}
Controller action:
public ViewResult myAction()
{
var myViewModel = new MyViewModel({
Result1 = XXX, // get result 1
Result2 = XXX, // get result 2
Result3 = XXX // get result 3
});
return View(myViewModel);
}
View:
#model MyViewModel;
<div id="results1">#Model.Result1.Tables[0].Rows[0]["someValueFromDB"].ToString();</div>
... etc.

LINQ, how to specify to include items from List<>

Ok, I am trying to find get all subcategories from my database (using query result shaping) that belong to the category that I supply. My class SubCategory includes a List<> of Categories.
The problem is that in the linq statement, g is referring to SubCategory (which in the end contains Categories<>). So the statement below is not allowed.
How do I change the Linq statement to generate the correct SQL query to include all SubCategories that contain the matching Category.
public class SubCategory
{
public int SubCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Article> Articles { get; set; }
public List<Category> Categories { get; set; }
}
//incorrect code below:
var SubCategories = storeDB.SubCategories.Include("Categories").Single(g => g.Name == category);
This worked for me (maybe too simple):
var Category = storeDB.Categories.Include("SubCategories").Single(c => c.Name == category);
return Category.SubCategories;
I find it a bit confusing that each SubCategory can belong to more than one Category - have you got this relationship the right way around?
Regardless, I think its probably more readable if you change the select to work on Categories first - i.e. something like:
var subCatQuery = from cat in storeDB.Categories
where cat.Name == category
select cat.SubCategories;
which you can then execute to get your IEnumerable<>:
var subCategories = subCatQuery.ToList();
I find that much more readable/understandable.
(I also find the query syntax easier to read here than the fluent style)
my preferred answer would be to use a linq join. if there aren't but 2 data sets you can use the linq operations .join and .groupjoin vs a from statement. this would be my approach.
Dictionary<MainQuery, List<subQuery>> Query =
dbStore.List<MainQuery>.include("IncludeAllObjects")
.groupjoin(db.List<SubTable>.Include("moreSubQueryTables"),
mainQueryA=>mainQueryA.PropToJoinOn,
subQueryB => sunQueryB.PropToJoinOn,
((main, sub)=> new {main, sub})
.ToDictionary(x=>x.main, x=>x.sub.ToList());

Resources