Display multiple models in a single view - c#

I have to create one view of invoice. I have many models (tables) and I want to display all data from multiple models in one view of invoice. I created one empty (without model) view and put into a partial views. One of Partial View return one view with one model but this solution return me exception... This is my work:
This is my action in controller:
public ActionResult Customer()
{
var data = db.Customer;
return PartialView("Index", data);
}
public ActionResult Invoice()
{
var data = db.Invoice;
return PartialView("Index", data);
}
public ActionResult Dealer()
{
var data = db.Dealer;
return PartialView("Index", data);
}
public ActionResult Paid()
{
var data = dane.Paid;
return PartialView("Index", data);
}
public ActionResult Products()
{
var data = dane.Products;
return PartialView("Index", data);
}
This is one of partial view:
#model IEnumerable<Invoice_v1._0.Models.Products>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Price)
</th>
<th>
#Html.DisplayNameFor(model => model.Amount)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.DisplayFor(modelItem => item.Amount)
</td>
</tr>
}
</table>
This is my "Index" view with Partial Views:
#Html.Partial("_Customer")
#Html.Partial("_Invoice")
#Html.Partial("_Dealer")
#Html.Partial("_Paid")
#Html.Partial("_Products")
How can I fix them?

If you insist on having a single view, then you can make rendering of partial views conditional:
if (Model != null && Model is Customer)
Html.Partial("_Customer", Model as Customer)
, and so on.
On a related note, since all the models are exclusive (you never have more than one object), I don't see the point of having one Index view. You already have one (partial) view per class, so why not just use them as regular views?
Update
If you choose to return separate view from each of the action methods, then you can do that by specifying the name of each view. Refer to this question for details: How to return ActionResult with specific View (not the controller name)
In your particular case, that would mean:
public ActionResult Customer()
{
return View("_Customer", db.Customer);
}
public ActionResult Invoice()
{
return View("_Invoice", db.Invoice);
}
public ActionResult Dealer()
{
return View("_Dealer", db.Dealer);
}
public ActionResult Paid()
{
return View("_Paid", db.Paid);
}
public ActionResult Products()
{
return View("_Products", db.Products);
}
Make sure that each of the views expects a strongly typed model. That will make it easy to implement the views.

