ViewImports statements do not work sometimes - blazor-server-side

Original title to question:
Getting error: Cannot convert System.Collections.Generic.List to System.Collections.Generic.IEnumerable
I am following a Blazor example in Visual Studio 2022, .Net 6 from a Pro ASP.Net Core 6 book. I am getting this error in a Blazor Razor Component called Product.razor:
Cannot implicitly convert type 'System.Collections.Generic.List<SportsStore.Models.Product>' to 'System.Collections.Generic.IEnumerable<SportsStore.Pages.Admin.Product>'. An explicit conversion exists (are you missing a cast?)
Here is the code for Product.razor:
#page "/admin/products"
#page "/admin"
#inherits OwningComponentBase<IStoreRepository>
<table class="table table-sm d-sm-table table-striped table-bordered">
<thead>
<tr>
<th>ID</th><th>Name</th>
<th>Category</th><th>Price</th>
</tr>
</thead>
<tbody>
#if (ProductData?.Count() > 0) {
#foreach (Product p in ProductData){
<tr>
<td>#p.ProductID</td>
</tr>
}
};
</tbody>
</table>
#code{
public IStoreRepository Repository => Service;
public IEnumerable<Product> ProductData { get; set; }
= Enumerable.Empty<Product>();
protected async override Task OnInitializedAsync() {
await UpdateData();
}
public async Task UpdateData() {
ProductData = await Repository.Products.ToListAsync();
}
public string GetDetailsUrl(long id) => $"/admin/products/details/{id}";
public string GetEditUrl(long id) => $"/admin/products/edit/{id}";
}
The error shows up in the UpdateData method:
Here is what the IStoreRepository interface looks like:
namespace SportsStore.Models {
public interface IStoreRepository {
IQueryable<Product> Products { get; }
void SaveProduct(Product p);
void CreateProduct(Product p);
void DeleteProduct(Product p);
}
}
Here is the implimentation called EFStoreRepository in EntityFramework:
namespace SportsStore.Models {
public class EFStoreRepository : IStoreRepository {
private StoreDbContext context;
public EFStoreRepository(StoreDbContext ctx) {
context = ctx;
}
public IQueryable<Product> Products => context.Products;
public void CreateProduct(Product p)
{
context.Add(p);
context.SaveChanges();
}
public void DeleteProduct(Product p)
{
context.Remove(p);
context.SaveChanges();
}
public void SaveProduct(Product p)
{
context.SaveChanges();
}
}
}
This is the Product class:
namespace SportsStore.Models {
public class Product {
public long? ProductID { get; set; }
[Required(ErrorMessage = "Please enter a product name")]
public string Name { get; set; } = String.Empty;
[Required(ErrorMessage = "Please enter a product description")]
public string Description { get; set; } = String.Empty;
[Range(0.01, double.MaxValue,
ErrorMessage = "Please enter a positive price")]
[Column(TypeName = "decimal(8, 2)")]
public decimal Price { get; set; }
[Required(ErrorMessage = "Please specify a category")]
public string Category { get; set; } = String.Empty;
}
}
There is also a _ViewImports file:
#namespace SportsStore.Pages
#using Microsoft.AspNetCore.Mvc.RazorPages
#using SportsStore.Models
#using SportsStore.Infrastructure
#add TagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
I have pulled down the authors code and looked at it side by side. I can't see any difference.
I have no idea why I am getting this error.
Also I am not getting the correct properties for a Product since it cannot do the conversion correctly.
For instance, there should be ProductId, Name, Description, etc in the intellisence pop there.
Update
Well I found the problem but not the root cause.
It seems the using statement:
#using SportsStore.Models
is not being applied from the _ViewImports file.
This happened earlier in the book before and it really trips me up when it happens.
Has anyone else had this happen and been able to solve in their environment?
I had to fully qualify a Product in the statements to bring it into the Product.razor component. Here is the updated component (I have not finished the tbody yet):
#page "/admin/products"
#page "/admin"
#inherits OwningComponentBase<IStoreRepository>
<table class="table table-sm d-sm-table table-striped table-bordered">
<thead>
<tr>
<th>ID</th><th>Name</th>
<th>Category</th><th>Price</th>
</tr>
</thead>
<tbody>
#if (ProductData?.Count() > 0) {
#foreach (SportsStore.Models.Product p in ProductData){
<tr>
<td>#p.ProductID</td>
</tr>
}
};
</tbody>
</table>
#code{
public IStoreRepository Repository => Service;
public IEnumerable<SportsStore.Models.Product> ProductData { get; set; }
= Enumerable.Empty<SportsStore.Models.Product>();
protected async override Task OnInitializedAsync() {
await UpdateData();
}
public async Task UpdateData() {
ProductData = await Repository.Products.ToListAsync();
}
public string GetDetailsUrl(long id) => $"/admin/products/details/{id}";
public string GetEditUrl(long id) => $"/admin/products/edit/{id}";
}

