I have some problem.
I have next model:
public class DocumentViewModel
{
public string Nazvanie { get; set; }
public Author DocumentAutors { get; set; }
}
public class Author
{
public long Id { get; set; }
public List<IPerson> Authors { get; set; }
}
public interface IPerson
{
long Id { get; set; }
}
public class PersonUL : IPerson
{
public long Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
public class PersonIP : IPerson
{
public long Id { get; set; }
public string FirstName { get; set; }
public string SecondNAme { get; set; }
public string PostAddress { get; set; }
}
In .cshtml
#model DocumentViewModel
#if (Model.DocumentAutors.Authors != null && Model.DocumentAutors.Authors.Count > 0)
{
for (int i = 0; i < Model.DocumentAutors.Authors.Count; i++)
{
if (Model.DocumentAutors.Authors is PersonUL )
{
<div class="form-group">
<label asp-for="#Model.DocumentAutors.Authors[i].Name" class="col-md-10 control-label"></label>
<div class="col-md-10">
<input asp-for="#Model.DocumentAutors.Authors[i].Name" class="form-control" />
<span asp-validation-for="#Model.DocumentAutors.Authors[i].Name" class="text-danger"></span>
</div>
</div>
}
}
}
Model.DocumentAutors.Authors[i] don't contain "Name" field, because it's interface. I need cast it, but if i write
if (Model.DocumentAutors.Authors is PersonUL )
{
PersonUL ul = (PersonUL)Model.DocumentAutors.Authors[i];
<div class="form-group">
<label asp-for="#ul.Name" class="col-md-10 control-label"></label>
<div class="col-md-10">
<input asp-for="#ul.Name" class="form-control" />
<span asp-validation-for="#ul.Name" class="text-danger"></span>
</div>
</div>
}
i will get html with wrong name like this
<input class="form-control" type="text" id="Name" name="Name" value="566">
instead
<input class="form-control" type="text" id="DocumentAutors.Authors[0].Name" name="DocumentAutors.Authors[0].Name" value="566">
and ModelBinder will not bint this field into Authors List.
Is there a solution for this problem or should I make one generic model for PersonUL and PersonIP with all fields, which I don't really like it?
I am sorry but IMHO I don't see any advantages in the interface. It only makes the code more confused.
Why you don't try
public class Author
{
public long Id { get; set; }
public List<PersonIP> IpAuthors { get; set; }
public List<PersonUL> UlAuthors { get; set; }
}
or even better
public class DocumentViewModel
{
public string Nazvanie { get; set; }
public long AuthorId { get; set; }
public List<PersonIP> IpAuthors { get; set; }
public List<PersonUL> UlAuthors { get; set; }
}
Related
I'm working on a small library website where you should be able to post a comment to each individual book. The problem is in my view that I can't say "Asp-for='BookComment.Name'" since my BookComment is a list in my Book Model
My Book Model
public class Book
{
[Key]
public int BookID { get; set; }
[Required]
[Column(TypeName = "Varchar(75)")]
public string Title { get; set; }
[Required]
[Column(TypeName = "Varchar(75)")]
public string Author { get; set; }
[Required]
[Column(TypeName = "Varchar(13)")]
public string Isbn { get; set; }
[Required]
[Column(TypeName = "Varchar(50)")]
public string Publisher { get; set; }
public int Sites { get; set; }
public DateTime ReleaseDate { get; set; }
public string Summary { get; set; }
public string Picture { get; set; }
public DateTime AddedDate { get; set; }
public int Stars { get; set; }
public List<BookCategory> BookCategory { get; set; } = new List<BookCategory>();
public List<BookComment> BookComment { get; set; } = new List<BookComment>();
}
BookComment Model:
public class BookComment
{
[Key]
public int BookCommentID { get; set; }
[Required]
public int BookID { get; set; }
[Column(TypeName = "Varchar(50)")]
public string Name { get; set; }
[Column(TypeName = "Varchar(100)")]
public string Email { get; set; }
public string Review { get; set; }
public DateTime Date { get; set; }
public decimal Stars { get; set; }
}
My Book Controller
public class BookController : Controller
{
private readonly ApplicationDbContext _db;
public BookController(ApplicationDbContext db)
{
_db = db;
}
public IActionResult Book(int? id)
{
var book = _db.Books.Include(o => o.BookComment).FirstOrDefault(p => p.BookID == id);
return View(book);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Book(BookComment comment)
{
_db.bookComments.Add(comment);
_db.SaveChanges();
return RedirectToAction("Book");
}
}
Snippet of my form
#model LibraryNew.Models.Book
<h5 class="mt-4">Tilføj en anmeldelse</h5>
<p>Din email vil ikke blive offentliggjort</p>
<form asp-action="Book" method="post"></form>
<div class="my-3">
<div class="form-group">
<label for="exampleFormControlSelect1">Antal stjerner:</label>
<select class="form-control col-md-1" id="exampleFormControlSelect1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="1">5</option>
</select>
</div>
</div>
<div class="form-group">
<label for="exampleFormControlInput1">Email address</label>
<input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name#example.com">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">Email address</label>
<input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name#example.com">
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">Example textarea</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
</div>
I can't say <select asp-for="BookComment.Stars"
Any help is appreciated. If any further information is needed please let me know!
In your view code try replacing #model LibraryNew.Models.Book with #model LibraryNew.Models.BookComment so you can have access to it in asp-for since what you are trying to do is post a single BookComment to add it to a book in your controller.
It looks like what is needed is to be able to use different models in a single page to achieve this you can create a single class that contains the models you will need for a single page for example
public class BookLibrary
{
public Book Book { get; set; }
public BookComment BookComment { get; set; }
public Author Author{ get; set; }
}
then in your view code you use #model LibraryNew.Models.BookLibrary and in your asp-for you will be able to access BookComment by using Model.BookComment
I'm working on Online Quiz System.
My models:
EXAM.cs
public Exam()
{
this.Questions = new HashSet<Question>();
}
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
public ICollection<Question> Questions { get; set; }
[Required]
public int QuestionsCount { get; set; }
Question.cs
public Question()
{
this.Id = Guid.NewGuid().ToString();
this.Choices = new HashSet<Choice>();
}
[Key]
public string Id { get; set; }
[Required]
public string Text { get; set; }
public ICollection<Choice> Choices { get; set; }
public bool IsActive { get; set; }
[Required]
public int ExamId { get; set; }
[ForeignKey(nameof(ExamId))]
public Exam Exam { get; set; }
Choice.cs
public Choice()
{
this.Id = Guid.NewGuid().ToString();
}
[Key]
public string Id { get; set; }
[Required]
public string QuestionId { get; set; }
[ForeignKey(nameof(QuestionId))]
public Question Question { get; set; }
[Required]
public string Text { get; set; }
[Required]
public bool IsTrue { get; set; }
Answer.cs
public Answer()
{
this.Id = Guid.NewGuid().ToString();
}
[Key]
public string Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey(nameof(UserId))]
public ApplicationUser User { get; set; }
[Required]
public string ChoiceId { get; set; }
[ForeignKey(nameof(ChoiceId))]
public Choice Choice { get; set; }
Each question can have many answers. Every answer can be true or false. If a question has more than one answer that is true, then i use checkboxes (multiple choice), in other case i use radiobuttons (single choice).
Take.cshtml - Exam View
<form method="post" class="form-horizontal">
#foreach (var q in Model.Questions)
{
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">#(counter +". "+q.Text)</h3>
</div>
<!-- /.box-header -->
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
#{
var trueChoices = q.Choices.Where(c => c.IsTrue == true).Count();
var order = Model.AnswersOrder;
var currentChoices = q.Choices;
}
#if (order == OrderType.Fixed)
{
currentChoices = currentChoices.OrderBy(c => c.CreatedOn).ToList();
}
else
{
currentChoices = examService.GetMixedChoices(currentChoices);
}
#foreach (var choice in currentChoices)
{
<tr>
#if (trueChoices <= 1)
{
<td class="col-xs-2"><input name='asd' value='asd' type="radio" /></td>
}
else
{
<td class="col-xs-2"><input name="asd" value='asd' type="checkbox" /></td>
}
<td class="col-xs-8">#choice.Text</td>
</tr>
}
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
counter++;
}
<div class="box-footer">
<button id="submitExam" type="submit" class="btn btn-block btn-primary">Submit</button>
</div>
</form>
My question is how can i submit this form? I want to put the information in one table with user id and choice id and to check if the selected choice is correct or not.
Thank you!
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" />
}
I'm trying to make a form that i could save a file(image) , but it shows me an error:
InvalidOperationException: The property 'Product.Image' is of an interface type ('IFormFile'). If it is a navigation property manually configure the relationship for this property by casting it to a mapped entity type, otherwise ignore the property from the model.
Apply
I dont know how to fix it , here's the code:
Product.cs
public class Product
{
public Product()
{
OrderDetails = new HashSet<OrderDetails>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? CategoryId { get; set; }
public decimal? Price { get; set; }
public int? Quantity { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<OrderDetails> OrderDetails { get; set; }
public virtual Category Category { get; set; }
}
ProductFormViewModel.cs
public class ProductFormViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? CategoryId { get; set; }
public decimal? Price { get; set; }
public int? Quantity { get; set; }
public IFormFile Image { get; set; }
}
Create Action
[HttpGet]
public IActionResult Create()
{
var categories = _repository.GetCategories().ToList();
var categoriesModel = categories.Select(p => new
{
p.Id,
p.Name
});
ViewBag.Categories = new SelectList(categoriesModel, "Id", "Name");
return View();
}
[HttpPost]
public IActionResult Create(ProductFormViewModel product)
{
var file = product.Image; // **it returns NULL**
var upload = Path.Combine(_environment.ContentRootPath, "wwwroot\\uploads", product.Name);
if (!Directory.Exists(upload))
Directory.CreateDirectory(upload);
var filePath = Path.Combine(upload, file.FileName);
if (file.Length > 0)
{
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(fileStream);
}
}
var producti = new Product();
producti.CategoryId = product.CategoryId;
producti.Description = product.Description;
producti.Name = product.Name;
producti.Price = product.Price;
producti.Quantity = product.Quantity;
producti.ImagePath = filePath;
_repository.AddProduct(producti);
_repository.SaveChanges();
return RedirectToAction("Index","Products");
}
Create.cshtml
#model ProductFormViewModel
<br />
<br />
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
</div>
<div class="panel-body">
<form class="form-group" asp-action="Create" asp-controller="Products" method="post">
<input type="hidden" asp-for="Id"/>
<div class="col-md-12">
<div class="form-group col-md-6">
<label asp-for="Name" class="control-label col-md-3"></label>
<input asp-for="Name" type="text" class="form-control col-md-3"/>
</div>
<div class="form-group col-md-6">
<label asp-for="CategoryId" class="control-label col-md-3"></label>
<select asp-for="CategoryId" asp-items="#ViewBag.Categories" class="form-control col-md-3">
<option hidden disabled selected >Select One</option>
</select>
</div>
<div class="form-group col-md-6">
<label asp-for="Description" class="control-label col-md-3"></label>
<textarea asp-for="Description" class="form-control" rows="4"></textarea>
</div>
<div class="form-group col-md-6">
<label asp-for="Price" class="control-label col-md-3"></label>
<input type="text" asp-for="Price" class="form-control col-md-3"/>
</div>
<div class="form-group col-md-6">
<label asp-for="Quantity" class="control-label col-md-3"></label>
<input type="text" asp-for="Quantity" class="form-control col-md-3"/>
</div>
<div class="form-group col-md-12">
<label class="control-label">Select Image</label>
<input asp-for="Image" type="file" class="btn-file"/>
</div>
<div class="form-group col-md-12 text-center">
<input type="submit" class="btn btn-success" value="Save"/>
</div>
</div>
</form>
</div>
</div>
</div>
IFormFile is a type used by the ASP.NET Core framework and it does not have a sql server type equivalent.
For your domain model store it as byte[] and when you work with views, is ok for you to use the IFormFile type.
ProductModel:
public class Product
{
public Product()
{
OrderDetails = new HashSet<OrderDetails>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? CategoryId { get; set; }
public decimal? Price { get; set; }
public int? Quantity { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<OrderDetails> OrderDetails { get; set; }
public virtual Category Category { get; set; }
}
ProductViewModel:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? CategoryId { get; set; }
public decimal? Price { get; set; }
public int? Quantity { get; set; }
public IFormFile Image { get; set; }
}
Controller method:
[HttpGet]
public IActionResult Create()
{
var categories = _repository.GetCategories().ToList();
var categoriesModel = categories.Select(p => new
{
p.Id,
p.Name
});
ViewBag.Categories = new SelectList(categoriesModel, "Id", "Name");
return View();
}
[HttpPost]
public IActionResult Create(ProductViewModel model)
{
// Save the image to desired location and retrieve the path
// string ImagePath = ...
// Add to db
_repository.Add(new Product
{
Id = model.Id,
ImagePath = ImagePath,
// and so on
});
return View();
}
Also specify to the form enctype="multipart/form-data" in your view.
using System.ComponentModel.DataAnnotations.Schema;
namespace model{
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? CategoryId { get; set; }
public decimal? Price { get; set; }
public int? Quantity { get; set; }
[NotMapped]
public IFormFile Image { get; set; }
}
}
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.