While your approach may work, you will have less or no control over the data (if you want to let's assume join or use any of the parent data). In such situations what I do is create a ViewModel with more than one objects as part of it. For example
public class InvoiceViewModel
{
public InvoiceHead Header { get;set; }
public IList<InvoiceDetail> Details { get; set; }
}
So now I can populate this View Model before going into my view.
public ActionResult Invoice(int id)
{
InvoiceViewModel viewModel = new InvoiceViewModel();
viewModel.Header = db.InvoiceHead.SingleOrDefault(i => i.ID == id);
if(viewModel.Header != null)
{
viewModel.Details = db.Details.Where(d => d.Inv_id == id).ToList();
}
return View(viewModel);
}
Now I can design my view with either Partial views or one whole view for my invoice data.
I will put as model the IEnumerable or IList
if I use partial for both it will be
#model db.Objects.InvoiceHead // for the header
#model IEnumerable // for the details

in html code will be like that
#Html.Partial("~/Views/Customers/_AttachFileList.cshtml")
use your own folder path. hope works

Related

How to count sum of products

I have a action , ViewModel that shows totaly of product grouped by ProductName. But this doesn't shows me how many in every department.
Let say I have 20 computers in It-department and 10 computers in adminstration department then my code shows my productname which is "Computers".
And Totaly withch is 30 but not How many in it-department and the same for administration.
And the same for Servers or other products.
So I'am trying to use this action to get number of products in every department. I know alrteady departemnt Id's and those department are not populate dynamicaly.
// This is in my HomeController and this is my action trying to get sum of every department
[HttpGet]
public ActionResult GetStatusOfDepartments(int ProductId, int departmentId )
{
var products = context.Inventory.Where(x => x.ProductId == ProductId && x.departmentId == departmentId).FirstOrDefault();
if(products != null)
{
return Content(products.Status.ToString());
}
else
{
return Content("No products");
}
}
And I want to call "GetStatusOfDepartments" action In this ViewModel but this givs me null. Can you please help me what is wrong
to call action in this ViewModel?
#model IEnumerable<ProductsInventory.Models.StatusModel>
<table class="table">
<tr>
<th>
Produkt name
</th>
<th>
It-Department
</th>
<th>
Adminstration
</th>
<th>
TotalProducts
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProductName)
</td>
<td>
// Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 1 })
</td>
<td>
// The same Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 2 })
</td>
<td>
#Html.DisplayFor(modelItem => item.Status)
</td>
</tr>
}
</table>
There are a couple of things that stand out with what you have done so far.
It's very unusual to see an ActionResult returning Content(). That means a controller is providing a raw string to a view, which is not really the point of controllers.
Currently the view has #Html.Action() requests embedded within a loop, which is a big indicator that the view is not being provided with an appropriate model.
The question's title suggests it has more to do with a database query than MVC.
At the moment, a single page load will result in many database queries, at least twice the number of product types. It is best to perform as few database queries as possible as they often have a big impact on the time it takes to load each page.
In my attempt to answer this question it became obvious that there is a relatively complex database query required to create the model. Maybe you have ended up using the approach above as a way to get past that, so I will try and answer this question without a complex database query while still adhering to the MVC design patterns (that should avoid the issues mentioned above)
Create a model
You already have a pretty good idea of what you want to display on the view. It's different enough from the database models that we should create a new model.
public class ProductsInDepartments
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int ITTotal { get; set; }
public int AdminTotal { get; set; }
public int Status { get; set; }
}
This is simply a property for each column in your view. I think you only have two departments and their names aren't mapped in the database. If that's not correct then I would suggest a different model and approach.
Controller
The controller needs to prepare a model. In this case, we will get all of the data, then transform it to the model structure we need:
public enum Departments // Enumerating a database key like this can be helpful if the database itself doesn't describe the numbers in a lookup table or something
{
IT = 1,
Admin = 2
};
[HttpGet]
public ActionResult Status()
{
var Inventory = context.Inventory.ToList(); // Get all records
var ViewModel = new List<Models.ProductsInDepartments>();
foreach (int ProductId in Inventory.Select(e => e.ProductId).Distinct().ToList())
{
ViewModel.Add(new Models.ProductsInDepartments()
{
ProductId = ProductId,
ProductName = Inventory.First(e => e.ProductId == ProductId).ProductName,
AdminTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.Admin),
ITTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.IT),
Status = Inventory.First(e => e.ProductId == ProductId).Status // I'm not sure what you are trying to do with Status, so you might need to change this
});
}
return View(ViewModel);
}
View
Now the view is very straightforward.
#model List<ProductsInventory.Models.ProductsInDepartments>
<table class="table">
<tr>
<th>Product Name</th>
<th>IT Department</th>
<th>Administration</th>
<th>Total Products</th>
</tr>
#foreach (var Item in Model)
{
<tr>
<td>#Model.ProductName</td>
<td>#Model.ITTotal.ToString()</td>
<td>#Model.AdminTotal.ToString()</td>
#if (Model.Status == 0)
{
<td>No Products</td>
}
else
{
<td>#Model.Status</td>
}
</tr>
}
</table>
Again, I'm not sure what you're trying to do with status but for string overrides you can do those in the view like this and it's perfectly fine. The view should handle various aspects of presentation-layer concerns.
In #Html.Action the first parameter should be action name and second parameter should be controller name. You need to change your code to match that.

Using checkbox for multiple deletion in asp-mvc [duplicate]

