MVC LINQ query to populate model with a one-to-many and many-to-one tables - linq

I am stuck trying to get my model populated given the domain table structure.
I have a main table that stores Vendors and each Vendor can select one-to-many categories from a master category lookup table. The vendor selections are stored in another table that only stores the VendorID and CategoryID to link between them.
I can write the query (below) to include the category table but then I can only display the categoryID and not the category names.
public VendorProfile GetVendor(String id)
{
Guid pid = Guid.Parse(id);
var view = _db.VendorProfiles.Include("VendorCategories").Single(v => v.ProfileID == pid);
return view;
}
I attempted to include the lookup table in the query (below) but this is generating a runtime error.
public VendorProfile GetVendor(String id)
{
Guid pid = Guid.Parse(id);
var view = _db.VendorProfiles.Include("VendorCategories").Include("ProductServiceCategory").Single(v => v.ProfileID == pid);
return view;
}
A specified Include path is not valid. The EntityType 'VendorProfilesIntranet.VendorProfile' does not declare a navigation property with the name 'ProductServiceCategory'.
The Category table does have the navigation property. I don't know how to add this same navigation property to the main table since it does not have any FK to the lookup table.
UPDATE:
#Gert This notation does work!
_db.VendorProfiles.Include("VendorCategories.ProductServiceCategory").Single(v => v.ProfileID == pid);
However, what I get displayed now is only the category items that were selected. I wish to get the entire list of catgories and have the ones checked that were selected. I am using a scrolling CheckboxList.
<div class="scroll_checkboxes">
<ul>
#foreach (var c in Model.VendorCategories)
{
<li>
<input type="checkbox" name="categories" value="#c.ID" /> #c.ProductServiceCategory.CategoryName
</li>
}
</ul>
#Html.ValidationMessage("Please check at least one Product/Service category")
</div>
UPDATE2:
There might be a better solution but for anyone stuck with similar situation, this worked
<div class="scroll_checkboxes">
<ul>
#foreach (var c in ViewBag.Categories)
{
<li>
#foreach(var vc in Model.VendorCategories)
{
if(c.Id == vc.CategoryID)
{
<input type="checkbox" name="categories" checked="checked" value="#c.Id" /> #vc.ProductServiceCategory.CategoryName
<br />
}
else
{
<input type="checkbox" name="categories" value="#c.Id" /> #vc.ProductServiceCategory.CategoryName
<br />
}
}
</li>
}
</ul>
#Html.ValidationMessage("Please check at least one Product/Service category")
</div>

You can do
_db.VendorProfiles.Include("VendorCategories.ProductServiceCategory")
to include both the VendorCategories and their ProductServiceCategorys in the result set.
By the way, if you use DbExtensions.Include you have intellisense to help you find the right include paths.

Related

Partial view in NET 6

i am lost with NET6.
I created this partial view _MenuNav.cshtml :
#model IEnumerable<CateringMilano.Models.Menu>
#foreach (var item in Model)
{
<div>
<a alt="#item.MenuTitle)">#item.MenuName</a>
<b>#item.MenuTitle</b>
</div>
}
and the cod in my controller is :
// GET: /Menus/_MenuNav/
public ActionResult _MenuNav(string menuPosition)
{
// Get my db
var model = _context.Menu.Include(m => m.ChildMenuSub) as IQueryable<Menu>;
model = model.Where(p => p.MenuPosition == menuPosition);
// Send your collection of Mreations to the View
return PartialView(model);
}
and in the last project with net 4 i use to write the following code in the principal view in order to call my partial view :
#Html.Action("_MenuNav", "Menus", new { menuPosition = "Menu" })
but it looks like it does not work anymore this with NET6
Do you know how to rewrite my code in order to get the same result?
you must be mixing view components with partial views. Partial view were always used like this
<div id="partialName">
<partial name="_PartialName" />
</div>
with model
<partial name="_PartialName" model="Model.MyInfo" />
or for async
<div id="partialName">
#await Html.PartialAsync("_PartialName")
</div>
and the most popular way to update parital view is using ajax
And you can check my answer about viewcomponents here
https://stackoverflow.com/a/69698058/11392290

ASP.Net Core 2.0 MVC dynamic model updating

