C# MVC Dictionary From HTML Form [duplicate] - c#

How do you properly bind a Dictionary and it's values per key to checkboxes?
I can display them in the HTTPGET but binding the selected values again to HTTPPOST doesn't seem to work.
viewmodel
public class EditViewModel
{
public Foo Foo { get; set; }
public Dictionary<Bar, List<BarVersionEditVM>> Matrix { get; set; }
}
public class BarVersionEditVM
{
public int ID { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public bool IsSupported { get; set; }
}
view:
<form asp-action="Edit">
<div class="row">
#foreach (var kvp in Model.Matrix.OrderByDescending(x => x.Key.Name))
{
<div class="col-md-2 col-lg-2">
<fieldset>
<legend>#kvp.Key.Name</legend>
#foreach (var version in kvp.Value)
{
<div>
<input type="checkbox" id="#version.ID" value="#version.IsSupported" name="#version.Name" #(version.IsSupported ? "checked=\"checked\"" : "") />
<label>#version.Version:</label>
</div>
}
</fieldset>
</div>
}
</div>
<input type="hidden" asp-for="#Model.Foo.ID" />
<input type="submit" value="Save" class="btn btn-default" />
</form>
In the View I tried also to rewrite with foreach and using Html helpers, but without success:
#Html.CheckBoxFor(model => model.Matrix[kvpair.Key][i].IsSupported)
controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(EditViewModel vm) {
// vm is there but Matrix are null.
// and only the ID of Foo property is filled in.
}
any suggestions?

Unless your Dictionary has simple value types for both the Key and Value (e.g. public Dictionary<string, string>), the DefaultModelBinder requires that the form control name attributes be in the format
<input .... name="Matrix[0].Key" value="..." />
<input .... name="Matrix[0].Value[0].ID" value="..." />
<input .... name="Matrix[0].Value[0].Name" value="..." />
There are no HtmlHelper methods that will generate the correct html to allow binding to your Dictionary.
It is far simpler to create simple view model(s) to with IList<T> properties for the collections. Based on the view you have shown, those models would be
public class EditVM
{
public int FooID { get; set; }
public List<BarVM> Bars { get; set; }
}
public class BarVM
{
public string Name { get; set; }
public List<BarVersionVM> Versions { get; set; }
}
public class BarVersionVM
{
public int ID { get; set; }
public string Name { get; set; } // not clear where you use this property
public string Version { get; set; }
public bool IsSupported { get; set; }
}
and your view would then be
#model EditVM
....
#Html.HiddenFor(m => m.FooID)
#for(int i = 0; i < Model.Bars.Count; i++)
{
<fieldset>
<legend>#Model.Bars[i].Name</legend>
#Html.HiddenFor(m => m.Bars[i].Name) // in case you need to return the view in the POST method
#for(int j = 0; j < Model.Bars[i].Versions.Count; j++)
{
<div>
#Html.HiddenFor(m => m.Bars[i].Versions[j].ID)
#Html.CheckBoxFor(m => m.Bars[i].Versions[j].IsSupported)
#Html.LabelFor((m => m.Bars[i].Versions[j].IsSupported, Model.Bars[i].Versions[j].Version)
</div>
}
</fieldset>
}
<input type="submit" value="Save" />

Related

How to get List<object> as parameter in action in ASP.NET Core MVC

I have a class which its name is "Question.cs" and I have another one that its name is "Answer.cs".
I want to send List of Questions to my action instead of one Question and I don't know how can I do it.
In fact, I don't know how to get the Questions as parameter. Can anyone tell me how I can receive the Questions in the action?
Here are my classes:
public class Question
{
[Key]
public int QuestionId { get; set; }
[Required]
[MaxLength(500)]
public string QuestionTitle { get; set; }
[Required]
[MaxLength(500)]
public string QuestionAnswer { get; set; }
//property
public List<Answer> Answers { get; set; }
}
public class Answer
{
[Key]
public int AnswerId { get; set; }
[Required]
public int QuestionId { get; set; }
[Required]
[MaxLength(500)]
public string AnswerTitle { get; set; }
//property
[ForeignKey("QuestionId")]
public Question Question { get; set; }
}
This is my Razor View (Question.cshtml) :
#using GameShop.Data.Domain.QuestIonsAnswers
#model List<GameShop.Data.Domain.QuestIonsAnswers.Question>
#{
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}
<form asp-action="Index" method="POST">
#foreach (var item in Model)
{
<div>#item.QuestionTitle</div>
<input asp-for="#item.QuestionAnswer" Value="#item.QuestionId" />
<br />
}
<input type="submit" class="btn btn-primary" value="ثبت">
</form>
As you see, I used a foreach loop which is give me all of the "Questions". So I can't receive just one "Question" in my action. These are my actions:
public IActionResult Index()
{
return View(_context.Questions.ToList());
}
[HttpPost]
public IActionResult Index(Question question)
{
foreach (var item in question)
{
var ans=new Answer(){
QuestionId=item.QuestionId,
AnswerTitle=item.QuestionAnswer
};
_context.Answers.Add(ans);
_context.SaveChanges();
}
return View(_context.Questions.ToList());
}
I think I have to get List<Question> in my second action but I don't know how?
Can anyone help me step by step?
Please refer to the following steps to modify your code:
In the View page, use for loop to loop through the Model and display the value. code like this:
#model List<WebApplication6.Models.Question>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<form asp-action="Index" method="POST">
#for (var i = 0; i < Model.Count(); i++)
{
<div>#Model[i].QuestionTitle</div>
<input asp-for="#Model[i].QuestionId" type="hidden" />
<input asp-for="#Model[i].QuestionTitle" type="hidden" />
<input asp-for="#Model[i].QuestionAnswer" />
<br />
}
<input type="submit" class="btn btn-primary" value="ثبت">
</form>
In the Post method, change the parameter data type to List<Question>.
[HttpPost]
public IActionResult Index(List<Question> questions)
{
foreach (var item in questions)
{
//var ans = new Answer()
//{
// QuestionId = item.QuestionId,
// AnswerTitle = item.QuestionAnswer
//};
//_context.Answers.Add(ans);
//_context.SaveChanges();
}
return View(_context.Questions.ToList());
}
Then, the result like this:

.NET Core Radio button group is not getting passed to the controller

Model:
public class TaxCertificateMailing
{
public IList<Report> SelectReports { get; set; }
public class Report
{
public string Text { get; set; }
public bool Selected { get; set; }
}
}
View:
#model LandNav.Areas.Reports.Models.TaxCertificateMailing
#{
ViewData["Title"] = "Tax Certificate Mailing List";
}
#using (Html.BeginForm("TaxCertificateMailing", "Reports", FormMethod.Post, new { id = "reportForm", #class = "report-form col-9" }))
{
<!--Start of the form body-->
<div class="row">
<div class="col-12">
<label><b>Select the report to run:</b></label><br />
#for (var x = 0; x < Model.SelectReports.Count; x++)
{
<input type="radio" asp-for="SelectReports" name="#reports" value="#Model.SelectReports[x].Selected" />
<input type="hidden" asp-for="SelectReports[x].Text"/>
<b>#Model.SelectReports[x].Text</b>
}
</div>
</div>
...
Controller:
[HttpPost]
public ActionResult TaxCertificateMailing(
//IFormCollection form
TaxCertificateMailing TCM
)
{
return View();
}
When the form is posted the SelectReports IList has a count of 0. What is the best way to handle posting a radio button group using .net core?
The name attribute of the input field should be #Model.SelectReports[x].Selected.
Use the code below for the for loop;
#for (var x = 0; x < Model.SelectReports.Count; x++)
{
<input type="radio" asp-for="SelectReports" name="#Model.SelectReports[x].Selected" value="#Model.SelectReports[x].Selected" />
<input type="hidden" asp-for="SelectReports[x].Text"/>
<b>#Model.SelectReports[x].Text</b>
}
Assuming only one value is selected at a time (which is how radio buttons are intended to be used), you have some issues with your models. I'd suggest this:
public class TaxCertificateMailing
{
public TaxCertificateMailing()
{
Reports = new HashSet<Report>();
}
public int SelectedReportID { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Report
{
public string Text { get; set; }
public int ID { get; set; }
}
This assumes you're pulling these from some sort of database - change the identifier to whatever makes sense, updating SelectedReportID's type to match.
Then, your view would look something like this:
<form asp-action="TaxCertificateMailing" asp-controller="Reports" method="post" id="reportForm" class="report-form col-9">
<fieldset>
<legend>Select the report to run:</legend>
#foreach (var report in Model.Reports)
{
<div class="form-group form-check">
<input type="radio" asp-for="SelectedReportID" id="report-#(report.ID)" value="#report.ID" class="form-check-input" />
<label for="report-#(report.ID)" class="form-check-label">#report.Text</label>
</div>
}
</fieldset>
</form>

FormMethod.Post returning 0

I have a problem with a FormMethod Post, I'm trying to post one single value (id) and store it in a Session variable, but the value return 0.
This is my code.
#foreach (var item in Model)
{
using (#Html.BeginForm("Index", "ProductSelec", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item.id, new { value = "#Html.DisplayFor(modelItem => item.id)" })
<div class="AppOpt">
<button type="submit" name="submit" style="background-image: url('../Content/ICONS/SystemApp/#Html.DisplayFor(modelItem => item.img)');border-radius: 20px;background-size: cover;background-repeat: no-repeat;" class="AppImg">
<div class="OptNameRec">
<div class="OptIcon">
<img src='~/Content/ICONS/#Html.DisplayFor(modelItem => item.icon)'>
</div>
<div>
<p>#Html.DisplayFor(modelItem => item.nombre)</p>
</div>
<div class="clear"></div>
</div>
<div class="OptImage"></div>
</button>
</div>
}
}
The form is inside the foreach, becuase I'm creating the elements dinamically from a DB.
I want to store the item.id clicked.
This is my Controller
public ActionResult Index()
{
return View(db.aplicaciones.ToList());
}
public ActionResult ProductFamily()
{
return View();
}
[HttpPost]
public int Index(aplicaciones aplicaciones)
{
Session["appID"] = aplicaciones.id;
return aplicaciones.id;
}
and this is my Model.
public partial class aplicaciones
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public aplicaciones()
{
this.appNfam = new HashSet<appNfam>();
}
public int id { get; set; }
public string nombre { get; set; }
public string icon { get; set; }
public string img { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<appNfam> appNfam { get; set; }
}
I was trying to create another Model, but when I added, the foreach didn't read the values from the database.
I hope you can help me.
Change your controller method to:
[HttpPost]
public int Index(int id)
{
Session["appID"] = id;
return id;
}
Change your Html.BeginForm to be:
#using (Html.BeginForm("Index", "ProductSelec", new { id = item.id },FormMethod.Post, new { })
You should also be able to remove the hidden field since the ID will be posted by itself from your form action.

How to create an entity with a Collection of another entity

As I am new to an ASP.NET Core 2.0 MVC I have a little demo app where I want to add a new entity of Type Lineup which has a ICollection of type Player in its model.
The involved classes are
public class Lineup
{
public int LineupID { get; set; }
public int PlayerID { get; set; }
public ICollection<Player> Attendants { get; set; }
}
and
public class Player
{
public int PlayerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
My Create Lineup view looks like this
<form asp-action="Create">
<div class="form-horizontal">
<h4>Lineup</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Attendants" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="Attendants" class="form-control"
asp-items="ViewBag.Players" multiple="multiple">
</select>
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
The ViewBag.Players is part of the LineupsController:
private void PopulatePlayersDropDownList(object selectedPlayer = null)
{
var playersQuery = from p in _context.Player
orderby p.Name
select p;
ViewBag.Players = new SelectList(playersQuery.AsNoTracking(), "PlayerID",
"Name", selectedPlayer);
}
That's how it looks so far:
but inside my Create method
public async Task<IActionResult> Create([Bind("Attendants")] Lineup lineup)
{
if (ModelState.IsValid)
{
_context.Add(lineup);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(lineup);
}
the Attendants count of the Lineup entity is 0 and not 2 as expected from the sample above. What is wrong here? Thanks for any help.
You need to instantiate your property when you create the object. Otherwise, the property is the default, which is null:
public class Lineup
{
public int LineupID { get; set; }
public int PlayerID { get; set; }
public ICollection<Player> Attendants { get; set; } = new List<Player>();
}
ok, these 2 steps solved my problem:
see answer of krillgar
changing the method signature of Create to
public async Task Create(int[] PlayerIDs)
{
...
}

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