In my web app I have a self referencing many to many relationship of plants. The M2M relationships are good neighbours and bad neighbours. I want to be able to have a form where a user can check off the both types of neighbours and then save the form.
What I have so far:
For brevity, I will only show code to Good neighbours relation, the bad neighbours is the same.
Models
public class Plant
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<GoodPlants> GoodNeighbours { get; set; }
}
public class GoodPlants
{
public int PlantId { get; set; }
public int GoodNeighbourId { get; set; }
public virtual Plant Plant { get; set; }
public virtual Plant GoodNeighbour {get; set;}
}
My viewmodel EditViewModel:
public class EditViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PlantIsSelectedViewModel> AllPlants { get; set; }
}
And the model PlantIsSelected
public class PlantIsSelectedViewModel
{
public int id { get; set; }
public string name { get; set; }
public bool isGoodNeighbour { get; set; }
public bool isBadNeighbour { get; set; }
}
I have an Edit method to display the edited plant and all the other plants:
var plant = await _context.Plants.FindAsync(id);
ICollection<Plant> plants = _context.Plants.ToList();
ICollection<GoodPlants> goodNeighbours = await _context.GoodNeighbours
.Where(g => g.PlantId == id)
.Include(g => g.GoodNeighbour)
.ToListAsync();
GoodPlants ownGoodPlant = goodNeighbours.FirstOrDefault(i => i.GoodNeighbour.Id == plant.Id);
Plant ownPlant = plants.FirstOrDefault(i => i.Id == plant.Id);
goodNeighbours.Remove(ownGoodPlant);
plants.Remove(ownPlant);
//populate the viewmodel
EditViewModel plantModel = new EditViewModel();
plantModel.Id = plant.Id;
plantModel.Name = plant.Name;
plantModel.AllPlants = _mapper.Map<ICollection<PlantIsSelectedViewModel>>(Plants);
foreach (var element in plantModel.AllPlants)
{
if (goodNeighbours.Any(g => g.GoodNeighbour.Id == element.id))
{
element.isGoodNeighbour = true;
}
else if (badNeighbours.Any(g => g.BadNeighbour.Id == element.id))
{
element.isBadNeighbour = true;
}
}
This desperately needs refactoring, but thats not the main issue here.
In my view I contrast the elements of AllPlants collection if it appears on either of the neighbours collections and have the checkbox set to checked or not:
<tbody>
#foreach (var item in Model.AllPlants)
{
<tr>
#if (item.isGoodNeighbour)
{
<td>
#Html.Label(item.name)
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id"
#(Html.Raw("checked=\"checked\""))
/>
</td>
<td>
<input type="checkbox" name="#Model.AllPlants"
value="#item.id" />
</td>
}
// else statements below for badneighbours and cases without any relation.
// ...
</tr>
}
I want to know how I can keep track of all the selected items (and unselected), get them in my editViewModel and send them back to my [HttpPost] Edit method. My current [HttpPost] method receives the same viewmodel, but the AllPlants property is empty.
How do I receive the correct data back?
Thanks in advance, I'm pretty stuck!
Related
I have been using the QuickGrid build by the Blazor team (https://aspnet.github.io/quickgridsamples/). It is working, but I have many columns, so I am interested in figuring out whether I can loop over the <PropertyColumn>-components instead of hardcoding them.
My Component.razor component:
<div class="scrollable" >
<QuickGrid Items="MethodThatGetsIQueryableElements()">
#foreach (var ColumnDTO in ColumnDTOs) {
if (ColumnDTO.Show)
{
if (ColumnDTO.Name == ComponentEnums.Department)
{
<PropertyColumn Property="#(v => v.Department)" Sortable="true" />
}
if (ColumnDTO.Name == ComponentEnums.Name)
{
<PropertyColumn Property="#(v => v.Name)" Sortable="true" />
}
[... Numerous other columns in same format...]
}
}
</QuickGrid>
</div>
The initial conditional statement is connected with a checkbox, so basically checking/unchecking will show/hide the given column. My main difficulty is with the lambda expression for the Property-parameter. I can't seem to get it working with a foreach-loop.
The Items I am trying to loop through is a IQueryable<RowDTO> with the RowDTO just holding information on each row of data. The ColumnDTO holds the name of the column based on an enum-class (ComponentEnums) and the show/hide-boolean. Samples of the classes:
RowDTO.cs:
public class RowDTO
{
public RowDTO(string department, string name, [....] )
{
Department = department;
Name = name;
[....]
}
public string Department { get; set; }
public string Name { get; set; }
[....]
}
ColumnDTO.cs:
public class ColumnDTO
{
public ColumnDTO(ComponentEnums name, bool show)
{
Name = name;
Show = show;
}
public ComponentEnums Name { get; set; }
public bool Show { get; set; } = true;
}
I have a View model that looks like this
public class ItemViewModel
{
[Required]
public int Id { get; set; }
public string ItemId { get; set; }
public string ItemName { get; set; }
public string MFGNumber { get; set; }
public IList<ItemPartViewModel> Parts { get; set; }
public IList<ItemComponentViewModel> Components{ get; set; }
public IList<ComponentPartViewModel> ComponentParts { get; set; }
public IList<ComponentSubCompViewModel> ComponentSubComps { get; set; }
public IList<SubCompPartViewModel> SubCompParts { get; set; }
public IList<SubCompSubCompViewModel> SubCompSubComps { get; set; }
public IList<SubCompSubCompPartViewModel> SubCompSubCompParts { get; set; }
}
As you can see the Viewmodel also has corresponding view models that look like this
public class ItemPartViewModel
{
[Required]
public int ID { get; set; }
public string PartID { get; set; }
public HtmlString PartLink { get; set; }
public string MFGNumber { get; set; }
public string PartName { get; set; }
public float QtyInItem { get; set; }
public float OnHand { get; set; }
public float OnWorkOrder { get; set; }
public float Committed { get; set; }
public float FSTK { get; set; }
// This is the additional property to contain what user picks
public PartActionType SelectedActionType { get; set; }
}
The ItemViewModel is populated through my OrderSelection GET method that looks like this
public ActionResult SpecialOrderSelection(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
JobOrder jobOrder = db.JobOrders.Find(id);
if (jobOrder == null)
{
return HttpNotFound();
}
ViewBag.JobOrderID = jobOrder.ID;
ItemInstance ii = db.ItemInstances.Where(x => x.serialNumber == jobOrder.serialNumber).FirstOrDefault();
Item item = db.Items.Find(ii.ItemID);
var vm = new ItemViewModel
{
Id = item.ID,
ItemId = item.ItemID,
ItemName = item.Name,
Parts = new List<ItemPartViewModel>(),
Components = new List<ItemComponentViewModel>(),
ComponentParts = new List<ComponentPartViewModel>(),
ComponentSubComps = new List<ComponentSubCompViewModel>(),
SubCompParts = new List<SubCompPartViewModel>(),
SubCompSubComps = new List<SubCompSubCompViewModel>(),
SubCompSubCompParts = new List<SubCompSubCompPartViewModel>()
};
foreach (ItemHasParts ihp in item.IHP)
{
Part part = db.Parts.Find(ihp.PartID);
vm.Parts.Add(new ItemPartViewModel
{
ID = part.ID,
PartID = part.PartID,
PartLink = part.PartIDLink,
MFGNumber = part.MFG_number,
QtyInItem = ihp.qty,
OnHand = part.On_Hand,
OnWorkOrder = part.On_Order_Count(true, true),
Committed = part.CommittedCount(true, true),
FSTK = part.FSTK,
PartName = part.Name,
SelectedActionType = PartActionType.Transfer
});
}
return View(vm);
}
The data then is correctly shown on the selection page. But on this page users must select whether they want to harvest/transfer/or dispose of a part. So once the user has finished selecting their options they hit a 'submit' button. This then POSTS to this method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SpecialOrderSelection(ItemViewModel model)
{
//list of transfers
//list of harvests
//list of disposals
if (ModelState.IsValid)
{
JobOrder jobOrder = db.JobOrders.Find(model.Id);
if (jobOrder == null)
{
return HttpNotFound();
}
ViewBag.JobOrderID = jobOrder.ID;
// do whatever with 'model' and return or redirect to a View
}
//ViewBag.submitted = true;
return RedirectToAction("SpecialOrderSummary", new { ID = jobOrder.ID });
}
The problem here is that for each list, (Parts/Components/ComponentParts/etc.) The ID is null. Why is it null on the POST but not the GET? And how can I fix this so it is not null
Here is the beginning of my View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id)
<div class="form-horizontal">
<h2 class="noprint">Special Order Selection</h2>
<p style="color:red" class="noprint">Please select what is to be done with each component/part</p>
<td align="left">
<hr class="noprint" />
<h4 class="noprint"><b>Work Order ID:</b> #Html.DisplayFor(model => j.ID)</h4>
<br class="noprint" />
And here is the payload of it
<form action="/JODetails/SpecialOrderSelection/3092" method="post"><input name="__RequestVerificationToken" type="hidden" value="LETTERSANDNUMBERS" /><input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="3092" /> <div class="form-horizontal">
<h2 class="noprint">Special Order Selection</h2>
<p style="color:red" class="noprint">Please select what is to be done with each component/part</p>
Here is an example of what a 'Part' in my 'parts' list is returning
Your hidden field in the form is Id, which is the Model.Id (ItemViewModel) and not the part! and yet the property you're inspecting on POST is ID (which is the Part ID). You're looking for the item part ID, but you only have the ItemViewModel Id in the form. Since you have other values for item part properties, you must be iterating over that list of parts somewhere in your form. Add a hidden input for the part ID there.
#{
foreach(var part in Model.Parts) {
#Html.HiddenFor(model => part.ID)
}
}
In my shopping cart i have passed data through Session.In here i face some problem there's 2 additional fields in Shopping cart.How do i add that one to my model.Because model not bind to this view.
Models
public class CartTotal
{
public decimal SubTotal { get; set; }
public decimal DeliveryCharges { get; set; }
//Some properties here
public List<Items> items { get; } = new List<Items>();
}
public class Items
{
public int ItemId { get; set; }
public string ItemCode { get; set; }
public string ItemName { get; set; }
public string PersonalizedName { get; set; }
public string Size { get; set; }
}
Before coming to the Cart i have added the items using ItemsHolder.
ItemHolder
public CartTotal ItemsHolder
{
get
{
object ItemsSession = Session["ItemSession"] as CartTotal;
if (ItemsSession == null)
{
ItemsSession = new CartTotal();
Session["ItemSession"] = ItemsSession;
}
return (CartTotal)ItemsSession;
}
}
I added items like below in item add section,
public ActionResult AddNewItems(int ItemId,string Dir)
{
ItemsHolder.items.Add(new Items() {
ItemId = items.ItemId,
ItemName = items.ItemName,
ItemPrice = items.ItemPrice,
ImageUrl = items.ImageUrl,
ItemCode = items.ItemCode
});
return RedirectToAction("Index", "Home");
}
NOW the problem is I have use that ItemSession in the Cart and in the Items dto has 2 properties called PersonalizedName & Size those fields filled it on the cart section.and i need to add those things to the ItemHolder.How do i do it?
my _cart Partial View
#{
var list = Session["ItemSession"] as XYZ.Domain.CartTotal;
}
#using (Html.BeginForm("CartFinalize", "Home"))
{
#foreach (var item in list.items)
{
// Some Codes here
<td class="cart-itm">
<input type="text" class="form-control personalizedName" placeholder="Personalized Name">
</td>
<td class="cart-itm">
<input type="text" class="form-control size" placeholder="Size">
</td>
}
}
I need to add those 2 items to the Specific item in the Cart.
I am new to ASP.NET MVC and trying to list some companies, then all contacts under that each company. I think I am getting close to make it work, so please help if you can.
The model for table and field name:
namespace ERP.Models
{
[Table("ERP_Company")]
public class ERP_Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
}
[Table("ERP_CompanyContact")]
public class ERP_Contact
{
[Key]
public int ContactID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyID { get; set; }
}
}
These are the methods getting the Company and Contact list:
namespace ERP.Models
{
public class Method1
{
public ERPEntities db = new ERPEntities();
public List<ERP_Company> getCompanyList()
{
List<ERP_Company> companyList = (
from c in db.ERP_Company
where c.Name.Contains("Network")
select c).Take(10).ToList();
return companyList;
}
// This below method needs to get the passing CompanyID from getCompanyList for filtering.
public List<ERP_Contact> getContactList()
{
List<ERP_Contact> contactList = (
from cc in db.ERP_CompanyContact
select cc).Take(50).ToList();
return contactList;
}
/* Tried this below, but not work for the Controller, maybe I am doing wrong.
public List<ERP_Contact> getContactList(int CompanyID)
{
List<ERP_Contact> contactList = (
from cc in db.ERP_CompanyContact
where cc.CompanyID == CompanyID
select cc).Take(50).ToList();
return contactList;
}
*/
}
}
Use ViewModel (suggested from other post), combine both models:
namespace ERP.Models
{
public class ViewModelDemoVM
{
public List<ERP_Company> allCompanies { get; set; }
public List<ERP_Contact> allContacts { get; set; }
}
}
This code in the Controller:
Method1 _repository = new Method1();
public ActionResult ViewModelDemo()
{
ViewModelDemoVM vm = new ViewModelDemoVM();
vm.allCompanies = _repository.getCompanyList();
vm.allContacts = _repository.getContactList();
return View(vm);
}
Lastly, the view code:
#model ERP.Models.ViewModelDemoVM
#{
ViewBag.Title = "ViewModelDemo";
}
<h2>ViewModelDemo</h2>
<ul>
#foreach (var company in Model.allCompanies)
{
<li>#company.CompanyID | #company.Name</li>
<ul>
<!-- HERE is I want to put the filtering... foreach contact WHERE CompanyID = Model.allCompanies.CompanyID-->
#foreach (var contact in Model.allContacts)
{
<li>#contact.ContactID | #contact.FirstName</li>
}
</ul>
}
</ul>
How can I filter the contacts (2nd loop) based on the #company.CompanyID? Sample code would be appreciated.
Thanks in advance.
you can apply where clause in second loop. try below code. i hope this helps
#model ERP.Models.ViewModelDemoVM
#{
ViewBag.Title = "ViewModelDemo";
}
<h2>ViewModelDemo</h2>
<ul>
#foreach (var company in Model.allCompanies)
{
<li>#company.CompanyID | #company.Name</li>
<ul>
<!-- HERE is I want to put the filtering... foreach contact WHERE CompanyID = Model.allCompanies.CompanyID-->
#foreach (var contact in Model.allContacts.Where(x=>x.CompanyId ==company.CompanyID)
{
<li>#contact.ContactID | #contact.FirstName</li>
}
</ul>
}
</ul>
You should create a view model with nested structure and use that. Remember, view models are specific to the view. So build it as your view needs it.
public class CompanyVm
{
public string Name { set; get; }
public IEnumerable<ContactVm> Contacts { set;get;}
}
public class ContactVm
{
public string Name { set; get; }
}
public class ViewModelDemoVM
{
public List<CompanyVm> Companies { set; get; }
}
Your Contact table/entity already has a foriegn key/navigational property to Company entity/table. So all you have to do is, get the companies and it's corresponding customers,map it to our view model and use it in the view.
Add a collection type to Company entity class to access it's contacts.
public class ERP_Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { set; get; }
}
Now in your action method, you can get the data
public IActionResult ViewModelDemo()
{
var vm = new ViewModelDemoVM();
vm.Companies = db.Companies
.Select(a => new CompanyVm { Name = a.Name,
Contacts = a.Contacts
.Select(c => new ContactVm
{ Name = c.Name })}
).ToList();
return View(vm);
}
Now in your view, just loop through the company and for each company, loop through it's contacts
#model ViewModelDemoVM
#foreach(var company in Model.Companies)
{
<h3>#company.Name</h3>
<h5>Contacts</h5>
#foreach(var contact in company.Contacts)
{
<p>#contact.Name</p>
}
}
Some notes
Create view models as needed by view
Do not mix entity classes (used by ORM) with view models
Keep less logic/C# code in views
I used class generic class names (Contact instead ERP_Contact) and property names. When you use the above code, make the needed changes to use your existing names if needed.
I have an MVC proyect using EF (database first) and I have already created CRUD for some entities.
Now I am trying to create a dashboard page that contains widgets or similar, each listing the last 10 entities from different db tables (last 10 products created, last 10 customers, etc)
To create the widget I have followed this tutorial https://www.codeproject.com/Articles/598383/Widgets-in-MVC
So I have 2 interfaces and 2 classes that implements those interfaces:
public interface IWidget
{
int SortOrder { get; set; }
string Entity { get; set; }
string CssClassName { get; set; }
string HeaderText { get; set; }
string FooterText { get; set; }
ISubWidget SubWidget { get; set; }
}
public interface ISubWidget
{
ICollection<Products> EntitiesList { get; set; }
}
public class Widget : IWidget
{
public int SortOrder { get; set; }
public string Entity { get; set; }
public string HeaderText { get; set; }
public string FooterText { get; set; }
public ISubWidget SubWidget { get; set; }
}
public class SubWidget : ISubWidget
{
public ICollection<Products> EntitiesList { get; set; }
}
then I have 3 partial views: one for the widget itself, one for the subwidget, and one that will act as a container of all created widgets.
#model Proyect.Comun.Widget.IWidget
<div class="widget">
<div class="#Model.CssClassName">
<div class="widgetHeader">
#Model.HeaderText
</div>
<div class="widgetBody">
#Html.Partial(string.Concat(new[] { "Widget", "/", "_SubWidget" }), #Model.SubWidget)
</div>
<div class="widgetFooter">
#Model.FooterText
</div>
</div>
</div>
#model Proyect.Comun.Widget.ISubWidget
<table>
#foreach (var item in Model.EntitiesList)
{
<tr>
<td> #Html.DisplayFor(modelItem => item.product_number) </td>
<td> #Html.DisplayFor(modelItem => item.product_name) </td>
<td> #Html.DisplayFor(modelItem => item.product_description) </td>
</tr>
}
</table>
#model Fruterias.Comun.Widget.IWidget
#foreach (Proyect.Comun.Widget.IWidget wm in ViewBag.Widgets)
{
#Html.Partial(string.Concat(new[] { "Widget", "/", "_Widget" }), wm)
}
then in the Dashboard controller:
public ActionResult Index()
{
ViewBag.Widgets = GetWidgetData();
return View();
}
public List<IWidget> GetWidgetData()
{
var lastWidget = new List<IWidget>{
new Widget()
{
SortOrder = 1,
CssClassName = "high",
Entity = "Products",
HeaderText = "Last Products",
FooterText = "",
SubWidget = new SubWidget {
EntitiesList = db.products.OrderByDescending(p => p.date).Take(10).ToList(),
}
},
};
return lastWidget;
}
And finally in the view for Dashboard/Index:
<p>
#{
#Html.Partial("Widget/_WidgetContainer");
}
</p>
This works ok and shows a list of Product entities. But now I want to create different widgets associated to different entities.
Of course the problem is that the property EntitiesList is declared as a collection of Product objects, so I can not fill it with, for example, customers objects.
What would be the solution here? I could create different interfaces (and classes) for each type of entity, but Im sure there must be a better way...
Also, as the entities models (Products, Customers...) are generated with Entity Framework db first, I guess I can not create an Interface and make them implement that interface (or can I?)... (anytime the db changes and models are regenerated by EF, all that would be lost?)
If you don't need to have the EntitiesList to be a specific type you could just have the SubWidget be generic and set its type when you instantiate.
public class SubWidget<T> : ISubWidget<T> where T : class
{
public ICollection<T> EntitiesList { get; set; }
}
or you could use dynamic object.
public class SubWidget : ISubWidget
{
public ICollection<dynamic> EntitiesList { get; set; }
}