I'm trying to build a dynamic view, where I can pass a list of properties that need to be filled in by the user.
The collection of properties is dynamic, so I can't build the view that displays specific properties.
I've been able to display the property names and their initial values, and the user can change the values on the screen, but those updated values don't make it to the controller action that would update the model.
I've tried using a dynamic model, as well as a list of key/value pairs.
I'm thinking that it has something to do with the over-posting protection. Since the properties are dynamic, I can't list them in the Bind attribute in the update action.
Here's the controller action methods:
public IActionResult Test()
{
dynamic testObj = new ExpandoObject();
testObj.IntProperty = 100;
testObj.StringProperty = "A String Value";
return View(testObj);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Test(ExpandoObject model)
{
return Ok();
}
Here's the view:
#model dynamic
#{
ViewData["Title"] = "Test";
}
<form asp-action="Test" method="post">
<div class="form-horizontal">
#foreach (var propertyName in ((System.Collections.Generic.IDictionary<string, object>)Model).Keys)
{
<div class="form-group">
<label class="col-md-2 control-label">#propertyName</label>
<div class="col-md-10">
#Html.TextBox(propertyName, ((System.Collections.Generic.IDictionary<string, object>)Model)[propertyName])
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
This is a new application, and I'm doing code-first - so the model can be changed somewhat. All I really need to do is to be able to have different properties that can be updated.
Thanks in advance.
I recommend that you do not rely on the IModelBinder for this purpose at all and why I recommend this is because the form data that is passed between the controller and view is dynamic in terms of structure. A better, yet more troublesome, solution would be to get the form data directly out of the HttpContext.Request.Form. This type has an indexer which allows you to access the posted values by their names. For example
var name = HttpContext.Request.Form["name"].FirstOrDefault();
The .FirstOrDefault() (or SingleOrDefault(), this throws an exception when finding more than a value that meets a condition in a collection) is called assuming that there would be a single value (or a single value that meets a condition) for the "Name" input. However, when you have an array of those, you can get the values either in a foreach loop, or using linq methods, or directly by an index. For example:
var name = HttpContext.Request.Form["name"].FirstOrDefault(x=> x.ToString().StartsWith("x"));
var name = HttpContext.Request.Form["name"][0];

MVC4 Razor syntax one-to-many nested lists

I'd be very grateful if someone could point out what I'm doing wrong. I'm using MVC 4, database first with an edmx file, two simple tables trying to list a set of child results under a parent result.
the controller code, simplified:
var query = db.Childtablemodel.Include(c => c.ParentTablemodel);
return View(query.ToList());
The view, which is IEunumerable:
enter code here#{
foreach (var itm in Model)
{
<div>
#itm.ParentTable.DisplayName
<ul>
<li>
<span> #itm.City </span> <span> #itm.State</span>
</li>
</ul>
</div>
}
}
the results repeat the parent name for each child
Company 1
Houston TX
Company 1
Austin TX
Where what I want is
Company 1
Houston TX
Austin TX
I am following the Contoso University tutorial and so chose the child model for my controller (after a discouraging attempt to create these groupings in a parent-model- based controller). The edmx model does show the proper navigation properties and they are present in the generated class files. But when I look at a trace of my query, I see that it is using an inner join, which explains my results -- but utterly baffles me!
You are going to want to use linq's groupby to do that.
#{
foreach (var parent in Model.GroupBy(m => m.ParentTable.DisplayName))
{
<div>
#parent.Key
#foreach( var itm in parent )
{
<ul>
<li>
<span> #itm.City </span> <span> #itm.State</span>
</li>
</ul>
}
</div>
}
}

Custom display template behavior on page refresh

I am having an issue with my custom template when page is refreshed. My scenario is that I have multiple address records to be displayed, in which I opted to use custom templates so I won't have to use foreach() to display each record. Here's my custom template code:
#model Address
<div>
#Html.LabelFor(model => model.AddressLine)
#Html.DisplayFor(model => model.AddressLine)
<br />
</div>
Let's say I have 2 address records for display. What's happening is that upon initial page load, it will display 2 records correctly but when page is refreshed, it will display the 2 records again in which now I end up with 4 records on screen. Subsequent page refreshes will display even more duplicates on the screen. My question is, do I need to handle that page refresh event? Or am I just doing something very obviously wrong here?
UPDATE
As requested here is my code for the View and the Action,
View:
#model Patient
<div id="editDetails">
#using(Html.BeginForm("Edit", "Patient"))
{
#Html.DisplayFor(m => m.Addresses)
<input type="submit" value="Submit" />
}
</div>
Action:
public ActionResult Details(string id)
{
Patient patient = patientRepository.GetPatientData(id);
if (patient != null)
{
patientData.GetOtherDetails(patient);
return View(patient);
}
else
return View();
}

Nested edit templates in mvc razor

I have seen many versions of this question but the answers always turn into "you don't need to do that" and never an answer.
I have a list of attributes about a product that I want to show in an unordered list with checkboxes to select particular attributes.
In My Model:
public List<ProductAttribute> ProductAttributes {get;set;}
in my Create.cshtml:
<div Class="ProductAttributes">
#Html.EditorFor(m => m.ProductAttributes, "ProductAttributeSelectorList")
</div>
In my ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
And finally, in my EditLocationAttributeList.cshtml
#model Models.DisplayLocationAttribute
#Html.HiddenFor(m => m.Id)
#Html.CheckBoxFor(m => m.IsSelected)
<a href="#" alt="#Model.Description" >#Model.Name</a>
This all displays on the page perfectly I can style it like I want with CSS, but when the submit returns, my model.ProductAttributes collection is null.
I know I can bind directly to the EditLocationAttributeList and it will display and return a populated model.ProductAttributes if I use this:
#Html.EditorFor(m => m.ProductAttributes, "EditLocationAttributeList")
but now I do not have the unordered list that I would like to have. I could treat the template like an Item Template and have the line item tags embeded in that template but that seems smelly to have a template that is tightly coupled to another template.
Any Ideas?
Thanks in advance,
Tal
model.ProductAttributes is null, because the DefaultModelBinder is not able to reference each DisplayLocationAttribute back to the ProductAttribute property of your model. The simplest solution is to name your list elements as an array, so that for example each IsSelected element is named in the style ProductAttributes[n].IsSelected.
Add the following to ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
#{
var i = 0;
}
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "ProductAttributes[" +
i.ToString() + "]";
i++;
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
#{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
This will give you an indexed array, which the DefaultModelBinder will be able to associate to ProductAttributes. However, it builds a hard dependency to the name ProductAttributes. You can get around the hard dependency by several methods, such as passing the property name in the ViewBag.

Resources