ASP.NET MVC Core Cascading DropDownList - c#

I'm having trouble finding a tutorial / video that shows how to implement Cascading DropDownList from a Database using EntityFramework. I'm using ASP.NET MVC Core, EntityFramework Core with C#.
As of now, I'm able to retrieve the data from my database to my 3 DropDownList fine.
What I would like to be able to accomplish is to have the user select a State first which would then display all Cities related to that State. Then after user has selected a City it would display the Zip Code(s) related to the City.
Any help would be greatly appreciated.
Models
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int StateId { get; set; }
public int CityId { get; set; }
public int ZipId { get; set; }
public State State { get; set; }
public City City { get; set; }
public Zip Zip { get; set; }
}
public class State
{
public int StateId { get; set; }
public string Abbr { get; set; }
public List<Customer> Customers { get; set; }
}
public class City
{
public int CityId { get; set; }
public string Name { get; set; }
public int StateId { get; set; }
public State State { get; set; }
public List<Customer> Customers { get; set; }
}
public class Zip
{
public int ZipId { get; set; }
public string PostalCode { get; set; }
public int CityId { get; set; }
public City City { get; set; }
public List<Customer> Customers { get; set; }
}
ViewModels
public class CustomerFormVM
{
public int CustomerId { get; set; }
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required(ErrorMessage = "Select State")]
[Display(Name = "State")]
public int StateId { get; set; }
//public IEnumerable<State> States { get; set; }
public IEnumerable<SelectListItem> States { get; set; }
[Required(ErrorMessage = "Select City")]
[Display(Name = "City")]
public int CityId { get; set; }
//public IEnumerable<City> Citys { get; set; }
public IEnumerable<SelectListItem> Citys { get; set; }
[Required(ErrorMessage = "Select Zip")]
[Display(Name = "Zip")]
public int ZipId { get; set; }
//public IEnumerable<Zip> Zips { get; set; }
public IEnumerable<SelectListItem> Zips { get; set; }
}
CustomerController
public class CustomerController : Controller
{
private MultiDbContext db;
public CustomerController(MultiDbContext context)
{
db = context;
}
// GET: /<controller>/
public IActionResult Index()
{
return View(db.Customers.ToList());
}
public IActionResult getCititesFromDatabaseByStateId(int id)
{
return View(db.Citys.Where(c => c.StateId == id).ToList());
}
public IActionResult getCities(int id)
{
var cities = new List<City>();
cities = getCititesFromDatabaseByStateId(id); //call repository
return Json(cities);
}
public ActionResult Create()
{
var states = db.States.ToList();
var citys = db.Citys.ToList();
var zips = db.Zips.ToList();
var viewModel = new CustomerFormVM
{
States = states,
Citys = citys,
Zips = zips
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CustomerFormVM vm)
{
if (ModelState.IsValid)
{
var customer = new Customer();
{
customer.FirstName = vm.FirstName;
customer.LastName = vm.LastName;
customer.StateId = vm.StateId;
customer.CityId = vm.CityId;
customer.ZipId = vm.ZipId;
}
db.Customers.Add(customer);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
vm.States = db.States.ToList();
vm.Citys = db.Citys.ToList();
vm.Zips = db.Zips.ToList();
return View(vm);
}
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var customervm = new CustomerFormVM();
{
Customer customer = db.Customers.SingleOrDefault(c => c.CustomerId == id);
if (customer == null)
{
return NotFound();
}
customervm.CustomerId = customer.CustomerId;
customervm.FirstName = customer.FirstName;
customervm.LastName = customer.LastName;
// Retrieve list of States
var states = db.States.ToList();
customervm.States = states;
// Retrieve list of Citys
var citys = db.Citys.ToList();
customervm.Citys = citys;
// Retrieve list of Citys
var zips = db.Zips.ToList();
customervm.Zips = zips;
// Set the selected state
customervm.StateId = customer.StateId;
// Set the selected city
customervm.CityId = customer.CityId;
// Set the selected zip
customervm.ZipId = customer.ZipId;
}
return View(customervm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(CustomerFormVM vmEdit)
{
if (ModelState.IsValid)
{
Customer customer = db.Customers.SingleOrDefault(c => c.CustomerId == vmEdit.CustomerId);
if (customer == null)
{
return NotFound();
}
customer.FirstName = vmEdit.FirstName;
customer.LastName = vmEdit.LastName;
customer.StateId = vmEdit.StateId;
customer.CityId = vmEdit.CityId;
customer.ZipId = vmEdit.ZipId;
db.Entry(customer).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vmEdit);
}
}
Create View
<div class="form-group">
#Html.LabelFor(c => c.FirstName)
#Html.TextBoxFor(c => c.FirstName, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(c => c.LastName)
#Html.TextBoxFor(c => c.LastName, new { #class = "form-control" })
</div>
<div class="form-group">
#*#Html.LabelFor(s => s.StateId)
#Html.DropDownListFor(s => s.StateId, new SelectList(Model.States, "StateId", "Abbr"), "", new { #class = "form-control" })
#Html.ValidationMessageFor(s => s.StateId)*#
<label asp-for="StateId "></label>
<select asp-for="StateId " asp-items="Model.States" class="form-control" id="state-target"></select>
<span asp-validation-for="StateId " class="text-danger"></span>
</div>
<div class="form-group">
#*#Html.LabelFor(ct => ct.CityId)
#Html.DropDownListFor(ct => ct.CityId, new SelectList(Model.Citys, "CityId", "Name"), "", new { #class = "form-control" })
#Html.ValidationMessageFor(ct => ct.CityId)*#
<label asp-for="CityId"></label>
<select asp-for="CityId" asp-items="Model.Citys" class="form-control" id="city-target"></select>
<span asp-validation-for="CityId" class="text-danger"></span>
</div>
<div class="form-group">
#Html.LabelFor(z => z.ZipId)
#Html.DropDownListFor(z => z.ZipId, new SelectList(Model.Zips, "ZipId", "PostalCode"), "", new { #class = "form-control" })
#Html.ValidationMessageFor(z => z.ZipId)
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
}
#section scripts {
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/lib/js/example.js"></script>,
}

I had a similar situation but in my example I have a Root folder and depending on which root folder I am using the next drop down list would display the corresponding sub-folders.
Not sure if there is a purly asp.net solution but, I used Jquery/Ajax for this.
Your code should look something like this:
html list:
<label asp-for="StateId "></label>
<select asp-for="StateId " asp-items="Model.States" class="form-control" id="state-target"></select>
<span asp-validation-for="StateId " class="text-danger"></span>
<label asp-for="CityId"></label>
<select asp-for="CityId" asp-items="Model.Citys" class="form-control" id="city-target"></select>
<span asp-validation-for="CityId" class="text-danger"></span>
Jquery code, you write this in .js file and then add it to a specific view with this statement<script src="~/js/example.js"></script>, Don't forget you need to add a jquery library to your project before any other javascript, and your example.js will contain:
$(document).ready(function () {
$("#state-target").on("change", function () {
$list = $("#city-target");
$.ajax({
url: "/getCities",
type: "GET",
data: { id: $("#state-target").val() }, //id of the state which is used to extract cities
traditional: true,
success: function (result) {
$list.empty();
$.each(result, function (i, item) {
$list.append('<option value="' + item["CityId"] + '"> ' + item["Name"] + ' </option>');
});
},
error: function () {
alert("Something went wrong call the police");
}
});
});
});
The Ajax request will call this action in the Controller which will retrieve a list of cities from the database (using something like return dbContext.CityTable.Where(c => c.StateId == id).ToList() inside a getCititesFromDatabaseByStateId(id) method) and then return the Json object, the success function will create a list of options and apply it:
public IActionResult getCities(int id)
{
var cities = new List<City>();
cities = getCititesFromDatabaseByStateId(id); //call repository
return Json(citites);
}
In your ViewModel consider changing IEnumerable<State/City/Zip> (IEnumerable<T>) to IEnumerable<SelectListItem>. I can say as well your Model's are messy (but if you can get data the from the database focus on getting the list working 1st), consider improving them later.
Fix for 2 errors mentioned in the comments:
public List<City> getCititesFromDatabaseByStateId(int id)
{
return db.Citys.Where(c => c.StateId == id).ToList();
}
public ActionResult Create()
{
var states = new SelectList(db.States.ToList(), "StateId", "Abbr");
var citys = new SelectList(db.Citys.ToList(), "CityId", "Name");
var zips = new SelectList(db.Zips.ToList(), "ZipId", "Code");
var viewModel = new CustomerFormVM
{
States = states,
Citys = citys,
Zips = zips
};
return View(viewModel);
}

Related

How to display data retrieved from ASP.NET API into angular V13

I'm trying to get every motorcycle content from the web api and display them into my angular project.
ASP.NET Framework Web API 4.7
Angular CLI: 13.3.7
Angular: 13.3.11
Web API side:
Controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class HomeController : ApiController
{
private NavEcommerceDBfirstEntities db = new NavEcommerceDBfirstEntities();
public HomeModel Get()
{
var streetBikesContents = db.Motorcycles.Where(m => m.Category.MotoCategory == "Street").Select(m => new MotorcycleDTO
{
ModelDto = m.Model,
PriceDto = m.Price,
BrandDto = m.Brand.Name,
CategoryDto = m.Category.MotoCategory,
DealersDto = m.Dealers.Select(d => d.Name).ToList()
});
var sportBikesContents = db.Motorcycles.Where(m => m.Category.MotoCategory == "Sport").Select(m => new MotorcycleDTO
{
ModelDto = m.Model,
PriceDto = m.Price,
BrandDto = m.Brand.Name,
CategoryDto = m.Category.MotoCategory,
DealersDto = m.Dealers.Select(d => d.Name).ToList()
});
var adventureBikesContents = db.Motorcycles.Where(m => m.Category.MotoCategory == "Adventure").Select(m => new MotorcycleDTO
{
ModelDto = m.Model,
PriceDto = m.Price,
BrandDto = m.Brand.Name,
CategoryDto = m.Category.MotoCategory,
DealersDto = m.Dealers.Select(d => d.Name).ToList()
});
var scooterBikesContents = db.Motorcycles.Where(m => m.Category.MotoCategory == "Scooter").Select(m => new MotorcycleDTO
{
ModelDto = m.Model,
PriceDto = m.Price,
BrandDto = m.Brand.Name,
CategoryDto = m.Category.MotoCategory,
DealersDto = m.Dealers.Select(d => d.Name).ToList()
});
var homeModel = new HomeModel
{
StreetBikesContents = streetBikesContents,
SportBikesContents = sportBikesContents,
AdventureBikesContents = adventureBikesContents,
ScooterBikesContents = scooterBikesContents
};
return homeModel;
}
}
}
}
Models:
HomeModel class:
public class HomeModel
{
public IEnumerable<MotorcycleDTO> StreetBikesContents { get; set; }
public IEnumerable<MotorcycleDTO> SportBikesContents { get; set; }
public IEnumerable<MotorcycleDTO> AdventureBikesContents { get; set; }
public IEnumerable<MotorcycleDTO> ScooterBikesContents { get; set; }
}
Motorcycle class:
//Database First Approach and Created by ADO.NET
public partial class Motorcycle
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Motorcycle()
{
this.Carts = new HashSet<Cart>();
this.OrderDetails = new HashSet<OrderDetail>();
this.Dealers = new HashSet<Dealer>();
}
public int MotorcycleId { get; set; }
public string Model { get; set; }
public double Price { get; set; }
public Nullable<int> BrandId { get; set; }
public byte[] Image { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Brand Brand { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Cart> Carts { get; set; }
public virtual Category Category { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Dealer> Dealers { get; set; }
}
DTO class:
public class MotorcycleDTO
{
public string ModelDto { get; set; }
public double PriceDto { get; set; }
public string BrandDto { get; set; }
public string CategoryDto { get; set; }
public IEnumerable<string> DealersDto { get; set; }
}
Angular Side:
Model:
home-categorised-bikes.model.ts:
export interface FromDTOContents{
ModelDto: string;
PriceDto: string;
BrandDto: string;
CategoryDto: string;
DealersDto: string[];
}
export interface HomeModel{
sportBikesContents: FromDTOContents[];
streetBikesContents: FromDTOContents[];
adventureBikesContents: FromDTOContents[];
scooterBikesContents: FromDTOContents[];
}
Service:
home-categorised-bikes.service.ts:
#Injectable({
providedIn: 'root'
})
export class HomeCategorisedBikesService {
Url = 'https://localhost:44377/api/Home';
constructor(private http: HttpClient) { }
get(): Observable<HomeModel> {
return this.http.get<HomeModel>(this.Url);
}
}
app.component.ts:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'Home Page';
constructor(private homeCategorisedBikesService: HomeCategorisedBikesService){}
ngOnInit(): void {
this.getAllBikesContents();
}
getAllBikesContents(){
this.homeCategorisedBikesService.get().subscribe(
Response => {
this.onHomeBikesContentsResponse(Response);
}
)
}
public sportBikesContentsvar: string[] = [];
public streetBikesContentsvar: string[] = [];
public adventureBikesContentsvar: string[] = [];
public scooterBikesContentsvar: string[] = [];
onHomeBikesContentsResponse(Response: HomeModel): void {
Response.sportBikesContents.forEach((content: FromDTOContents) => {
this.sportBikesContentsvar.push(`${content.BrandDto, content.CategoryDto, content.ModelDto, content.PriceDto, content.DealersDto}`);
});
Response.sportBikesContents.forEach((content: FromDTOContents) => {
this.streetBikesContentsvar.push(`${content.BrandDto, content.CategoryDto, content.ModelDto, content.PriceDto, content.DealersDto}`);
});
Response.sportBikesContents.forEach((content: FromDTOContents) => {
this.adventureBikesContentsvar.push(`${content.BrandDto, content.CategoryDto, content.ModelDto, content.PriceDto, content.DealersDto}`);
});
Response.sportBikesContents.forEach((content: FromDTOContents) => {
this.scooterBikesContentsvar.push(`${content.BrandDto, content.CategoryDto, content.ModelDto, content.PriceDto, content.DealersDto}`);
});
}
}
app.component.html:
<div class="container">
<h3 class="textCenter">Soprt Bikes</h3>
<div class="column" *ngFor="let c of sportBikesContentsvar">
<h3>{{c}}</h3>
</div>
<div class="devideElement">
<h3 class="textCenter">Street Bikes</h3>
<div class="column" *ngFor="let c of streetBikesContentsvar">
<h3>{{c}}</h3>
</div>
</div>
<div class="devideElement">
<h3 class="textCenter">Adventure Bikes</h3>
<div class="column" *ngFor="let c of adventureBikesContentsvar">
<h3>{{c}}</h3>
</div>
</div>
<div class="devideElement">
<h3 class="textCenter">Scooter Bikes</h3>
<div class="column" *ngFor="let c of scooterBikesContentsvar">
<h3>{{c}}</h3>
</div>
</div>
</div>
Question:
I want to display the c.model, c.brand, c.category, c.price, c.dealers which is an array of dealers for each motorcycle individually.
Please let me know if anything is unclear in the code or the question.
Thank you in advance.
The way you are declaring your sportBikesContentsvar and others are going to give you issue. It's better to define your object type FromDTOContents[].
This way you will have access to all the object properties.
I'm posting one example, rest would be same
public sportBikesContentsvar: FromDTOContents[] = [];
onHomeBikesContentsResponse(Response: HomeModel): void {
this.sportBikesContentsvar = Response.sportBikesContents;
}
Then in HTML
<div class="container">
<h3 class="textCenter">Sport Bikes</h3>
<div class="column" *ngFor="let c of sportBikesContentsvar">
<h3>{{c.ModelDto}}</h3>
<h3>{{c.PriceDto}}</h3>
<h3>{{c.BrandDto}}</h3>
<h3>{{c.CategoryDto}}</h3>
<ng-container *ngFor="let dealers of c.DealersDto">
<h3>{{dealers}}</h3>
</ng-container>
</div>
</div>