Related

Asp Core Filter & Search Data Using Drop Downs & Search Bars

I would like users to search through pets using a search bar or drop down lists. I tried using viewbag and asp tag helpers but I keep getting errors. Below is a picture of what i'm going for. Any help is appreciated.
Model
public class Reptile
{
public int ReptileId { get; set; }
public string Name { get; set; }
public string Age { get; set; }
[Display(Name ="Reptile's Image")]
public byte[] Image { get; set; }
[Display(Name ="Food Requirements")]
public string FoodReq { get; set; }
[Display(Name="Habitat Requiremtns")]
public string HabitatReq { get; set; }
public string Gender { get; set; }
public string Type { get; set; }
public string Size { get; set; }
public string Color { get; set; }
[Display(Name="Recent Checkup")]
public bool RecentCheckup { get; set; }
public bool Trained { get; set; }
public bool Neutered { get; set; }
public bool Declawed { get; set; }
[Display(Name = "Good With Other Reptiles")]
public bool GoodWithRept { get; set; }
[Display(Name = "Good With Kids")]
public bool GoodWithKids { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int ApplicationUserId { get; set; }
}
Controller
public async Task<IActionResult> Index(string searchString)
{
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
return View(await reptiles.ToListAsync());
}
View
<form asp-controller="Reptiles" asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Search By Type: <input type="text" name="SearchString" />
<input type="submit" value="Filter" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
I've been trying to follow the docs here Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core. Not having any luck though.
Here is a simple demo to show how to use searchstring:
Controller:
public IActionResult Index(string searchString)
{
IEnumerable<Reptile> list = new List<Reptile> { new Reptile { Type = "t1", Name= "Reptile1" }, new Reptile { Type = "t2", Name = "Reptile2" }, new Reptile { Type = "t3", Name = "Reptile3" } };
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
list = list.Where(s => s.Name.Contains(searchString));
}
return View(list);
}
View:
Find by name:
|
Back to Full List
<table>
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Type)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<input type="text" asp-for="#item.Type">
</td>
</tr>
}
</tbody>
</table>
result:
Okay, I figured out how to use select to filter the reptile page by using the data users already added to the database from the properties in the model. I had to create a view model and add the Reptile model to it.
View Model
public class ReptileGenderViewModel
{
public Reptile Reptile { get; set; }
public List<Reptile> reptiles;
public SelectList genders;
public string reptileGender { get; set; }
}
Reptile Controller
public async Task<IActionResult> Index(string searchString, string reptileGender)
{
IQueryable<string> genderQuery = from g in _context.Reptiles
orderby g.Gender
select g.Gender;
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
if (!string.IsNullOrEmpty(reptileGender))
{
reptiles = reptiles.Where(g => g.Gender == reptileGender);
}
var reptileGenderVM = new ReptileGenderViewModel();
reptileGenderVM.genders = new SelectList(await genderQuery.Distinct().ToListAsync());
reptileGenderVM.reptiles = await reptiles.ToListAsync();
return View(reptileGenderVM);
}
View
<select asp-for="reptileGender" asp-items="Model.genders">
<option value="">All</option>
</select>

How to deal with this decimal error for a price?

