How to use IEnumerable in form razor page - c#

I have a form for create page in razor pages to fill organization details.
The organization has a field ParentId(it might belong to another organisation).
I want to iterate list of parentIds (and show Title in options) in Select.
I am getting this error: The following sample generates CS1579 because the MyCollection class doesn't contain the public GetEnumerator method:
//Model
public class Organisation : EntityBase
{
[Key]
public int Id { get; set; }
public Organisation Parent { get; set; }
[Required]
public string Title { get; set; }
}
//Controller
public IActionResult Create()
{
IEnumerable<Organisation> objList = _db.Organisations;
return View(objList);
}
//View
#model MindNavigatorDB.Entities.Organisation;
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Parent" class="control-label"></label>
<select id="country"
class="form-select form-control"
asp-for="Parent"
aria-label="Select">
#foreach (Organisation item in Model)
{
<option selected="selected" value="">Please select</option>
}
</select>
<span asp-validation-for="Parent" class="text-danger"></span>
</div>
</form>

you have to add ParentId to Orgainzation
public class Organisation : EntityBase
{
[Key]
public int Id { get; set; }
public int? ParentId { get; set; }
public virtual Organisation Parent { get; set; }
[Required]
public string Title { get; set; }
}
Create view model
public class OrganizationViewModel
{
public Organization {get; set;}
public List<SelectListItem> ParentSelectList {get; set;}
}
action
public IActionResult Create()
{
var viewModel= new OrganizationViewModel
{
Organization=new Organization(),
ParentSelectList = _db.Organisations.Select( i=> new SelectListItem
{
Value=i.Id.ToString(),
Text=i.Title
}).ToList()
}
return View(viewModel);
}
view
#model OrganisationViewModel;
<form asp-action="Create" method="post">
<div class="form-group">
<label class="control-label"> Parent </label>
<select class="form-control" asp-for="#Model.Organization.ParentId" asp-items="#Model.ParentSelectList" ></select>
<span asp-validation-for="#Model.Organization.ParentId" class="text-danger"></span>
</div>
</form>

Related

Foreign Key not showing in dropdown box | MVC