Model comes null to controller

This is my first ask. I have 2 models for 1 view. I built the code but i have a problem. data comes null from view to controller.
Models:
Mom model:
public class BildirimOlusturViewModel
{
public BildirimOlusturModel bildirimOlusturModel { get; set; }
public TagBoxViewModel tagBoxViewModel { get; set; }
}
Child models:
public class BildirimOlusturModel
{
[Required(ErrorMessage = "Lütfen bildirim tipi seçiniz")]
public string BildirimTipi { get; set; }
[Required(ErrorMessage = "Lütfen alıcı tipi seçiniz")]
public string AliciTipi { get; set; }
[Required(ErrorMessage = "Lütfen alıcı seçiniz")]
public string Alicilar { get; set; }
[Required(ErrorMessage = "Lütfen bir başlık giriniz")]
public string Baslik { get; set; }
[Required(ErrorMessage = "Mesaj boş olamaz")]
public string Mesaj { get; set; }
}
public class TagBoxViewModel
{
public List<string> Items { get; set; }
}
View:
#model xyz.Web.Notifications.Models.BildirimOlusturViewModel
<form method="post" asp-controller="Bildirim" asp-action="BildirimOlustur">
...
#(Html.DevExtreme().SelectBoxFor(s => s.bildirimOlusturModel.AliciTipi)
.Placeholder("Alıcı Tipi...")
.DataSource(new List<SelectListItem> {
new SelectListItem
{
Text = "Personel",
Value = "personel".ToString()
},
new SelectListItem
{
Text = "Müşteri",
Value = "musteri".ToString()
}})
.ValueExpr("Value").DisplayExpr("Text")
.OnValueChanged("alicitipi_changed")
.ID("slcAliciTipi")
)
</div>
<div class="col-md-8">
#(Html.DevExtreme().TagBoxFor(x => x.bildirimOlusturModel.Alicilar)
.Items(Model.tagBoxViewModel.Items)
.SearchEnabled(true)
.Placeholder("Alıcı...")
.ID("TagBoxAlici")
)
#(Html.DevExtreme().TextBoxFor(x => x.bildirimOlusturModel.Baslik)
.Placeholder("Başlık...")
)
<input type="text" id="Mesaj" asp-for="bildirimOlusturModel.Mesaj" name="bildirimOlusturModel.Mesaj" id="bildirimOlusturModel.Mesaj"/>
#(Html.DevExtreme().Button()
.Text("Submit")
.Type(ButtonType.Default)
.StylingMode(ButtonStylingMode.Contained)
.Width(120)
.UseSubmitBehavior(true)
)
</form>
Controller:
[HttpPost]
public IActionResult BildirimOlustur(BildirimOlusturModel model)
{
string sAlicilar = model.Alicilar;
string sAliciTipi = model.AliciTipi;
string sBaslik = model.Baslik;
string sBildirimTipi = model.BildirimTipi;
string sMesaj = model.Mesaj;
}
Submit button sends me inside the post method but not sends the model. My variables coming null. Thank you for help.
Try adding a [FromBody] attribute before your argument.
public IActionResult BildirimOlustur([Frombody] BildirimOlusturModel model)
I solved the problem. controller was waiting for the wrong parameter.
[HttpPost]
public IActionResult BildirimOlustur(BildirimOlusturViewModel model)
{
BildirimOlusturModel mdl = new BildirimOlusturModel();
mdl = model.bildirimOlusturModel;
string sAlicilar = mdl.Alicilar;
}