I have a view with a table that displays my model items. I've extracted the relevant portions of my view:
#model System.Collections.Generic.IEnumerable<Provision>
#using (Html.BeginForm("SaveAndSend", "Provision", FormMethod.Post))
{
if (Model != null && Model.Any())
{
<table class="table table-striped table-hover table-bordered table-condensed">
<tr>
...
// other column headers
...
<th>
#Html.DisplayNameFor(model => model.IncludeProvision)
</th>
...
// other column headers
...
</tr>
#foreach (var item in Model)
{
<tr>
...
// other columns
...
<td>
#Html.CheckBoxFor(modelItem => item.IncludeProvision)
</td>
...
// other columns
...
</tr>
}
</table>
<button id="save" class="btn btn-success" type="submit">Save + Send</button>
}
...
}
This works fine and the checkbox values are displayed correctly in the view depending on the boolean value of the IncludeProvision field for the given model item.
As per Andrew Orlov's answer, I've modified the view and controller and the SaveAndSend() controller method is now:
[HttpPost]
public ActionResult SaveAndSend(List<Provision> provisions)
{
if (ModelState.IsValid)
{
// perform all the save and send functions
_provisionHelper.SaveAndSend(provisions);
}
return RedirectToAction("Index");
}
However, at this point the passed in model object is null.
Including the Provision model object for completeness:
namespace
{
public partial class Provision
{
...
// other fields
...
public bool IncludeProvision { get; set; }
}
}
My question is, what is the best way to grab the checked/unchecked value from each checkbox and update the session IncludeProvision field for each model item when the 'SaveAndSend' button is clicked?
You cannot use a foreach loop to generate form controls for properties in a collection. It creates duplicate name attributes (in your case name="item.IncludeProvision") which have no relationship to your model and duplicate id attributes which is invalid html. Use either a for loop (you models needs to be IList<Provision>
for(int i = 0; i < Model.Count; i++)
{
<tr>
<td>....</td>
<td>#Html.CheckBoxFor(m => m[i].IncludeProvision)<td>
</tr>
}
or create an EditorTemplate for typeof Provision. In /Views/Shared/EditorTemplates/Provision.cshtml (note the name of the template must match the name of the type)
#model Provision
<tr>
<td>....</td>
<td>#Html.CheckBoxFor(m => m.IncludeProvision)<td>
</tr>
and in the main view (the model can be IEnumerable<Provision>)
<table>
#Html.EditorFor(m => m)
</table>
As #mattytommo said in comments, you should post your model to controller. It can be done with putting your checkbox inside a form. After clicking on button "Save and exit" all data from inputs inside this form will be serialized and sent to your controller where you can perform manipulations with session variables and so on. After that you can redirect wherever you like.
Model
public class YourModel
{
...
public bool IncludeProvision { get; set; }
...
}
View
#model YourModel
...
#using (Html.BeginForm("SaveAndSend", "Test", FormMethod.Post))
{
...
#Html.CheckBoxFor(model => model.IncludeProvision)
...
<button type="submit">Save and send</button>
}
...
Controller
public class TestController : Controller
{
...
[HttpPost]
public ActionResult SaveAndSend(YourModel model)
{
if (ModelState.IsValid)
{
// Some magic with your data
return RedirectToAction(...);
}
return View(model); // As an example
}
...
}

Filter a property IENumerable to only include items that pass a test

I have a list of items, Articles, and each article can have a series of reviews. Right now I grab the list of articles from my database using Entity Framework in my controller, db.Articles and I send that to my view. In my view I want to display all the articles and some of the reviews. For instance, the reviews done by a certain user.
The way I do this know looks like:
#foreach (var item in Model) { //item is a article, model is IEnumerable<Article>
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus.Where(u=> u.user.Id == ViewBag.userId))
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}
I feel like there should be a way in my Controller to send Articles that already have this filter applied so that I don't have to do it in my view (since it can be a bit slow).
Primarily it is not good that you are using the Entity Framework object as model for your view. Model in MVC is conceptually still element in UI layer, but EF is far away from it in persistence layer. And you should have at least domain and business logic layer in between.
What I usually do is to define the Model as an extra class, I construct the instance of class in controller action handler and I take all the data I need within the view from that instance (which is available in view as Model indeed).
In your case the class(es) would look somehow like this:
public class ArticleModel {
public string Title { get; set; }
// other article properties
private IEnumerable<ReviewModel> reviews;
public IEnumerable<ReviewModel> Reviews {
get {
if (reviews == null) {
// lazy loader to get ReviewModels from your business/domain logic
}
return reviews;
}
}
}
public class ArticlesPageModel {
public string PageTitle { get; set; }
private IEnumerable<ArticleModel> articles;
public IEnumerable<ArticleModel> Articles {
get {
if (articles == null) {
// lazy load articles from business/domain logic
}
return articles;
}
}
}
etc... I hope you see the pattern...
Usually my "model" classes have a constructor, which takes domain object (EF entity in your case) and they know how to initialize themselves.
I think in the action in your controller you can do something like this:
var allArticles = new IEnumerable<Article>();
allArticles = db.GetAllArticles(); //or whatever method that you have for grabing everything from db
var filteredArticles = allArticles.FinAll(x=>x.ReviewStatus != null && x.ReviewStatus.Count > 0 && x.ReviewStatus.Exists(u=> u.user.Id == 1234) && x.ReviewStatus.Exists(u.otherCriteria = 4321 ));
foreach(var article in filterArticles)
{
var specificReviews_1 = article.ReviewStatus.FinaAll(u=> u.user.Id == 1234).Take(3);
var specificReviews_2 = article.ReviewStatus.FinaAll(u=> u.user.Id != 1234 && u.otherCriteria = 4321).Take(3);
article.ReviewStatus = new List<ReviewStatus>();
article.ReviewStatus.AddRange(specificReviews_1 );
article.ReviewStatus.AddRange(specificReviews_2 );
}
return View("yourView", filterArticles);
and then in view do something like this
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus)
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}

