ASP.MVC GroupBy - sql

I have to entites and i try group by them, but i cant get it to work.
When I run it I get the following error, I think it's because I try to parse GroupBy to a ToDictionary(), see the controller method. But in the view it uses IEnumerable. In some way I need to parse Dictionary to the view. But I don't know how.
Do you have some ideas how can I get this solved?
The model item passed into the dictionary is of type:
System.Collections.Generic.List1[System.Collections.Generic.KeyValuePair2[System.String,System.Int32]]
but this dictionary requires a model item of type:
System.Collections.Generic.IEnumerable1[aProejct.Models.Database.Properties]`.
Here I have my Controller method:
public ActionResult Test()
{
var group = db.Propertiess.AsNoTracking().GroupBy(x => x.PropertiesName).ToDictionary(g => g.Key, g => g.Count());
return View(group.ToList());
}
And my view
model IEnumerable<aProject.Models.Database.Properties>
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Test</title>
</head>
<body>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<ul>
#foreach (var group in Model.GroupBy(item => item.PropertiesName)) {
<li>
#Html.Encode(group.Key)
<ul>
#foreach (var item in group)
{
<li>
#Html.Encode(item.SubPropertiess)
</li>
}
</ul>
</li>
}
</ul>
</body>
</html>

In your controller you are doing .ToDictionary(g => g.Key, g => g.Count()); which returns a KeyValue dictionary where the Key is the PropertiesName and the Value is the number of entries matched for that group.
I don't really think this is what you want. You are not actually passing the items in these groups, to the view in any way.
g is actually an enumerable containing your Properties, so you would need to also make sure this is passed on to the view in order to iterate through them.
edit: From what it looks like in your code - you're not really using the Count anyway. Try passing your g (containing the items in the group) as the value instead of doing the Count(). Then you should be able to use something like List<string, IEnumerable<Property>> for your model in your view.
If you actually do need the Count later on, you can just do a Count on the value, in your view.
I would however suggest looking into creating a real viewmodel class for your view, where everything the view may need can be set by the controller, to keep your view as simple as possible.

Related

Laravel 8 - Show database results using for loop (not foreach)

Using Laravel 8: I need to display my data (images saved as image names in the db) in 8 columns on the blade page. So if I have a db row count of 18, I would equally distribute the 18 in 8 columns (+remainder if any but that is irrelevant to this q - so, 18 images/8 cols = 2 (+2 remainder)). I can do this using two for loops so:
#for ($col=1; $col<=8; $col++)
#for ($img=1; $img<=$image_percolumn; $img++)
<img src="images/{{$featuredbrands->brandLogo'}}">
#endfor
#endfor
$featuredbrands is being passed from the controller as an array:
class HomeController extends Controller{
public function index(){
$fbrands = brands::join('subscriptions', 'brands.id','=','subscriptions.brandID')
->where('subscriptions.packageID','=',2)
->get(['brands.id','brands.brandLogo','brands.logoLink']);
return view('home',[
'featuredbrands'=>$fbrands
]);
} // end function
} // end class
I am getting the following error for the <img src...> line in my blade page:
Property [brandLogo] does not exist on this collection instance.
To test if I'm passing the data correctly to the blade page, I have tried using foreach to display the images. With foreach, I get the output in one column (so I know that data passed from controller is not the issue).
What is the correct way to reference the image field using the for loop? If that indeed is the problem?
This seems to be much harder than it should be, I am sure I am missing something.
One option - convert your collection to an array, and simply reference elements by key. Something like this:
In your Controller method:
return view('home', [
'featuredbrands' => $fbrands->toArray()
]);
And in your view:
#for ($col = 0; $col <= 16; $col += 2)
<img src="images/{{ $featuredbrands[$col]->brandLogo }}">
<img src="images/{{ $featuredbrands[$col+1]->brandLogo }}">
#endfor
You could also try to do it more the Laravel way, by using the Collection chunk() method, though this also seems very clunky here. Maybe something like:
#foreach ($featuredbrands->chunk(8) as $row)
#foreach ($row->chunk(2) as $col)
#foreach ($col as $brand)
<img src="images/{{ $brand->brandLogo }}">
#endforeach
#endforeach
#endforeach

Razor Pages Tag Helper with Dynamic Parameters Assigning Current Url as Href

I'm running into trouble trying to set anchor tag helper parameters dynamically and looking for some help.
I have a nav that is a view component inside the shared _Layout.cshtml page that populates departments from a model.
#model List<DepartmentModel>
<ul class="nav">
#foreach (var d in Model)
{
<li>
<a asp-page="catalog/departments" asp-route-departmentName="#d.Name" asp-route-departmentId="#d.Id">#d.Name</a>
</li>
}
</ul>
Here is the InvokeAsync() from my View Component class
public async Task<IViewComponentResult> InvokeAsync()
{
var departments = _catalogService.GetNavDepartments();
return View(departments);
}
When I first launch the page, all the hrefs are populating correctly.
"catalog/departments/department-name-1/department-id-1"
"catalog/departments/department-name-2/department-id-2"
"catalog/departments/department-name-3/department-id-3"
If I click on one of the links, like the first link for example, I go to the proper department page "catalog/departments/department-name-1/department-id-1"
However, once I click that first link and navigate to the respective page, all the nav hrefs populate to the current url "catalog/departments/department-name-1/department-id-1" instead of the originally generated hrefs. This makes it so I can't navigate to another department.
Here is my route in the Startup.cs
services.AddRazorPages().AddRazorPagesOptions(options => {
options.Conventions.AddPageRoute("/Catalog/Departments", "{dName}/{dId}");
});
Based on the convention above, it eliminates the "catalog/department" piece of the url but I added it in this description for a sense of what I'm trying to accomplish. Even if I add this template to the page that populates the "catalog/departments" url, I get the same result.
#page "{dName}/{dId}"
Can anyone help me figure out what I am missing? Thanks in advance!
******** UPDATE ********
Currently, the only way I am able to get this to work is by adding the cache tag helper.
<cache>
<ul class="nav">
#foreach (var d in Model)
{
<li>
<a asp-page="catalog/departments" asp-route-departmentName="#d.Name" asp-route-departmentId="#d.Id">#d.Name</a>
</li>
}
</ul>
</cache>
This doesn't seem like a proper fix. Seems more of a hack then anything. Anybody have any ideas? Thanks!

Binding to a ExpandoObject in Blazor

I've build a class which has a dynamic list of properties (such as color, size, weight etc etc), the contents of this aren't known at compile time so I used a ExpandoObject
Where I'm struggling is to get Blazor to bind to the value properties of the object for example
#foreach (var option in (IDictionary<string, object>)selectedProduct.Properties)
{
<div class="col-sm">
<input type="text" placeholder="#option.Key" #bind="option.Value"/>
</div>
}
However when this is compiled the autogen code results in an error :- 'KeyValuePair<string, object>.Value' cannot be assigned to
Has anyone had any success binding to a ExpandoObject (and children properties?)
This is not directly related to the ExpandoObject.
When you iterate over a Dictionary you get back immutable KeyValuePair items.
You get an error because #bind="option.Value" compiles to code that wants to assign to option.Value.
And as tuples and anonymous types are all immutable I think you will have to write a <Label, Value> class for this.
Thanks to Henk's answer I was able to modify my code to support the ExpandoObject
#foreach (var option in (IDictionary<string, object>)selectedProduct.Properties)
{
<div class="col-sm">
<input type="text" placeholder="#option.Key" #onchange="#((ChangeEventArgs __e) => ((IDictionary<string, object>)selectedProduct.Properties)[option.Key] = (object)__e.Value.ToString())"/>
</div>
}
Its uglier to look at but does the job

Passing Model Through POST

I'm having some problems with one specific part of my ASP.NET site. This page is essentially where a user edits an invoice and then posts the changes.
At this point I'm doing a re-write of an existing page, which works. I cannot figure out what the difference is other than the other one is older MVC 3.
When I first go to the EditInvoice Action:
public ActionResult EditInvoice()
{
SalesDocument invoice = null;
try
{
invoice = SomeService.GetTheInvoice();
}
catch (Exception ex)
{
return HandleControllerException(ex);
}
return View("InvoiceEdit", invoice.LineItems);
}
The model of the "InvoiceEdit" view is the List
Now the view loads fine, and displays all the document line items in a form:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MyApp.SalesDocumentLineItem>>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Line Items</legend>
<div id="lineItems" class="table">
<div class="row">
<div class="colHButton">X</div>
<div class="colH">Item</div>
<div class="colHPrice">Price</div>
<div class="colHQty">Qty</div>
</div>
<% foreach (var item in Model.Where(m => m.LineItemType.Id == NexTow.Client.Framework.UnitsService.LineItemTypes.SaleItem || m.LineItemType.Id == NexTow.Client.Framework.UnitsService.LineItemTypes.NonSaleItem))
{ %>
<div class="row">
<%=Html.Hidden("Model.LineItemNameId", item.LineItemNameId)%>
<div class="cellButton"><button onclick="DeleteContainer(event);">X</button></div>
<div class="cell"><span class="formData"><%=item.LineItemDescription %></span></div>
<div class="cellPrice">
<span class="formData">$</span>
<%= Html.TextBox("Model.Price", item.Price, new { style="width:75px;" })%>
</div>
<div class="cellQty">
<%= Html.TextBox("Model.Quantity", item.Quantity, new { style = "width:60px;" })%>
</div>
</div>
<%} %>
</div>
</fieldset>
<p>
<input type="submit" value="Update Invoice" onclick="SequenceFormElementsNames('salesItems');" />
</p>
<% } %>
</asp:Content>
This then provides the user the ability to edit the entries, and then the user clicks the "Update Invoice" submit button. This posts to the same view and action using a POST:
[HttpPost]
public ActionResult EditInvoice(List<SalesDocumentLineItem> salesItems)
{
if (salesItems == null || salesItems.Count == 0)
{
return View("ClientError", new ErrorModel() { Description = "Line items required." });
}
SalesDocument invoice = null;
try
{
invoice = SomeService.UpdateInvoice();
}
catch (Exception ex)
{
return HandleControllerException(ex);
}
InvoiceModel model = new InvoiceModel();
model.Document = invoice;
return View("InvoicePreview", model);
}
However, eventhough this worked in the old application. In the new one, this does not work. When we breakpoint at the final EditInvoice POST action method, the collection of salesItems is NULL. What is happening!?
When you use Html.TextBox(string, object), the first argument is used as the name of the form field. When you post this form back to the server, MVC looks at your action method's argument list and uses the names of the form fields to try and build those arguments. In your case, it tries to build a List<SalesDocumentLineItem>.
It looks like you're using "Model.Something" as the names of your form fields. This probably worked in the past, but I'm guessing something changed in MVC4 such that the framework doesn't know what you're talking about anymore. Fortunately, there's a better way.
Instead of setting the name of the form field using a string, use the strongly-typed HTML helpers like this:
<% for (var i = 0; i < Model.Count; i++) { %>
<div class="row">
<%= Html.HiddenFor(model => model[i].LineItemNameId) %>
<!-- etc... -->
</div>
<% } %>
These versions of the HTML helpers use a lambda expression to point to a property in your model and say "See that property? That one right there? Generate an appropriate form field name for it." (Bonus: Since the expression is pointing at the property anyway, you don't have to specify a value anymore -- MVC will just use the value of the property the expression represents)
Since lambda expressions are C# code, you will get a compile-time error if you make a typo, and tools like Visual Studio's "Rename" feature will work if you ever change the property's name.
(This answer goes into more detail on exactly how this works.)

JSTL based nested iteration

Assume you have the following code:
<c:if test="${fn:length(myIterator) > 0}">
<span>blabla</span>
<c:forEach var="myVar" items="${myIterator}" varStatus="status">
// some stuff
</c:forEach>
<br />
</c:if>
Note that myIterator is an Iterator (surprise, surprise *g).
The problem is that I do not end up in the each loop and do not see "// some stuff" to happen. I think the reason is that fn:length(myIterator) has already iterated over the Iterator to determine its size so that we have already reached the end when arriving at the forEach loop.
I have not seen any elegant way to reset the iterator manually or so.
Of course I could try using scriptlet code (something like <% myIterator.reset() %>, but I would like to avoid that.
I have also tried something like:
<c:if test="${not empty myIterator}">
But that did not work either (did not even enter the if-clause's body).
Any elegant suggestions?
Thanks.
Why are you referring to an Iterator. That should be a Collection like an ArrayList or something.
an iterator can only be iterated once
[EDIT]
IMHO, the cleanest approach is to change the Java controller to give you a Collection. However, based on your feedback that you dont want to or can't...
Change the Java code
Use Java scriptlets in your JSP
You can solve this with some Javascript/DHTML. Consider this JSP code
<script type="text/javascript">
var loopHasContent = false;
</script>
<div id="loop-content" style="display:none">
<span>blabla</span>
<c:forEach var="myVar" items="${myIterator}" varStatus="status">
<script type="text/javascript">
loopHasContent = true;
</script>
// some stuff
</c:forEach>
<br />
</div>
<script type="text/javascript">
if(loopHasContent) {
document.getElementById("loop-content").style.display = "block";
}
</script>
You only reference the Iterable once so your issue goes away. The div is not displayed by default. As long as your forEach loop executes at least once then the javascript variable loopHasContent is set to true, otherwise it remains false. The final javascript trailing the div will only show your loop-content if it has to.