In a HTML helper dropdownlist, how to get foreign key data?

I am new to ASP.NET. My form look like this
This code display role in Form
#Html.DropDownList("id", (IEnumerable<SelectListItem>)ViewBag.lis, null, new { #class = "form-control" })
in Controller
public ActionResult register()
{ //
ViewBag.lis = new SelectList(new dbdemoEntities().Roles, "id", "name");
return View();
}
ROLE CLASS
public partial class Role
{
public int Id { get; set; }
public string name { get; set; }
public virtual Register Register { get; set; }
}
Register class
public partial class Register
{
public int Id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string password { get; set; }
public Nullable<int> phone_no { get; set; }
public virtual Role Role { get; set; }
}
The problem is that I can get all data except for Role. The role is null. How do I get the Role ID?
[HttpPost]
public ActionResult register(Register obj)
{
using(var db = new dbdemoEntities())
{
var data = new Register()
{
email = obj.email,
name = obj.name,
password = obj.password,
phone_no = obj.phone_no,
Role = obj.Role
};
db.Registers.Add(data);
db.SaveChanges();
ViewBag.register = "Your account has been registered!";
}
return PartialView();
}
I think the problem is that I should write model => model.role like the example of the name here.
#Html.EditorFor(model => model.name, new { htmlAttributes = new { #class = "form-control" } })
here is what I updated now
ViewBag.lis = new SelectList(new dbdemoEntities().Roles, "Id", "name");
In HTML
Problem after update:
After changing
Role = db.Roles.Single(r => r.Id == obj.Role.Id)
Here is another error
Try this:
Change: ViewBag.lis = new SelectList(new dbdemoEntities().Roles, "id", "name");
to
ViewBag.lis = new SelectList(new dbdemoEntities().Roles, "Id", "name");
and then:
#Html.DropDownList(model => model.Role.Id, (IEnumerable<SelectListItem>)ViewBag.lis, null, new { #class = "form-control" })
and also make a constructor for Register class:
public partial class Register
{
public Register()
{
this.Role = new Role();
}
public int Id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string password { get; set; }
public Nullable<int> phone_no { get; set; }
public virtual Role Role { get; set; }
}
====== Update =======
Change the action like this:
[HttpPost]
public ActionResult register(Register obj)
{
using(var db = new dbdemoEntities())
{
var data = new Register()
{
email = obj.email,
name = obj.name,
password = obj.password,
phone_no = obj.phone_no,
Role = db.Roles.Single(r=> r.Id == obj.Role.Id)
};
db.Registers.Add(data);
db.SaveChanges();
ViewBag.register = "Your account has been registered!";
}
return PartialView();
}
Change to this this if you want to post Register.Id property:
#Html.DropDownListFor(model => model.Id,(SelectList) ViewBag.list,new { #class="form-control"})