I'm working on this app that should show on "localhost/catalog" some data. I have a library for the models and for the services that the application might use. I am getting this error:
InvalidOperationException: The property 'Price' is not a navigation
property of entity type 'StoreAsset'. The 'Include(string)' method can
only be used with a '.' separated list of navigation property names.Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.WalkNavigations(IEntityType entityType, IReadOnlyList<string> navigationPropertyPaths, IncludeLoadTree includeLoadTree, bool shouldThrow)
Here is the code that I'm using (controller, models and view) and the service methods on bottom:
public class CatalogController : Controller
{
private IStoreAsset _assets;
public CatalogController(IStoreAsset assets)
{
_assets = assets;
}
public ActionResult Index()
{
var assetModels = _assets.GetAll();
var listingResult = assetModels
.Select(result => new AssetIndexListingModel
{
Id = result.Id,
Tipology = _assets.GetTipology(result.Id),
Size = _assets.GetSize(result.Id),
Price = decimal.Parse(_assets.GetPrice(result.Id))
});
var model = new AssetIndexModel()
{
Assets = listingResult
};
return View(model);
}
public class AssetIndexListingModel
{
public int Id { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
public string Tipology { get; set; }
public string ImageUrl { get; set; }
}
public abstract class StoreAsset
{
public int Id { get; set; }
[Required]
public Status Status { get; set; }
[Required]
public decimal Price { get; set; }
public string ImageUrl { get; set; }
}
public class Dress : StoreAsset
{
[Required]
public string Color { get; set; }
[Required]
public string Tipology { get; set; }
[Required]
public string Size { get; set; }
}
#model Models.Catalog.AssetIndexModel
<div id="assets">
<h3></h3>
<div id="assetsTable">
<table class="table table-condensed" id="catalogIndexTable">
<thead>
<tr>
<th>Size</th>
<th>Price</th>
<th>Tipology</th>
</tr>
</thead>
<tbody>
#foreach (var asset in Model.Assets)
{
<tr class="assetRow">
<td class="">
<a asp-controller="Catalog" asp-action="Detail" asp-route-id="#asset.Id">
<img src="#asset.ImageUrl" class="imageCell" />
</a>
</td>
<td class="">#asset.Price</td>
<td class="">#asset.Size</td>
<td class="">#asset.Tipology</td>
</tr>
}
</tbody>
</table>
</div>
public class StoreAssetService : IStoreAsset
{
private Context _context;
public StoreAssetService(Context context)
{
_context = context;
}
public void Add(StoreAsset newAsset)
{
_context.Add(newAsset);
_context.SaveChanges();
}
public IEnumerable<StoreAsset> GetAll()
{
return _context.StoreAssets
.Include(asset => asset.Status)
.Include(asset => asset.Price);
}
public StoreAsset GetById(int id)
{
// Return a query (same as returning GetAll().FirstOrDefault(...))
return _context.StoreAssets
.Include(assets => assets.Status)
.Include(assets => assets.Price)
// So it can return null with no problem
.FirstOrDefault(asset => asset.Id == id);
}
public StoreBranch GetCurrentLocation(int id)
{
throw new NotImplementedException();
}
// To implement and test
public string GetPrice(int id)
{
return _context.Dresses.FirstOrDefault(p => p.Id == id).Price.ToString();
}
public string GetSize(int id)
{
return _context.Dresses.FirstOrDefault(s => s.Id == id).Size;
}
public string GetStatus(int id)
{
throw new NotImplementedException();
}
public string GetTipology(int id)
{
var dress = _context.StoreAssets.OfType<Dress>()
.Where(b => b.Id == id);
// For now return other if it's not a party dress
return dress.Any() ? "Party" : "Other";
}
}
Should I use some ForeignKey attribute or change Price to a string?
Any help would be great thanks
As pointed out in the error message, the Include is for the Navigation property only.
You need to change below:
return _context.StoreAssets
.Include(asset => asset.Status)
.Include(asset => asset.Price);
To:
return _context.StoreAssets
.Include(asset => asset.Status).ToList();
Reference: https://learn.microsoft.com/en-us/ef/core/modeling/relationships#definition-of-terms
https://learn.microsoft.com/en-us/ef/core/querying/related-data
I am having yet another problem. When I go to "localhost/catalog" the page should display all columns/entries that I have in the database but it only displays one column. Is there something wrong in the foreach cicle?

Two models in a view (with foreach)

I have two classes - Student.cs and Lecturer.cs placed under Models. Now that in my razor view I have to place two classes together.
I know there's a method Tuple to solve the problem. But I do not know what to do next. What should I do with my #foreach?
Here's my code in cshtml.
#model Tuple<MVCApp1.Models.Student, MVCApp1.Models.Lecturer>
#{
ViewBag.Title = "MainPage";
Layout = "~/Views/Shared/_Layout.cshtml";
}
I'm using a table, below is my #foreach code section.
#foreach (var j in Model)
{
<tr>
<td">#j.FirstName
</td>
<td>#j.MiddleName
</td>
<td>#j.LastName
</td>
I need to have 2 tables each with different attributes. First table from Student.cs and second table will be Lecturer.cs.
I know there is something wrong with the #foreach but I just can't find any solution online. Please help.
A tuple does not expose an iterator.
public class Tuple<T1> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple
What you are after is a ViewModel.
public class ViewModel
{
public List<Student> Students { get; set; }
public List<Teacher> Teachers { get; set; }
}
public ActionResult Index()
{
ViewModel model = new ViewModel();
// retreive from database
model.Students = new List<Student>() { new Student()};
model.Teachers = new List<Teacher>() { new Teacher()};
return View(model);
}
Then you can structure your table
<table>
<tr>
<th>First</th>
<th>Middle</th>
<th>Last</th>
</tr>
#foreach (var student in Model.Students)
{
<tr>
<td>#student.First</td>
<td>#student.Middle</td>
<td>#student.Last</td>
</tr>
}
#foreach (var teacher in Model.Teachers)
{
<tr>
<td>#teacher.First</td>
<td>#teacher.Middle</td>
<td>#teacher.Last</td>
</tr>
}
</table>
Once you are comfortable with this, you can explore inheritance and Entity Framework TPH Table per hierarchy.
You could end up with something like this:
public abstract class Person
{
public int Id { get; set; }
public string First { get; set; }
public string Middle { get; set; }
public string Last { get; set; }
}
public class Teacher : Person
{
public string Class { get; set; }
public DateTime HireDate { get; set; }
}
public class Student : Person
{
public int Grade { get; set; }
public DateTime EnrolledDate { get; set; }
}
public class ViewModel
{
public List<Student> StudentsOnly { get; set; }
public List<Person> StudentsAndTeachers { get; set; }
}
public ActionResult Index()
{
Context db = new Context();
ViewModel model = new ViewModel();
// You could collect just the students
model.StudentsOnly = db.People.OfType<Student>().ToList();
// Or all of them
model.StudentsAndTeachers = db.People.ToList();
return View(model);
}
Then you would only have to iterate through the single list of people, if you only needed to display their names.
<table>
...
#foreach (var person in Model.StudentsAndTeachers)
{
<tr>
<td>#person.First</td>
<td>#person.Middle</td>
<td>#person.Last</td>
</tr>
}
</table>

A referential integrity constraint violation On Update with DbContext

Please see below my Model class, my controller action and my view.
When I edit from my view I have got the exception :A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
I have already asked this question, but I have got no answers; pls help!!
public partial class Organization : ILockable, IAuditable, IEntity
{
/*** Construtor(s) ***/
public Organization()
{
}
public Organization(Party obj)
: this()
{
Party = obj;
}
/*** Public Members ***/
[Key, Display(Name = "Id")]
public int PartyId { get; set; }
/* IEntity */
public string Caption { get; set; }
public string NameInUse { get; set; }
public string Description { get; set; }
/* IAuditable */
[NotMapped, ScaffoldColumn(false)]
public System.DateTimeOffset Created
{
get { return Party.Created; }
set { Party.Created = value; }
}
[NotMapped, ScaffoldColumn(false)]
public string CreatedBy
{
get { return Party.CreatedBy; }
set { Party.CreatedBy = value; }
}
[NotMapped, ScaffoldColumn(false)]
public Nullable<System.DateTimeOffset> LastMod
{
get { return Party.LastMod; }
set { Party.LastMod = value; }
}
[NotMapped, ScaffoldColumn(false)]
public string LastModBy
{
get { return Party.LastModBy; }
set { Party.LastModBy = value; }
}
[NotMapped, Display(Name = "Del?")]
public bool IsSoftDeleted
{
get { return Party.IsSoftDeleted; }
set { Party.IsSoftDeleted = value; }
}
[NotMapped, ScaffoldColumn(false)]
public Nullable<System.DateTimeOffset> SoftDeleted
{
get { return Party.SoftDeleted; }
set { Party.SoftDeleted = value; }
}
[NotMapped, ScaffoldColumn(false)]
public string SoftDeletedBy
{
get { return Party.SoftDeletedBy; }
set { Party.SoftDeletedBy = value; }
}
/* ILockable */
public string GetTableName()
{
return "Organization";
}
public int GetLockId()
{
return this.PartyId;
}
/* Navigation Properties */
/// <summary>
/// Foreign key to Party: PartyId
/// Organization is subtype of Party
/// </summary>
public virtual Party Party { get; set; }
}
Controller Edit Action:
[HttpPost]
public ActionResult Edit(Organization obj)
{
//remove the lock since it is not required for inserts
if (ModelState.IsValid)
{
OrganizationRepo.Update(obj);
UnitOfWork.Save();
LockSvc.Unlock(obj);
return RedirectToAction("List");
}
else
{
return View();
}
}
View:
#using PartyBiz.Models.Objects
#using d2Utils.Reflection
#model IEnumerable<Organization>
#{
ViewBag.Title = "Details";
}
<table>
<tr>
<th>
#Html.Raw("Caption")
</th>
<th></th>
</tr>
<tr>
<td colspan="4">
#foreach (var item in Model)
{
<table>
<tr>
#using (Html.BeginForm("Edit", "Organization", FormMethod.Post))
{
<td >
#Html.TextBox("Caption", item.GetValForProp<string>("Caption"), new { #class = "txt" })
</td>
<td >
#Html.TextBox("NameInUse", item.GetValForProp<string>("NameInUse"), new { #class = "txt" })
</td>
<td >
#Html.TextBox("Description", item.GetValForProp<string>("Description"), new { #class = "txt" })
</td>
<td>
<input type="hidden" name="PartyId" value="#item.PartyId"/>
<button type="submit">Edit</button>
</td>
}
</tr>
</table>
}
</td>
</tr>
</table>
Context Method:
public virtual void Update(T obj)
{
IAuditable audit = obj as IAuditable;
IOverTime overtime = obj as IOverTime;
// Existing entity
D2Repository.Updated(ref audit, UserName);
D2Repository.FromDate(ref overtime);
Set.Attach(obj);
Ctxt.Entry(obj).State = EntityState.Modified;
}
I have added
obj.Party.PartyId = obj.PartyId;
in my edit action and it is working now.
I still want to know if this is the correct way of doing it?

how to bind data to checkbox from viewmodel

I'm trying to make a view model to show a list of checkboxes. A checkbox will be checked when its ID is found in the database. However, my code is generating an error.
CheckFacilityVN
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace XNet.WebUI.Hotel.ViewModel
{
public class CheckFacilityVM
{
public int FacilityID { get; set; }
public string facilityName { get; set; }
public List<FacilityAvailable> facilityAvailable { get; set; }
}
public class FacilityAvailable
{
public bool isCheck { get; set; }
}
}
My controller
public ActionResult Facility()
{
var htl = _hotelService.ShowRoomFacility(2);
var list = new List<FacilityAvailable>();
foreach (var x in htl)
{
list.Add(new FacilityAvailable { FacilityID = htl.FacilityID, facilityName = htl.FacilityName, isCheck = htl.IsActive });
}
return View();
}
My constructor
public Facility ShowRoomFacility(int HotelID)
{
var x = (from d in db.Facilities
where d.FacilityID == HotelID
select d).FirstOrDefault();
return x;
}
How can I make these checkboxes?
Start by adapting your view model:
public class CheckFacilityVM
{
public int FacilityID { get; set; }
public string FacilityName { get; set; }
public bool IsFacilityAvailable { get; set; }
}
and then use this view model:
public ActionResult Facility()
{
var model = _hotelService
.ShowRoomFacility(2)
.Select(htl => new CheckFacilityVM
{
FacilityID = html.FacilityID,
FacilityName = html.FacilityName,
IsFacilityAvailable = htl.IsActive,
})
.ToList();
return View(model);
}
and then write a corresponding view:
#model List<CheckFacilityVM>
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Is available</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].FacilityID)
#Html.HiddenFor(x => x[i].FacilityID)
</td>
<td>
#Html.DisplayFor(x => x[i].FacilityName)
#Html.HiddenFor(x => x[i].FacilityName)
</td>
<td>
#Html.CheckBoxFor(x => x[i].IsFacilityAvailable)
</td>
</tr>
}
</tbody>
</table>
<button type="submit">Save</button>
}
and finally:
[HttpPost]
public ActionResult Facility(List<CheckFacilityVM> model)
{
// process the values from the view model here ...
}