pass var to IEnumerable view

This is my controller:
[HttpPost]
public ActionResult RegisterUser(User user)
{
var users = new User(user);
Context.Users.Insert(user);
return RedirectToAction("RegisterdUsers");
}
public ActionResult RegisterdUsers()
{
var users = Context.Users.FindAll();
return View(users);
}
And here is my view:
#model IEnumerable<SportsFantasy_2._0.Models.User>
#{
ViewBag.Title = "RegisterdUsers";
}
<h2>RegisterdUsers</h2>
<table>
<tr>
<th>
#Html.DisplayNameFor(m => m.username)
</th>
</tr>
So as you can see i send users to my view and i want to use IEnumerable to iterate through users. But when i try to use the model m=> m.username it cant find username?
What am i doing wrong?
m represents your Model. And your model is IEnumerable<SportsFantasy_2._0.Models.User> which doesn't have a username property.Use a loop if you want to display all users.
you have to iterate as it is IEnumerable of some type not a simple type:
#foreach(var item in Model)
{
<th>
#Html.DisplayNameFor(m => item.username)
</th>
}

Custom ValidationAttribute not firing IsValid function call in view model

I have created my own custom ValidationAttribute:
public class UrlValidationAttribute : ValidationAttribute
{
public UrlValidationAttribute() {}
public override bool IsValid(object value)
{
if (value == null)
return true;
var text = value as string;
Uri uri;
return (!string.IsNullOrWhiteSpace(text) &&
Uri.TryCreate(text, UriKind.Absolute, out uri));
}
}
I am using that on one of my models and it works perfectly. However, now I am attempting to use it on a view model:
public class DeviceAttribute
{
public DeviceAttribute(int id, attributeDefinition, String url)
{
ID = id;
Url = url;
}
public int ID { get; set; }
[UrlValidation]
public String Url { get; set; }
}
The view model is used in the partial view like this:
#model List<ICMDB.Models.DeviceAttribute>
<table class="editor-table">
#foreach (var attribute in Model)
{
<tr>
#Html.HiddenFor(a => attribute.ID)
<td class="editor-label">
#Html.LabelFor(a => attribute.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(a => attribute.Url)
#Html.ValidationMessageFor(a => attribute.Url)
</td>
</tr>
}
</table>
For some unknown reason, while the constructor for UrlValidationAttribute fires, the IsValid function doesn't fire. Any ideas?
Edit: On further investigation, it seems this is happening because the DeviceAttribute view model is actually the view model for a partial. The full page is passed a different view model that contains the list of DeviceAttribute view models. So when my controller action is called, the full page view model is constructed and its values filled, but no DeviceAttribute view models are constructed, hence why no validation is run.
I would recommend you using editor templates instead of writing foreach loops. I suppose that your main view model looks something like this:
public class MyViewModel
{
public List<DeviceAttribute> Devices { get; set; }
...
}
Now in your main view:
#model MyViewModel
#using (Html.BeginForm())
{
<table class="editor-table">
#Html.EditorFor(x => x.Devices)
</table>
<input type="submit" value="OK" />
}
and in the corresponding editor template (~/Views/Shared/EditorTemplates/DeviceAttribute.cshtml):
#model DeviceAttribute
<tr>
#Html.HiddenFor(x => x.ID)
<td class="editor-label">
#Html.LabelFor(x => x.Url)
</td>
<td class="editor-field">
#Html.TextBoxFor(x => x.Url)
#Html.ValidationMessageFor(x => x.Url)
</td>
</tr>
And your POST action takes the view model back:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
...
}
Now the default model binder will successfully bind all values in the view model and kick validation.
Here's a nice blog post about templates.

Categories

Resources