Adding a TextBox dynamically based on bool

Case is an MVC 5 application
I have a class Team (sports team), that looks like this:
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public string Badge { get; set; }
public TeamColor? TeamColor { get; set; }
public League? League { get; set; }
public bool HasAlias { get; set; }
public bool IsAlias { get; set; }
public int? AliasId { get; set; }
public virtual Team AliasTeam { get; set; }
public virtual ICollection<Team> Aliases { get; set; }
}
An AliasTeam is a Team with just a TeamName an AliasId.
How do I go about adding a textbox for filing out the AliasTeamName if the user has clicked the HasAlias checkbox on the original team, and how do I loop through the textboxes in the controller in order to add them as Teams with an AliasId of the original team?
Controller action looks like this right now, where I can add a Team
public ActionResult CreateTeam(CreateTeamViewModel model)
{
if (ModelState.IsValid)
{
var team = new Team
{
TeamName = model.TeamName,
TeamColor = model.TeamColor,
League = model.League
};
db.Teams.Add(team);
db.SaveChanges();
return RedirectToAction("AddTeamImage", new {teamid = team.Id});
}
return View(model);
}
EDIT:
Solved it by adding following js:
$(document).ready(function() {
$("#HasAlias").change(function () {
if (this.checked) {
$(".textboxes").append('<div><input class="form-control text-box single-line" placeholder="enter aliasname" type="text" name="teams"/> <span class=" glyphicon glyphicon-plus-sign"></span> <span class=" glyphicon glyphicon-minus-sign"></span></div>');
} else {
$(".textboxes").empty();
}
});
$('.textboxes').on("click", ".add_field", function(e) {
e.preventDefault();
$(".textboxes").append('<div><input class="form-control text-box single-line" placeholder="enter aliasname" type="text" name="teams"/> <span class=" glyphicon glyphicon-plus-sign"></span> <span class=" glyphicon glyphicon-minus-sign"></span></div>');
});
$('.textboxes').on("click", ".remove_field", function (e) {
e.preventDefault(); $(this).parent('div').remove();
});
});
And then looping through array of teams in Action:
public ActionResult CreateTeam(CreateTeamViewModel model, string[] teams)
{
if (ModelState.IsValid)
{
var team = new Team
{
TeamName = model.TeamName,
TeamColor = model.TeamColor,
League = model.League,
HasAlias = model.HasAlias
};
db.Teams.Add(team);
db.SaveChanges();
foreach (var s in teams)
{
var t = new Team
{
TeamName = s,
AliasId = team.Id,
IsAlias = true
};
db.Teams.Add(t);
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(model);
}
I may have misunderstood the whole ViewModel paradigm then. I thought you where meant to create ViewModels, with only the information needed to create a result.

How to count nested collection from database using Entity Framework

I'm making my first Asp.net Mvc application - Forum System.
I'm trying to display how many posts and threads there are in sub category.
This are my tables:
public class Category
{
private ICollection<SubCategory> subCategories;
public Category()
{
this.subCategories = new HashSet<SubCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubCategory> SubCategories
{
get { return this.subCategories; }
set { this.subCategories = value; }
}
}
public class SubCategory
{
private ICollection<Thread> threads;
public SubCategory()
{
this.threads = new HashSet<Thread>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Thread> Threads
{
get { return this.threads; }
set { this.threads = value; }
}
}
public class Thread
{
private ICollection<Post> posts;
public Thread()
{
this.posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual SubCategory SubCategory { get; set; }
public int SubCategoryId { get; set; }
public virtual ICollection<Post> Posts
{
get { return this.posts; }
set { this.posts = value; }
}
public string AuthorId { get; set; }
public virtual ApplicationUser Author { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; }
public int ThreadId { get; set; }
public virtual Thread Thread { get; set; }
public string AuthorId { get; set; }
public virtual ApplicationUser Author { get; set; }
}
this is my view:
#model IEnumerable<ForumSystem.Web.ViewModels.Home.IndexCategoryViewModel>
#{
ViewBag.Title = "Home Page";
}
<div class="container">
#foreach (var category in Model)
{
<div class="row">
<h5>#category.Name</h5>
#foreach (var subCat in category.SubCategories)
{
<div class="col-md-10">
<div class="row">
<h7>
#subCat.Title
</h7>
</div>
<div class="row">
<p>#subCat.Description</p>
</div>
</div>
<div class="col-md-2">
<p>#category.ThreadsCount threads</p>
<p>#category.PostsCount posts</p>
<div class="row">
</div>
</div>
}
</div>
}
</div>
#category.ThreadsCount and PostsCount are not working.
I can get easy threads count in view with #subCategory.Threads.Count, but I can't get posts count.
In controller I have tried many things.
The code in the moment is :
public ActionResult Index()
{
var threadScount = this.Data
.SubCategories
.All()
.SelectMany(x => x.Threads)
.Count();
var postsCount = this.Data
.Threads
.All()
.SelectMany(x => x.Posts)
.Count();
var model = this.Data
.Categories
.All()
.Select(c => new IndexCategoryViewModel
{
Name = x.Name,
SubCategories = c.SubCategories,
ThreadsCount = threadScount,
PostsCount = postsCount,
})
.ToList();
return this.View(model);
}
But this gives me all threads and posts count, not specific for each subCategory.
Thanks in advance.
Create view models that represent what you want to display
public class SubCategoryVM
{
public string Title { get; set; }
public string Description { get; set; }
public int ThreadsCount { get; set; }
public int PostCount { get; set; }
}
public class CategoryVM
{
public string Name { get; set; }
public List<SubCategoryVM> SubCategories { get; set; }
}
Controller
public ActionResult Index()
{
var model = this.Data.Categories.Select(c => new CategoryVM
{
Name = c.Name,
SubCategories = c.SubCategories.Select(s => new SubCategoryVM
{
Title = s.Title,
Description = s.Description,
ThreadsCount = s.Threads.Count,
PostsCount = s.Threads.SelectMany(t => t.Posts).Count;
})
});
return View(model);
}
View
#model IEnumerable<CategoryVM>
#{
ViewBag.Title = "Home Page";
}
<div class="container">
#foreach (var category in Model)
{
<div class="row">
<h5>#category.Name</h5>
#foreach (var subCategory in category.SubCategories)
{
<div class="col-md-10">
<div class="row">
<h7>
// The following line in your view makes no sense
// #subCat.Title
// Assuming you have method: public ActionResult Details(string title) in SubCategoryController, then
#Html.ActionLink(subCategory.Title, "Details", "SubCategory", new { title = subCategory.Title }, null)
</h7>
</div>
<div class="row">
<p>#subCategory.Description</p>
</div>
</div>
<div class="col-md-2">
<p><span>#subCategory.ThreadCount</span><span>threads</span></p>
<p><span>#subCategory.PostCount</span><span>posts</span></p>
</div>
}
</div>
}
</div>
I believe what you're looking for is this:
var model = this.Data
.Categories
.Select(c => new IndexCategoryViewModel
{
Name = c.Name,
SubCategories = c.SubCategories,
ThreadsCount = c.Threads.Count(),
PostsCount = c.Threads.Sum(t => t.Posts.Count()),
})
.ToList();
Here you create nested queries to count threads and posts for each category. You don't need the threadsCount and postsCount since they store overall counts and not per-category counts.
NOTE
I assume that Threads property exists on category class and Posts property exists on thread class. Otherwise you'll need to have predicates to associate posts with threads and threads with categories. Most commonly they would be ids, in which case the code would look similar to this:
var model = this.Data
.Categories
.Select(c => new IndexCategoryViewModel
{
Name = c.Name,
SubCategories = c.SubCategories,
ThreadsCount = this.Data
.Threads
.Count(t => t.CategoryId == c.Id),
PostsCount = this.Data
.Posts
.Count(p => this.Data
.Threads
.Any(t => p.ThreadId == t.Id && t.CategoryId == c.Id)
),
})
.ToList();
Notice that I also skipped all .All() calls since they seem redundant, although I'm not sure what they do so you can throw it back in there if necessary.
Try this, also you will need to change the ViewModel to reflect requirements
public ActionResult Index()
{
var model = from subCategory in this.Data.SubCategories
select new SubCategoryViewModel
{
Name = subCategory.Category.Name,
SubCategory = subCategory,
ThreadsCount = subCategory.Threads.Count(),
PostsCount = subCategory.Threads.SelectMany(c => c.Posts).Count(),
})
.ToList();
return this.View(model);
}

Categories