I'm somewhat new to MVC and have been following along with a tutorial but it has no answers regarding my question. For my Create page, the Foreign keys are not showing up. Basically, on the Projects page I created a project, on the People page I created a person. So when I try to create a ProjectRole on the ProjectRoles page, the ProjectId and PersonId are not showing up in the drop-down menu. Down below all of my code, I have provided a screenshot of what I have tried to put into words.
My models:
public class Project
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string Name { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime DueDate { get; set; }
public ICollection<ProjectRole> ProjectRoles { get; set; }
}
public class Person
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string MiddleName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public ICollection<ProjectRole> ProjectRoles { get; set; }
}
public class ProjectRole
{
public int Id { get; set; }
[Required]
public double HourlyRate { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
[ForeignKey("Project")]
public int ProjectId { get; set; }
[ForeignKey("AppRole")]
public int RoleId { get; set; }
}
My Controller code:
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,HourlyRate,PersonId,ProjectId,RoleId")] ProjectRole projectRole)
{
if (ModelState.IsValid)
{
_context.Add(projectRole);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(projectRole);
}
And my view code here:
#model Project2.Models.Entities.ProjectRole
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>ProjectRole</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="HourlyRate" class="control-label"></label>
<input asp-for="HourlyRate" class="form-control" />
<span asp-validation-for="HourlyRate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PersonId" class="control-label"></label>
<select asp-for="PersonId" class ="form-control" asp-items="ViewBag.PersonId"></select>
</div>
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<select asp-for="ProjectId" class ="form-control" asp-items="ViewBag.ProjectId"></select>
</div>
<div class="form-group">
<label asp-for="RoleId" class="control-label"></label>
<input asp-for="RoleId" class="form-control" />
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Screenshot of example of what I mean:
I suppose more personable to display the Person Name (and the Project Name) in the select controls, but to pass the PersonId (and ProjectId) to the Create method when click on the Create button.
Therefore, prepare the Person list (and the Project list) like below:
public IActionResult Create()
{
var persons = new List<SelectListItem>();
// Iterate through `Persons`
foreach(var p in _context.Persons)
{
persons.Add(new SelectListItem() { Value= p.Id, Text = p.FirstName+", "+p.LastName});
}
ViewBag.Persons = persons;
// Prepare the projects list (like `Persons` list above)
// ... your code here
ViewBag.Projects = persons;
return View(new ProjectRole(){ /* Add your code here to create the ProjectRole .../* });
}
And in the view:
<div class="form-group">
<label asp-for="PersonId" class="control-label"></label>
<select asp-for="PersonId" class="form-control" asp-items="ViewBag.Persons"></select>
</div>
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<select asp-for="ProjectId" class="form-control" asp-items="ViewBag.Projects"></select>
</div>
For additional information see The Select Tag Helper
*NOTE: And I would recommend to create compound view model to include all required information. *
ViewModels or ViewBag?
Understanding Best Way to Use Multiple Models in ASP.NET MVC

ASP.NET MVC EF Core, Property Access in View while on selected ID for different Model

I´m having an issue where I have three models as show below: Person, Competence with PersonCompetence between them. My current controller method gets an Id from previous page and shows that person with a list of this person's Competence and Level. In this View however I want to have a POST for new Competence. So at the same time you are adding a new one and you can see which ones you already have.
With the controller method I have now I can access the PersonCompetence and Competence when showing the list.
I dont have access to the Competence properties for asp-for="Competence" marked ###### in the View for AddComp.
I need the ID of person for POST to right person
I need the CompetenceType for POST to that property
I need PersonCompetence to show the list of current PersonCompetence.
I get that with the current #model CompetenceRadar.Models.Person I only reach Person properties.
I have looked at having a ViewModel with access to all tables with an IEnumerable for each table, but this breaks my current Controller when I search for the Id of the person showing. I have switched the #model in the View, but then I can't access Person ID/name.
So how do I access the Competence properties , list one person and get a list of PersonCompetences for that Person.
Please tell me if you want me to clarify something.
I don't need working code, just something to point me in the right direction for a solution.
Is it a ViewModel?
Can I POST without the asp-forattribute?
Models
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public ICollection<PersonCompetences> PersonCompetences { get; set; }
}
public class PersonCompetence
{
public int ID { get; set; }
public int PersonID { get; set; } // FK
public int CompetenceID { get; set; } // FK
public int Level { get; set; }
public Competece Competence { get; set; }
public Person Person { get; set; }
}
public class Competence
{
public int ID { get; set; }
public string CompetenceType { get; set; }
public string CompetenceCategory { get; set; }
public ICollection<PersonCompetence> PersonCompetences { get; set; }
}
AddComp Kontroller function
public async Task<IActionResult> AddComp(int? id)
{
var person = await _context.Personer
.Include(pk => pk.PersonCompetences)
.ThenInclude(k => k.Competence)
.FirstOrDefaultAsync(m => m.ID == id);
return View(person);
}
View_AddComp View for AddComp
#model CompetenceRadar.Models.Person
<h1>AddComp</h1>
<div class="row">
<form asp-action="AddComp">
<input type="hidden" asp-for="ID" />
<div class="form-group col-sm-4">
<label asp-for="#############" class="control-label col-sm-4"></label>
<input asp-for="#############" class="form-control col-sm-4" />
<span class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-dark" />
</div>
</form>
</div>
#foreach (var item in Model.PersonCompetences)
{
<div class="row py-2 rounded" style="background-color:lightslategray">
<div class="col-sm-3 pt-2">#item.Competence.CompetenceType</div>
<div class="col-sm-1 pt-2">#item.Niva</div>
<div class="col-sm-3 pt-2">#item.Competence.CompetenceCategory</div>
<div class="col-sm-5 d-flex justify-content-end">
<a class="btn btn-dark mr-1" role="button" asp-area="" asp-controller="Competence" asp-action="UpdateLevel" asp-route-id="#item.ID">Update</a>
<a class="btn btn-dark mr-1" role="button" asp-area="" asp-controller="Competence" asp-action="DeleteComp" asp-route-id="#item.CompetenceID">Remove</a>
</div>
</div>
}
Simple anwser is that I needed a ViewModel with all three Models
public class ExampleViewModel {
public Person person { get; set; }
public PersonCompetence personCompetence { get; set; }
public Competence competence { get; set; }}
This alows me to access the different values for ID, CompetenceType and a List for of the current PersonCompetence.
What is ViewModel in MVC?

How to bind view model for Razor Pages (.NET Core)?

Let's say I have this view model. Bear in mind, this is a view model. Not the domain/entity model.
public class Cart
{
public string Name { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
}
How do I scaffold to create CRUD Razor Page ?
Here is a demo ,you could refer to :
OrderItem Entity model and Cart View model, the View Model is related to the presentation layer of our application. They are defined based on how the data is presented to the user rather than how they are stored.
public class OrderItem
{
public int Id { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
}
public class Cart
{
public string Name { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
}
public class RazorPagesDbContext:DbContext
{
public RazorPagesDbContext(DbContextOptions<RazorPagesDbContext> options):base(options)
{ }
public DbSet<Product> Product { get; set; }
public DbSet<OrderItem> OrderItem { get; set; }
}
The CreateOrder Razor Page
#page
#model RazorPages2_2.Pages.Carts.CreateOrderModel
#{
ViewData["Title"] = "CreateOrder";
}
<h1>CreateOrder</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Cart.Name" class="control-label"></label>
<input asp-for="Cart.Name" class="form-control" />
<span asp-validation-for="Cart.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.Price" class="control-label"></label>
<input asp-for="Cart.Price" class="form-control" />
<span asp-validation-for="Cart.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.Qty" class="control-label"></label>
<input asp-for="Cart.Qty" class="form-control" />
<span asp-validation-for="Cart.Qty" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.TotalPrice" class="control-label"></label>
<input asp-for="Cart.TotalPrice" class="form-control" />
<span asp-validation-for="Cart.TotalPrice" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
The CreateOrder page model, the Cartproperty uses the [BindProperty] attribute to opt-in to model binding. When the Create form posts the form values, the ASP.NET Core runtime binds the posted values to the Cart model then put the values into the entity model.
public class CreateOrderModel : PageModel
{
private readonly RazorPagesDbContext _context;
public CreateOrderModel(RazorPagesDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
var product = _context.Product.FirstOrDefault();
Cart = new Cart
{
Name = product.ProductName,
Price = product.Price,
Qty = 2,
TotalPrice = product.Price * 2
};
return Page();
}
[BindProperty]
public Cart Cart { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var product = _context.Product.SingleOrDefault(p => p.ProductName == Cart.Name);
OrderItem orderItem = new OrderItem
{
Price = Cart.Price,
Qty = Cart.Qty,
TotalPrice = Cart.TotalPrice,
Product = product
};
_context.OrderItem.Add(orderItem);
await _context.SaveChangesAsync();
return RedirectToPage("../Index");
}
}
Result:
You could refer to the offocial doc about the Razor pages to create the page you want .
CODE BEHIND :
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _db;
public IndexModel(ApplicationDbContext db)
{
_db = db;
}
public IEnumerable<Cart> Carts { get; set; }
public async Task OnGet()
{
Books = await _db.Carts.ToListAsync();
}
}
You need :
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{
}
public DbSet<Cart> carts { get; set; }
}
for the View :
#model CardList.IndexModel

Build a checkbox list in razor page from many-to-many EF Core entity

My question is: How to build html markup in razor pages and the LINQ queries (in the backend) to bring a checkbox list of all my SubCategoies in the EDIT and CREATE views.
Allowing me to create a product with multiple subcategories and also updating them at any time in the EDIT view.
Using .Net EF Core 2.2, Razor Pages.
Main class (Product):
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public List<ProductSubcategory> SubCategories { get; set; }
}
Product has a many-to-many relationship with Subcategory:
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public List<ProductSubcategory> SubCategories { get; set; }
}
So the join table (entity) is ProductSubcategory:
public class ProductSubcategory
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SubCategoryId { get; set; }
public SubCategory SubCategory { get; set; }
}
The Edit (and create) Product view:
<h2>Editar: #Model.Product.Name</h2>
<form method="post">
<input type="hidden" asp-for="Product.Id" />
<div class="form-group">
<label asp-for="Product.Name"></label>
<input asp-for="Product.Name" class="form-control" />
<span class="text-danger" asp-validation-for="Product.Name"></span>
</div>
<div class="form-group">
<label asp-for="Product.Description"></label>
<textarea asp-for="Product.Description" class="form-control"></textarea>
<span class="text-danger" asp-validation-for="Product.Description"></span>
</div>
<div class="form-group">
<label asp-for="Product.Category"></label>
<select class="form-control" asp-for="Product.Category" asp-items="Model.Categories"></select>
<span class="text-danger" asp-validation-for="Product.Category"></span>
</div>
<div class="form-group">
//Code to allow the subcategory selection.
//preferable as checkboxes
//() subcat1 (x)subcat2 ()subcat3
//() subcat4 ()subcat5 (x)subcat6
</div>
<button type="submit" class="btn btn-primary">Salvar</button>
</form>
The Edit.cshtml.cs PageModel
public class EditModel : PageModel
{
private readonly IProductData _ProductData;
private readonly IHtmlHelper _HtmlHelper;
[BindProperty]
public Product Product { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
public string MessageCreate { get; set; }
public EditModel(IProductData _productData, IHtmlHelper _htmlHelper)
{
_ProductData = _productData;
_HtmlHelper = _htmlHelper;
}
public IActionResult OnGet(int? productId)
{
Categories = _HtmlHelper.GetEnumSelectList<Category>();
if (productId.HasValue)
{
Product = _ProductData.GetById(productId.Value);
}
else
{
MessageCreate = "Criar novo Produto";
Product = new Product();
}
if (Product == null)
{
return RedirectToPage("./NotFound");
}
return Page();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
Categories = _HtmlHelper.GetEnumSelectList<Category>();
return Page();
}
if (Product.Id > 0)
{
_ProductData.Update(Product);
}
else
{
_ProductData.Create(Product);
}
_ProductData.Commit();
TempData["Message"] = "Produto salvo!!!";
//PRG POST-REDIRECT-GET
return RedirectToPage("./Detail", new { productId = Product.Id });
}
}
The checkbox is used to represent a boolean property. I see you don't have a bool property so I suppose you need to add a Boolean property in SubCategories class like:
public bool IsChecked { get; set; } // added this property
Then you need to add a property to your PageModel(Edit or Create) to represent the data and ensured that posted values will be bound to it:
[BindProperty]
public List<Subcategory> SubCategories { get; set; } = new List<Subcategory>();
At the end all you need is to get the model binder to associate each checkbox with a specific Subcategory. The following code shows my example in .cshtml file:
#for (var i = 0; i < Model.SubCategories.Count(); i++)
{
<input asp-for="SubCategories[i].IsChecked" />
}

Asp.net MVC C# One Form, Two Model

I have two models like below.
public class Bill
{
public int Id { get; set; }
public string InvoiceNumber { get; set; }
public Int64 Amount { get; set; }
public int? NewPaymentId { get; set; }
public virtual NewPayment RelPayment { get; set; }
}
public class NewPayment
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LstName { get; set; }
public DateTime PaymentDate { get; set; }
public Int64 ProvisionNumber { get; set; }
public Int64 CreditCardNumber { get; set; }
public int ExpMonth { get; set; }
public int ExpYear { get; set; }
public int Cv2 { get; set; }
public Int64 Amount { get; set; }
public string UserId { get; set; }
public string CustomerNote { get; set; }
}
Customer is going to pay his invoices via credit card in my application.
I had one view which i posted the NewPayment model to the action. But now, i need to send also which invoices will be paid. So i need to create one more form for the Bill model i think ? But i cant figure out how can i pass two model to same action and i dont know the NewPaymentId before executing the payment method.
REGARDING TO THE COMMENTS :
My combine model as below :
public class Payment
{
public IEnumerable<Bill> Bill { get; set; }
public NewPayment NewPayment { get; set; }
}
And my view as below :
#model IEnumerable<ModulericaV1.Models.Bill>
<form class="form-no-horizontal-spacing" id="NewPayment" action="/NewPayment/AddInvoice" method="post">
<div class="row column-seperation">
<div class="col-md-6">
<h4>Kart Bilgileri</h4>
<div class="row form-row">
<div class="col-md-5">
<input name="FirstName" id="FirstName" type="text" class="form-control" placeholder="Kart Üzerindeki Ad">
</div>
<div class="col-md-7">
<input name="LastName" id="LastName" type="text" class="form-control" placeholder="Kart Üzerindeki Soyad">
</div>
</div>
<div class="row form-row">
<div class="col-md-12">
<input name="CreditCardNumber" id="CreditCardNumber" type="text" class="form-control" placeholder="Kart Numarası">
</div>
</div>
<div class="row form-row">
<div class="col-md-5">
<input name="ExpYear" id="ExpYear" type="text" class="form-control" placeholder="Son Kullanma Yıl (20..)">
</div>
<div class="col-md-7">
<input name="ExpMonth" id="ExpMonth" type="text" class="form-control" placeholder="Son Kullanma Ay (1-12)">
</div>
</div>
<div class="row form-row">
<div class="col-md-5">
<input name="Cv2" id="Cv2" type="text" class="form-control" placeholder="Cv2">
</div>
<div class="col-md-7">
<input name="Amount" id="Amount" type="text" class="form-control" placeholder="Miktar TL ">
</div>
</div>
<div id="container">
<input id="Interests_0__Id" type="hidden" value="" class="iHidden" name="Interests[0].Id"><input type="text" id="InvoiceNumber_0__InvoiceNumber" name="[0].InvoiceNumber"><input type="text" id="Interests_0__InterestText" name="[0].Amount"> <br><input id="Interests_1__Id" type="hidden" value="" class="iHidden" name="Interests[1].Id"><input type="text" id="InvoiceNumber_1__InvoiceNumber" name="[1].InvoiceNumber"><input type="text" id="Interests_1__InterestText" name="[1].Amount"> <br>
</div>
<input type="button" id="btnAdd" value="Add New Item" />
<button class="btn btn-danger btn-cons" type="submit"> Ödemeyi Gerçekleştir</button>
</form>
</div>
</div>
In my controller, i am getting payment model as null.
public ActionResult AddInvoice(Payment payment) {
foreach (var item in payment.Bill)
{
var Billing = new Bill();
Billing.Amount = item.Amount;
Billing.InvoiceNumber = item.InvoiceNumber;
db.Bill.Add(Billing);
db.SaveChanges();
}
return View();
}
}
i complete Marko with an example
public class CombineModel
{
public Bill Bill{ get; set; }
public NewPayment NewPayment{ get; set; }
}
You appear to already have the solution in your model. Your bill object can hold a reference to a related new payment. You can either lazy read the new payment from database or you could assign a new newpayment object to the bill before sending to the view.
View models are good practice, but you might be happy levering the model you have naturally as I just described.
Update
Sorry, this should be:
The other way around - Pass in NewPayment
Add public IEnumarable<Bill> Bills {get; set;} to NewPayment model
And that way, you can access the Bills associated with the given payment.
Code first stuff:
You should decorate Bill's RelPayment with [ForeignKey("NewPaymentID"], so EF (I assume you are using Entity Framework), knows how to wire up the relationship.
You will also likely need to add the following Bills = new List<Bill>(); into a NewPayment constructor.
If you don't like Zakos Solution you can make tuple :
var tuple= new Tuple<Bill,NewPayment>(obj1,obj2);
And in view you will have :
#model Tuple<Bill,NewPayment>
But you should use #Zakos solution.
So you can use ViewModel, take this ViewModel:
public class PaymentBillViewModel
{
public int BillId { get; set; }
public int PaymentId { get; set; }
public string InvoiceNumber { get; set; }
public Int64 Amount { get; set; }
public int? NewPaymentId { get; set; }
public virtual NewPayment RelPayment { get; set; }
public int Id { get; set; }
public string FirstName { get; set; }
public string LstName { get; set; }
public DateTime PaymentDate { get; set; }
public Int64 ProvisionNumber { get; set; }
public Int64 CreditCardNumber { get; set; }
public int ExpMonth { get; set; }
public int ExpYear { get; set; }
public int Cv2 { get; set; }
public Int64 Amount { get; set; }
public string UserId { get; set; }
public string CustomerNote { get; set; }
}
actually put what you need in your View. then in the post action cast the ViewModel to the related Model:
[HttpPost]
public ActionResult Sample(PaymentBillViewModel model)
{
if (ModelState.IsValid)
{
var obj=new NewPayment
{
LstName= model.LstName,
Amount=model.Amount,
//... cast what else you need
}
}
return View();
}
you can use Automapper on casting, for more info about using Automapper take a look at this article.

Categories

Resources